4

It is well known that the Java compiler pulls in constant field values from other classes at compile time. The resulting class file does not contain a Constant Pool entry (of any type) for such constants.

Q: can the compiler be told to do just that? (Oracle JDK 7 would be nice)

As illustration, consider a piece of code out.println(some.other.class.FOO) that reads FOO (say, public static final int FOO = 1234) and outputs it. I am able to find the references to println no problem, but the the constant is turned into an anonymous sipush 1234.

For class-level dependency analysis, it would be great to have transparency here! Note I'm not asking to make any changed value there appear somehow in the dependent code (see loads of other SO questions on that) ...

I'm thinking about a Java Compiler API plug-in to javac, but that sounds like a bit far-fetched? Any ideas?

mgaert
  • 2,338
  • 21
  • 27
  • 1
    Related: http://stackoverflow.com/questions/3524150/is-it-possible-to-disable-javacs-inlining-of-static-final-variables?rq=1 –  Mar 02 '15 at 17:34
  • Can you be more specific on what you want the compiler to be told ? .. I think I may have an interesting answer... – Jean-François Savard Mar 02 '15 at 17:43
  • Thanks @ RC for the other pointer. Sorry I had missed this one when searching (and probably some more...). @Jean-François, the idea is to create a synthetic (unused) Fieldref (like for GETSTATIC opcodes) into the Constant Pool, somehow. I realize that this would be for *own* code only (compilation required), but would keep the dep checker tool at least on class-level, not on source code level. – mgaert Mar 02 '15 at 17:57
  • This would be doable in C, where `.obj` files (Java `.class`) are _linked_ to an executable (Java .jar), no longer using `.obj` files in the end product. In Java the .class is already submitted to a final optimization process. It would make more sense in java working with source code parsing, with AST (abstract syntax trees), see ASM and others. – Joop Eggen Mar 02 '15 at 20:47
  • @JoopEggen, [ASM](http://forge.ow2.org/projects/asm) works on byte code level as well, so there's no access to the AST. To bridge the gap to the source code, I am thinking about a "processor" plug-in to javac. This should have access to the AST, and can influence the byte code. – mgaert Mar 03 '15 at 17:23

1 Answers1

2

Only final variables initialized to constant expressions can be so inlined. So if you want to avoid compile time inlining like that, the obvious approach is to either make the field non-final or make the initializing expression complicated enough that it is no longer considered constant (e.g. (null == null) ? 1234 : 0)) ¹.

Once you've already run the compiler it's too late, because the generated code is exactly equivalent to if you had inserted the constant inline instead of referencing a field.

If you are doing static analysis on the source code, you can obviously just use any of the standard dependency finding tools.

Holger
  • 285,553
  • 42
  • 434
  • 765
Antimony
  • 37,781
  • 10
  • 100
  • 107
  • Not entirely true. If you have a non-static final boolean with a preassigned value that is used within a conditional expression within the same class, that value is also inlined in order to remove the conditional. For the sake of completness. – Rafael Winterhalter Mar 03 '15 at 07:32
  • @Rafael Winterhalter: the use site doesn’t need to be in the same class, regardless of whether the field is `static` or not. Of course, it has to be in the case of `private` fields, but that’s also independent of the `static` flag. And, by the way, *every* `final` variable can be a compile-time constant, even local variables. (yes, that implies that if you declare a local variable like `final int foo=10;` you can use it’s name in a follow-up `switch` statement as a `case` label). Seems to be a common misconception to think that it was limited to `static` fields (or fields in general). – Holger Mar 03 '15 at 08:49
  • @Holger you are right, compile-time constants have nothing to do with fields in the first place, as it is implicit from the spec: (http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.28). I tried to add something to the answer. Mainly, I forgot about inner classes as I just assumed that non-static fields from other classes would not make a difference here as they cannot be referred to without going through an instance of that class which are again never compile-time constants. But you are right, of course. – Rafael Winterhalter Mar 03 '15 at 09:26
  • @Rafael Winterhalter: you have to access an instance field via an instance, even if it’s a compile-time constant, to fulfill the formal rules but the value will get inlined or, more precise, the generated bytecode will contain a `null`-check to emulate the instance access (`javac` uses an `instance.getClass()`) followed by using the inlined constant value. Anyway, there is little use for instance fields that are actually compile-time constants as they are wasting per-instance storage for something that is never accessed (unless Reflection is involved). – Holger Mar 03 '15 at 09:36
  • @Holger I did not know that, thanks for the additional information. But do you have an example? I just tried to reproduce what you described but I cannot observe the `getClass`-driven `null`-check for `Foo$Bar`: `class Foo { final String foo = "foo"; class Bar { void bar() { System.out.println(foo); } } }` The value is just inlined without checking for the outer instance to not be `null`. – Rafael Winterhalter Mar 03 '15 at 09:49
  • @Rafael Winterhalter: you are accessing the field via `this`; there is no need to check `this` against `null`. (this includes `Outer.this`) I was talking about accessing the field from another class, not being an inner class, where it is implied that the reference will be an ordinary variable rather than `this`… – Holger Mar 03 '15 at 09:53
  • @Holger That I really did not know. Thanks for the clarification! (For future reader's reference: `class Foo { final boolean foo = true; } class Bar { void bar(Foo foo) { boolean bar = foo.foo } }`) This is especially interesting as the current C2 compiler throws a cached, stack-less `NullPointerException`: https://bugs.openjdk.java.net/browse/JDK-8073432 This javac compilation decision opens the doors for some happy debugging sessions. – Rafael Winterhalter Mar 03 '15 at 10:01
  • @Holger: Interesting, I never knew that. Thanks for fixing things. – Antimony Mar 03 '15 at 15:57