13

I ran into an issue while manipulating some bytecode, where a certain final String constant was not inlined by the java compiler (Java 8), see the example below:

public class MyTest
{
  private static final String ENABLED  = "Y";
  private static final String DISABLED = "N";

  private static boolean isEnabled(String key) {
      return key.equals("A");
  }

  private static String getString(String key, String value) {
      return key + value;
  }

  public static void main(String[] args) throws Exception {
    String flag = getString("F", isEnabled("A") ? ENABLED : DISABLED);
    System.out.println(flag);

    String flag2 = getString("F", isEnabled("A") ? ENABLED : DISABLED);
    System.out.println(flag2);
  }
}

Resulting bytecode with javac (1.8.0_101)

public static void main(java.lang.String[]) throws java.lang.Exception;
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #8                  // String F
         2: ldc           #2                  // String A
         4: invokestatic  #9                  // Method isEnabled:(Ljava/lang/String;)Z
         7: ifeq          16
        10: getstatic     #10                 // Field ENABLED:Ljava/lang/String;
        13: goto          19
        16: getstatic     #11                 // Field DISABLED:Ljava/lang/String;
        19: invokestatic  #12                 // Method getString:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
        22: astore_1
        23: getstatic     #13                 // Field java/lang/System.out:Ljava/io/PrintStream;
        26: aload_1
        27: invokevirtual #14                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        30: ldc           #8                  // String F
        32: ldc           #2                  // String A
        34: invokestatic  #9                  // Method isEnabled:(Ljava/lang/String;)Z
        37: ifeq          46
        40: getstatic     #10                 // Field ENABLED:Ljava/lang/String;
        43: goto          49
        46: getstatic     #11                 // Field DISABLED:Ljava/lang/String;
        49: invokestatic  #12                 // Method getString:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
        52: astore_2
        53: getstatic     #13                 // Field java/lang/System.out:Ljava/io/PrintStream;
        56: aload_2
        57: invokevirtual #14                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        60: return

You can see that the second time the fields ENABLED and DISABLED are being accessed, the compiler did not inline their values (using ldc), but instead used getstatic to access the field directly. Testing it with other compilers (Java 7, Eclipse) did not trigger the same behavior and the constants were always inlined.

Can this be considered a compiler bug, or is it allowed to not inline string constants all the time according to the JLS?

Holger
  • 285,553
  • 42
  • 434
  • 765
T. Neidhart
  • 6,060
  • 2
  • 15
  • 38
  • 1
    Define 'inlined'. The JLS require *pooling*. Is that what you're talking about? – user207421 Sep 08 '16 at 08:48
  • 1
    Inlining means to replace the getstatic instruction with an instruction that directly pushes the constant value onto the stack, e.g. ldc in case of strings, or iconst_X/bipush/sipush/... in case of integer primitives, ... – T. Neidhart Sep 08 '16 at 08:55
  • The JLS does not mention *any* bytecode-instructions nor the concept of a constant-pool - it describes the *language*. – piet.t Sep 08 '16 at 09:09
  • Sure, I wanted to give a concrete example, based on actual instructions. I understand that the JLS does not talk about byte-code level instructions itself in this matter. – T. Neidhart Sep 08 '16 at 09:21
  • 2
    @piet.t: I removed the byte code tag, as the language specification does indeed not deal with byte code. However, the *behavior* is defined without ambiguity, which allows to answer the question. – Holger Sep 08 '16 at 11:04
  • 2
    Well spotted. This is indeed a bug, and it's been fixed by the fix to [JDK-8066871](https://bugs.openjdk.java.net/browse/JDK-8066871) which was integrated in JDK 9 and backported to JDK 1.8.0_102 -- which is 1.8.0_101 plus a patch set. Note that JDK-8066871 has different symptoms, but the same fix corrects that error as well as the one described here. – Stuart Marks Sep 08 '16 at 18:49
  • 1
    I just filed a bug report (JI-9043578) as I did not find a related entry in the bug database. I guess it can be closed then. – T. Neidhart Sep 08 '16 at 19:28
  • @T.Neidhart Thanks for filing the bug! I've linked them together as duplicates. I think we probably need a regression test in this area, as this bug was present in the first release of JDK 8 and in two years' worth of updates. – Stuart Marks Sep 12 '16 at 23:05

1 Answers1

12

Yes, the “inlining” behavior is mandated by the specification:

13.1. The Form of a Binary

  1. A reference to a field that is a constant variable (§4.12.4) must be resolved at compile time to the value V denoted by the constant variable's initializer.

    If such a field is static, then no reference to the field should be present in the code in a binary file, including the class or interface which declared the field. Such a field must always appear to have been initialized (§12.4.2); the default initial value for the field (if different than V) must never be observed.

    If such a field is non-static, then no reference to the field should be present in the code in a binary file, except in the class containing the field. (It will be a class rather than an interface, since an interface has only static fields.) The class should have code to set the field's value to V during instance creation (§12.5).

Note, how this precisely addresses you scenario: “If such a field is static, then no reference to the field should be present in the code in a binary file, including the class or interface which declared the field”.

In other words, if you encounter a compiler not adhering to this, you found a compiler bug.


As an addendum, the starting point for finding this information was:

4.12.4. final Variables

A constant variable is a final variable of primitive type or type String that is initialized with a constant expression (§15.28). Whether a variable is a constant variable or not may have implications with respect to class initialization (§12.4.1), binary compatibility (§13.1, §13.4.9), and definite assignment (§16 (Definite Assignment)).

Holger
  • 285,553
  • 42
  • 434
  • 765