Cfinvokeargument casts numeric arguments to string

Ok, I had a thought. I realize that all your replies and tests are using the cfscript style of coding, where my style is the original cfml. So I wrote 2 tests:

old.cfc:

<cfcomponent>
<cffunction name="testNumbers" output="no" access="remote" returntype="struct">
	<cfargument name="tStr" type="string" required="yes" />
    <cfargument name="tNum" type="numeric" required="yes" />    
   
    <cfreturn arguments />
</cffunction>
</cfcomponent>

test.cfc (I hope this is the same, I have not delved into the script version)

component {
	function testNumbers( required string tStr, required numeric tNum)
    {
    	return arguments
    }
}

And now for test.cfm:

<cfinvoke component="cfc.old" method="testNumbers" returnvariable="r">
	<cfinvokeargument name="tStr" value="string to test" >
    <cfinvokeargument name="tNum" value=1234 >
</cfinvoke>

<cfdump var="#r#" />


<cfscript>
	obj= createObject("component","cfc.test");
	a = invoke( obj, "testNumbers", {tStr="a string to test", tNum=1234});
    writeDump(a);
</cfscript>

And do you want to see the answer? You might not…

Scope Arguments
tStr	1	string	string to test
tNum	2 string	1234

Scope Arguments
tStr	1	string	a string to test
tNum	2 number	1234

And to make it easier on the eyes:
Screenshot 2025-05-18 023534

Something is not right here, or am I mistaken?

<cfinvoke component="cfc.old" method="testNumbers" returnvariable="r">
	<cfinvokeargument name="tStr" value="string to test" >
    <cfinvokeargument name="tNum" value="#1234#" >
</cfinvoke>
<cfset a1=234>
<cfinvoke component="test" method="testNumbers" returnvariable="r">
	<cfinvokeargument name="tStr" value="string to test" >
    <cfinvokeargument name="tNum" value="#a1#" >
</cfinvoke>

try “#1234#” or set to variable result is number

Thanks for pointing this out. This is another thing different from OpenBlueDragon. I can send a number string via cfinvokeargument and with the type=“numeric” it converts it to a number.

I do not know if this is a bug in Lucee or not. Why does the cfscript return it properly, but not the cfml?

I took my above test 1 step further since you noted this. and sent

<cfscript>
	obj= createObject("component","cfc.old");
	a = invoke( obj, "testNumbers", {tStr="a string to test", tNum=1234});
    writeDump(a);
</cfscript>

And that returns as number. So why what makes calling cfinvoke different from invoke()? Should they not act the same?

Always, nothing beats problems with repos!

That looks like indeed a bug, generally speaking, Lucee only checks that an argument can be cast to the specified type, it doesn’t actually convert the type (less GC! faster code, ymmv, except with primitive types).

But there are edge cases and this is one.

With Lucee 7 we recently fixed cfproperty to preserve types and allow complex types as default, previously everything was being converted to strings

https://luceeserver.atlassian.net/browse/LDEV-801

So now for the fun stuff, if you have java support enabled in your vs code, you can drag the lucee generated bytecode classes from cfclasses into vscode and it will automatically decode the bytecode into java.

So let’s have a look…

here’s the extract of the tag bytecode

try {
	do {
		var1.write("\r\n\t");
		InvokeArgument var4 = (InvokeArgument)((PageContextImpl)var1).use("lucee.runtime.tag.InvokeArgument", "cfinvokeargument", 0, "C:\\lucee\\tomcat\\webapps\\ROOT\\invoke\\test.cfm:2");

		try {
			var4.setName("tStr");
			var4.setValue("string to test");
			var4.doStartTag();
			if (var4.doEndTag() == 5) {
			throw Abort.newInstance(0);
			}
		} finally {
			((PageContextImpl)var1).reuse(var4);
		}

		var1.write("\r\n    ");
		InvokeArgument var6 = (InvokeArgument)((PageContextImpl)var1).use("lucee.runtime.tag.InvokeArgument", "cfinvokeargument", 0, "C:\\lucee\\tomcat\\webapps\\ROOT\\invoke\\test.cfm:3");

		try {
			var6.setName("tNum");
			var6.setValue("1234");
			var6.doStartTag();
			if (var6.doEndTag() == 5) {
			throw Abort.newInstance(0);
			}
		} finally {
			((PageContextImpl)var1).reuse(var6);
		}

		var1.write("\r\n");
	} while(var2.doAfterBody() == 2);
} finally {
	if (var3 != 1) {
		var1.popBody();
	}
}

You can see that the tag version is passing the num argument as a string

var6.setValue("1234");

this is what the script version looks like, you can see the types being preserved

var1.write("\r\n\r\n\r\n");
	if (true) {
		((PageContextImpl)var1).us(KeyConstants._OBJ, CreateObject.call(var1, "component", "test"));
		((PageContextImpl)var1).us(KeyConstants._A,
				lucee.runtime.functions.dynamicEvaluation.Invoke.call(
					var1, ((
						PageContextImpl)var1).us(KeyConstants._OBJ), "testNumbers",
				LiteralStruct.call(var1,
					new Object[]{FunctionValueImpl.newInstance(keys[0], "a string to test"),
					FunctionValueImpl.newInstance(keys[1],
					LiteralValue.toNumber(var1, 1234L))}
				)
			)
		);
		CFFunction.call(var1, new Object[]
	}

Bug filed

https://luceeserver.atlassian.net/browse/LDEV-5629

Testcase

all that said, this is really old crufty code style, I haven’t used these tags in over a decade or more.

Lucee supports doing this really simply if you need to be doing dynamic invoking of methods

<cfscript>
    obj = new component {
	    function testNumbers( required string tStr, required numeric tNum) {
    		return arguments
	    }
    }

    dump(obj["testNumbers"](1,2))
</cfscript>

Yeah, I never jumped onto the cfscript bus when it drove by. It is something I am going to be getting into, as I like the cleaner code look and javascript style. The slow take up is a lack of time to get into cfscript.

As for vscode, I use sublime text 4. But I may look into vscode some day.