1

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.

Sergey Tsypanov
  • 3,265
  • 3
  • 8
  • 34

1 Answers1

1

As it was pointed out by Daniel Fuchs we need to compile the code with -parameters flag.

https://docs.oracle.com/en/java/javase/18/docs/specs/man/javac.html

Sergey Tsypanov
  • 3,265
  • 3
  • 8
  • 34