Skip to content

8362885: A more formal way to mark javac's Flags that belong to a specific Symbol type only #26452

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

lahodaj
Copy link
Contributor

@lahodaj lahodaj commented Jul 24, 2025

This PR proposes to improve handling of javac's Flags in two ways:

  • for each flag, there's now an informational annotation specifying what is the target Symbol type. Only targets right now are TypeSymbols, MethodSymbols and VarSymbols. If we ran out of flags for TypeSymbols, we could split those to module/package/class/type variable, but it does not seem to be quite necessary yet. There's an auxiliary special BLOCK, which is for JCBlock.
  • the manually handled Flags.Flag enum is replaced with autogenerated FlagsEnum

This is inspired by:
#26181 (review)

There may be some better to handle Flags eventually, but this hopefully improves the current situation at least somewhat, by providing more formal way to say the flags' target, and restricting the need to read comments and search for free flags.

As a side-effect of this annotation, the test/langtools/tools/javac/flags/FlagsTest.java now also prints which flags are free, for each Symbol type.

(I will remove the build label for now, until discussion on javac level is done, and will re-add it if we decide the goal to autogenerate the FlagsEnum makes sense.)

/label remove build


Progress

  • Change must be properly reviewed (1 review required, with at least 1 Reviewer)
  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue

Issue

  • JDK-8362885: A more formal way to mark javac's Flags that belong to a specific Symbol type only (Task - P3)

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/26452/head:pull/26452
$ git checkout pull/26452

Update a local copy of the PR:
$ git checkout pull/26452
$ git pull https://git.openjdk.org/jdk.git pull/26452/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 26452

View PR using the GUI difftool:
$ git pr show -t 26452

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/26452.diff

Using Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Jul 24, 2025

👋 Welcome back jlahoda! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Jul 24, 2025

❗ This change is not yet ready to be integrated.
See the Progress checklist in the description for automated requirements.

@openjdk openjdk bot added the rfr Pull request is ready for review label Jul 24, 2025
@openjdk
Copy link

openjdk bot commented Jul 24, 2025

@lahodaj The build label was not set.

@openjdk
Copy link

openjdk bot commented Jul 24, 2025

@lahodaj The following labels will be automatically applied to this pull request:

  • build
  • compiler

When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing lists. If you would like to change these labels, use the /label pull request command.

@openjdk openjdk bot added build build-dev@openjdk.org compiler compiler-dev@openjdk.org labels Jul 24, 2025
@lahodaj
Copy link
Contributor Author

lahodaj commented Jul 24, 2025

/label remove build

@openjdk openjdk bot removed the build build-dev@openjdk.org label Jul 24, 2025
@openjdk
Copy link

openjdk bot commented Jul 24, 2025

@lahodaj
The build label was successfully removed.

@mlbridge
Copy link

mlbridge bot commented Jul 24, 2025

Webrevs

@archiecobbs
Copy link
Contributor

This looks like a really nice improvement for maintainability.

? Would it make more sense (and/or be simpler?) to move the conflict-checking logic into FlagsGenerator?

It somehow seems odd to have a tool which can knowingly generate an invalid FlagsEnum.java file... and then defer the check for that until later, maybe, in a unit test... when we could just make the build fail instead.

? Is it worth adding some simple runtime check for correct FlagTarget usage?

For example (just brainstorming):

  • For each FlagTarget have FlagsGenerator output e.g. public static final long METHOD_FLAGS = 0x0301930420520017L;
  • At appropriate location(s) in the code, add Assert.check(this.flags_field & ~METHOD_FLAGS) == 0);

(Of course even better would be to check this at compile time using stronger typing (e.g., by replacing long flags_field with some EnumSet, etc.) but that's probably way beyond this first step.)


private static void printFreeFlags(String comment, long freeFlags) {
System.err.print("free flags for " + comment + ": ");
for (int bit = 16; bit < 64; bit++) { //lowest 16 bits are used in classfiles, never suggest adding anything there
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace 16 with Character.SIZE and 64 with Long.SIZE?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think Character.SIZE is really appropriate, but I've declared U2_SIZE for 16, and used Long.SIZE for 64.


try (PrintWriter out = new PrintWriter(Files.newBufferedWriter(Paths.get(args[1])))) {
out.println("""
package com.sun.tools.javac.code;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need a license header for generated sources?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CompilerProperties and other files that are generated using similar means don't have license headers, so I assume we don't need that.

}

private static void verifyFlagsNonOverlapping() throws Throwable {
Map<FlagTarget, Map<Long, List<Field>>> target2Flag2Fields = computeTarget2Flag2Fields();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could calculate this once in main and pass to verifyFlagNonOverlapping and findFreeFlags

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given this is a test, I would prefer to keep the test cases separate. This is a bit moot now, as the test is no doing the verification anymore.

- when conflict is detected, the generator fails
- adding runtime checks
- using constants for number of bits
public static final int StandardFlags = 0x0fff;

// Because the following access flags are overloaded with other
// bit positions, we translate them when reading and writing class
// files into unique bits positions: ACC_SYNTHETIC <-> SYNTHETIC,
// for example.
@NotFlag
public static final int ACC_SUPER = 0x0020;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why didn't we add annotation on these ACC ones? After all, they also overlap with previous flags -- e.g. ACC_SUPER overlaps with SYNCHRONIZED, and it works because the former goes on types and the latter on methods. So, isn't this the same trick we're pulling?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -167,8 +168,11 @@ private static Object valueOfValueAttribute(AnnotationMirror am) {

public enum FlagTarget {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just flyby comment: symbol kind is an enum, so in principle we could use constants such as MTH, VAR, TYP as targets, if we wanted to connect the decls more with the actual code. But I like your approach as well (as symbol kind has also a lot of other more dubious stuff/noise)

@lahodaj
Copy link
Contributor Author

lahodaj commented Jul 25, 2025

This looks like a really nice improvement for maintainability.

? Would it make more sense (and/or be simpler?) to move the conflict-checking logic into FlagsGenerator?

It somehow seems odd to have a tool which can knowingly generate an invalid FlagsEnum.java file... and then defer the check for that until later, maybe, in a unit test... when we could just make the build fail instead.

Done.

? Is it worth adding some simple runtime check for correct FlagTarget usage?

For example (just brainstorming):

* For each `FlagTarget` have `FlagsGenerator` output e.g. `public static final long METHOD_FLAGS = 0x0301930420520017L;`

* At appropriate location(s) in the code, add `Assert.check(this.flags_field & ~METHOD_FLAGS) == 0);`

I've added the checks. It may be useful, but it is somewhat limited, as it cannot detect the most dangerous case where the flag with overloaded meaning is set incorrectly.

(Of course even better would be to check this at compile time using stronger typing (e.g., by replacing long flags_field with some EnumSet, etc.) but that's probably way beyond this first step.)

There were several experiments, in the past and currently ongoing, but it is difficult to do something that is safer and does not cost too much on performance/memory consumption. Value classes may or may not help.

@@ -2127,6 +2127,9 @@ private boolean needPackageInfoClass(JCPackageDecl pd) {
}

public void visitModuleDef(JCModuleDecl tree) {
FlagsEnum.assertNoUnexpectedFlags(tree.sym.flags_field,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not super sure about rolling this into Lower... but a separate visitor will also look a bit "too much"...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For instance, for type variables there's no way to check them at this point?

@mcimadamore
Copy link
Contributor

There's a lot to like in this patch. Now flags cannot erroneously overlap, which will give us all javac developers more confidence that we're not stepping into each other toes. Also, by making the mechanism more formally correct, it's easier to see which "holes" we have available (and there's probably quite a lot left).

The only thing I'm unsure of, as pointed out in the review, is the dynamic checks. I believe that, as far as this PR is concerned, this feels like "scope creep". I think it would be cleaner if this PR only concerned about introducing the machinery. Then maybe we can brainstorm separately on what would be the best way to add some of these checks (but I'm skeptical that we can find anything that doesn't feel like a compromise).

For instance, another way to do this could be to do another combo test like the tree test, where we compile all the existing tests, and check the validity of all the flags on all the symbols. That's an offline way to make sure that what we do in javac is "sort of" correct, but one that doesn't leak into the code.

The real fix for this, as we know (and as @archiecobbs commented) is to move away from longs, but that's not what this PR is about.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler compiler-dev@openjdk.org rfr Pull request is ready for review
Development

Successfully merging this pull request may close these issues.

5 participants