This question is kind of continuation of one previously asked in Preserving parameter/argument names in compiled java classes and the answer is accepted, but the solution seems to be not working.
With ad-hoc built JDK 19 (having exposed Executable.hasRealParameterData()
) I take the code
public class Main {
public static void main(String[] args) throws NoSuchMethodException {
Method foo = Main.class.getMethod("foo", String.class, int.class);
System.out.println(foo.hasRealParameterData());
}
public void foo(String parameter1, int parameter2) {}
}
and compile it with
% javac Main.java
Then I run compiled Java class and it prints false
into console. This is fine because decompiled Main
class looks like
public class Main {
public Main() {}
public static void main(String[] var0) throws NoSuchMethodException {
Method var1 = Main.class.getMethod("foo", String.class, Integer.TYPE);
System.out.println(var1.hasRealParameterData());
}
public void foo(String var1, int var2) {} // parameter names are not 'real'
}
i.e. parameter names are synthetic.
This behaviour is understandable.
Then I take the same Java sources and recompile the class with
javac -g:vars Main.java
I run the same code again and again it prints false
to console. This puzzles me, because now the compiled code looks different:
public class Main {
public Main() {}
public static void main(String[] args) throws NoSuchMethodException {
Method foo = Main.class.getMethod("foo", String.class, Integer.TYPE);
System.out.println(foo.hasRealParameterData());
}
public void foo(String parameter1, int parameter2) {} // parameter names are 'real'
}
Same happens if for recompilation I use plain -g
flag (generates all auxiliary data).
Now let's stop calling JDK's private API and rely only on the methods available out-of-the-box, e.g. Parameter.isNamePresent()
(this one calls Executable.hasRealParameterData()
under the hood):
public static void main(String[] args) throws NoSuchMethodException {
Method foo = Main.class.getMethod("foo", String.class, int.class);
Parameter parameter1 = foo.getParameters()[0];
Parameter parameter2 = foo.getParameters()[1];
System.out.println(parameter1.isNamePresent());
System.out.println(parameter2.isNamePresent());
}
public void foo(String parameter1, int parameter2) {}
And again, no matter how I compile the sources, this code prints false false
.
The problem here is that Executable.hasRealParameterData()
calls native method getParameters0()
implemented like:
JVM_ENTRY(jobjectArray, JVM_GetMethodParameters(JNIEnv *env, jobject method))
{
// method is a handle to a java.lang.reflect.Method object
Method* method_ptr = jvm_get_method_common(method);
methodHandle mh (THREAD, method_ptr);
Handle reflected_method (THREAD, JNIHandles::resolve_non_null(method));
const int num_params = mh->method_parameters_length();
if (num_params < 0) {
// A -1 return value from method_parameters_length means there is no
// parameter data. Return null to indicate this to the reflection
// API.
assert(num_params == -1, "num_params should be -1 if it is less than zero");
return (jobjectArray)NULL;
} else {
// Otherwise, we return something up to reflection, even if it is
// a zero-length array. Why? Because in some cases this can
// trigger a MalformedParametersException.
// make sure all the symbols are properly formatted
for (int i = 0; i < num_params; i++) {
MethodParametersElement* params = mh->method_parameters_start();
int index = params[i].name_cp_index;
constantPoolHandle cp(THREAD, mh->constants());
bounds_check(cp, index, CHECK_NULL);
if (0 != index && !mh->constants()->tag_at(index).is_utf8()) {
THROW_MSG_0(vmSymbols::java_lang_IllegalArgumentException(),
"Wrong type at constant pool index");
}
}
objArrayOop result_oop = oopFactory::new_objArray(vmClasses::reflect_Parameter_klass(), num_params, CHECK_NULL);
objArrayHandle result (THREAD, result_oop);
for (int i = 0; i < num_params; i++) {
MethodParametersElement* params = mh->method_parameters_start();
// For a 0 index, give a NULL symbol
Symbol* sym = 0 != params[i].name_cp_index ?
mh->constants()->symbol_at(params[i].name_cp_index) : NULL;
int flags = params[i].flags;
oop param = Reflection::new_parameter(reflected_method, i, sym,
flags, CHECK_NULL);
result->obj_at_put(i, param);
}
return (jobjectArray)JNIHandles::make_local(THREAD, result());
}
}
JVM_END
From the code I see that null
is returned only in case when there's no parameter data. But the data is there, in the compiled class.
So my question is whether this is a bug or am I doing something wrong?
P.S. Parameter.isNamePresent()
works unexpectedly even when I run it on conventional, not hacked JDK.
P.P.S. In compiled code I see 'real' parameter names, but if I stop at debug point in IDEA parameter name is suddenly arg0
in Parameter.name
field.