Skip to content

Access to Enum in SwitchCase #1433

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
eranberg opened this issue Mar 14, 2025 · 2 comments
Open

Access to Enum in SwitchCase #1433

eranberg opened this issue Mar 14, 2025 · 2 comments

Comments

@eranberg
Copy link

I am trying to analyze my codebase and find usages of an Enum

public enum ExampleEnum {
    A,
    B,
    C
}
public class ExampleEnumMapper {

    public String mapMyEnum(final ExampleEnum exampleEnum) {
        if (exampleEnum == ExampleEnum.C)
            return "C";

        switch (exampleEnum) {
            case A:
                return "A";
            case B:
                return "B";
            default:
                return "unknown";
        }
    }
}

I am trying to collect all of the ExampleEnum usages in my classes:

public static Set<String> findEnumNamesAccess(JavaClass javaClass) {
    return javaClass.getAccessesFromSelf().stream()
            .filter(access -> access.getTargetOwner().isAssignableTo(ExampleEnum.class))
            .map(access -> access.getTarget().getName())
            .collect(Collectors.toSet());
}

however javaClass.getAccessesFromSelf() only gives me the access of ExampleEnum.C and ignores the switch-case access to the enum
Should I be retrieving the access information in a different way in order to expose A and B usage in the switch-case?

@hankem
Copy link
Member

hankem commented Mar 16, 2025

Enum constants in a switch statement are difficult to recognize in the byte code:

  public java.lang.String mapMyEnum(ExampleEnum);
    descriptor: (LExampleEnum;)Ljava/lang/String;
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_1
         1: getstatic     #7                  // Field ExampleEnum.C:LExampleEnum;
         4: if_acmpne     10
         7: ldc           #13                 // String C
         9: areturn
        10: getstatic     #14                 // Field ExampleEnumMapper$1.$SwitchMap$ExampleEnum:[I
        13: aload_1
        14: invokevirtual #20                 // Method ExampleEnum.ordinal:()I
        17: iaload
        18: lookupswitch  { // 2
                       1: 44
                       2: 47
                 default: 50
            }
        44: ldc           #24                 // String A
        46: areturn
        47: ldc           #26                 // String B
        49: areturn
        50: ldc           #28                 // String unknown
        52: areturn

using a synthetic field in a synthetic class:

class ExampleEnumMapper$1
  minor version: 0
  major version: 65
  flags: (0x1020) ACC_SUPER, ACC_SYNTHETIC
  this_class: #8                          // ExampleEnumMapper$1
  super_class: #26                        // java/lang/Object
  interfaces: 0, fields: 1, methods: 1, attributes: 4
{
  static final int[] $SwitchMap$ExampleEnum;
    descriptor: [I
    flags: (0x1018) ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC

  static {};
    descriptor: ()V
    flags: (0x0008) ACC_STATIC
    Code:
      stack=3, locals=1, args_size=0
         0: invokestatic  #1                  // Method ExampleEnum.values:()[LExampleEnum;
         3: arraylength
         4: newarray       int
         6: putstatic     #7                  // Field $SwitchMap$ExampleEnum:[I
         9: getstatic     #7                  // Field $SwitchMap$ExampleEnum:[I
        12: getstatic     #13                 // Field ExampleEnum.A:LExampleEnum;
        15: invokevirtual #17                 // Method ExampleEnum.ordinal:()I
        18: iconst_1
        19: iastore
        20: goto          24
        23: astore_0
        24: getstatic     #7                  // Field $SwitchMap$ExampleEnum:[I
        27: getstatic     #23                 // Field ExampleEnum.B:LExampleEnum;
        30: invokevirtual #17                 // Method ExampleEnum.ordinal:()I
        33: iconst_2
        34: iastore
        35: goto          39
        38: astore_0
        39: return

So what my compiler (I was using OpenJDK 21) generates is essentially equivalent to

   static final int[] SWITCH_MAP = new int[ExampleEnum.values().length];
   static {
     SWITCH_MAP[ExampleEnum.A.ordinal()] = 1;
     SWITCH_MAP[ExampleEnum.B.ordinal()] = 2;
   }
   
   public String mapMyEnum(ExampleEnum exampleEnum) {
        if (exampleEnum == ExampleEnum.C) {
            return "C";
        }

        switch (SWITCH_MAP[exampleEnum.ordinal()]) {
           case 1:
               return "A";
           case 2:
               return "B";
           default:
               return "unknown";
        }
    }

ArchUnit currently doesn't reconstruct dependencies from such a pattern in the byte code.
Do you think it should?

I think it would require a quite sophisticated reverse-engineering, as I noticed that the byte code for another switch like

        switch (exampleEnum) {
            case A:
                return "A";
            default:
                return "unknown";
        }

may also use the same synthetic ExampleEnumMapper$1.$SwitchMap$ExampleEnum field, but just differ in the lookupswitch table.

@eranberg
Copy link
Author

After reading your post I noticed there are several more similar issues regarding synthetic classes.
I'm fairly new to ArchUnit and I'm writing a tool to detect certain patterns in our (very large) codebase. I was hoping that ArchUnit would have that capability of analyzing synthetic classes but I guess I'll need to use a different technique for now.

@hankem thanks for pointing this out

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants