Skip to content
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

Make EnumTypeAdapter friendly with obfuscation #1495

Merged
merged 10 commits into from Aug 4, 2021
Merged

Conversation

ganadist
Copy link
Contributor

@ganadist ganadist commented Mar 22, 2019

When enum value was obfuscated by proguard, EnumTypeAdapter raise NoSuchFieldException, even if apply SerializedName annotation.
Because EnumTypeAdapter cannot find obfuscated enum constant field with its name.
But without this workaround, there is no way to obfuscate enum name.

@JakeWharton
Copy link
Contributor

@JakeWharton JakeWharton commented Mar 22, 2019

Test case?

@ganadist
Copy link
Contributor Author

@ganadist ganadist commented Mar 22, 2019

I tried to integrate proguard obfuscation on testcase like this.

ganadist@251ec47

And intent of this mvn rule are this order.

  • copy-resources (pre-obfuscate-class)
  • proguard (default)
  • copy-resources (post-obfuscate-class)

But order of plugin execution does not work as intended.

  • copy-resources (pre-obfuscate-class)
  • copy-resources (post-obfuscate-class)
  • proguard (default)

Can I get any idea?

@JakeWharton
Copy link
Contributor

@JakeWharton JakeWharton commented Mar 22, 2019

@ganadist
Copy link
Contributor Author

@ganadist ganadist commented Mar 23, 2019

Just specify a different name and ensure it's honored in the JSON output.

Gson has already com.google.gson.functional.testEnumCaseMapping() testcase.

@ganadist
Copy link
Contributor Author

@ganadist ganadist commented Mar 23, 2019

I updated first conversation comment of this PR. because it could be misleading.

@ganadist ganadist changed the title SerializedName annotation workaround for obfuscated enums by ProGuard Make EnumTypeAdapter friendly with obfuscation Mar 23, 2019
@inder123
Copy link
Collaborator

@inder123 inder123 commented Mar 24, 2019

I ran into this problem as well. This is a good fix. Let's add a testcase.

@ganadist
Copy link
Contributor Author

@ganadist ganadist commented Mar 25, 2019

To verify this change, enum class in testcase must be obfuscated by proguard.
And I updated testcase on ganadist@1b65e3a , added to check enum class is obfuscated.

But it still has a problem to integrate proguard. :(

@ganadist
Copy link
Contributor Author

@ganadist ganadist commented Apr 7, 2019

Proguard integration on Maven testcase is pretty dirty, but.

Running com.google.gson.functional.EnumWithObfuscatedTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.001 sec
$ cat gson/target/proguard_map.txt
com.google.gson.functional.EnumWithObfuscatedTest -> com.google.gson.functional.EnumWithObfuscatedTest:
    com.google.gson.Gson gson -> a
    void <init>() -> <init>
    void setUp() -> setUp
    void testEnumClassWithObfuscated() -> testEnumClassWithObfuscated
com.google.gson.functional.EnumWithObfuscatedTest$Gender -> com.google.gson.functional.EnumWithObfuscatedTest$Gender:
    com.google.gson.functional.EnumWithObfuscatedTest$Gender MALE -> a
    com.google.gson.functional.EnumWithObfuscatedTest$Gender FEMALE -> b
    com.google.gson.functional.EnumWithObfuscatedTest$Gender[] $VALUES -> c
    com.google.gson.functional.EnumWithObfuscatedTest$Gender[] values() -> values
    com.google.gson.functional.EnumWithObfuscatedTest$Gender valueOf(java.lang.String) -> valueOf
    void <init>(java.lang.String,int) -> <init>
    void <clinit>() -> <clinit>

throw new AssertionError(e);
} catch (NullPointerException e) {
throw new AssertionError(e);
} catch (ExceptionInInitializerError e) {
Copy link
Contributor

@Marcono1234 Marcono1234 Sep 9, 2020

Choose a reason for hiding this comment

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

Why catch this?

Copy link
Contributor Author

@ganadist ganadist Sep 10, 2020

Choose a reason for hiding this comment

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

Copy link
Contributor

@Marcono1234 Marcono1234 Sep 11, 2020

Choose a reason for hiding this comment

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

Yes, but only because the method can throw the error does not mean that it should be caught here. I assume if that error actually occurred it could also happen in the non-Proguard specific code and there it is not caught either. So in my opinion it should not be caught here.

(Though note that I am not a maintainer of this project)

Copy link
Contributor Author

@ganadist ganadist Sep 12, 2020

Choose a reason for hiding this comment

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

Fixed at eba1415

} catch (NoSuchFieldException e) {
} catch (IllegalAccessException e) {
throw new AssertionError(e);
} catch (NullPointerException e) {
Copy link
Contributor

@Marcono1234 Marcono1234 Sep 9, 2020

Choose a reason for hiding this comment

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

Why catch this?

Copy link
Contributor Author

@ganadist ganadist Sep 10, 2020

Choose a reason for hiding this comment

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

Copy link
Contributor

@Marcono1234 Marcono1234 Sep 11, 2020

Choose a reason for hiding this comment

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

NullPointerException - if the specified object is null and the field is an instance field.

But the enum constants are static fields so this should not be possible.

Copy link
Contributor Author

@ganadist ganadist Sep 12, 2020

Choose a reason for hiding this comment

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

Fixed at eba1415

if (!field.isEnumConstant()) {
continue;
}
field.setAccessible(true);
Copy link
Contributor

@Marcono1234 Marcono1234 Sep 9, 2020

Choose a reason for hiding this comment

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

Why make the field accessible? Enum element fields are public so making them accessible should not be necessary.

Copy link
Contributor Author

@ganadist ganadist Sep 10, 2020

Choose a reason for hiding this comment

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

Without this line, unittests failed with following error.

Failed tests:   testCollectionOfEnumsDeserialization(com.google.gson.functional.EnumTest): AssertionError (GSON 2.8.6-SNAPSHOT): java.lang.IllegalAccessException: class com.google.gson.internal.bind.TypeAdapters$EnumTypeAdapter cannot access a member of class com.google.gson.functional.EnumTest$MyEnum with modifiers "public static final"
  testTopLevelEnumDeserialization(com.google.gson.functional.EnumTest): AssertionError (GSON 2.8.6-SNAPSHOT): java.lang.IllegalAccessException: class com.google.gson.internal.bind.TypeAdapters$EnumTypeAdapter cannot access a member of class com.google.gson.functional.EnumTest$MyEnum with modifiers "public static final"
  testClassWithEnumFieldSerialization(com.google.gson.functional.EnumTest): java.lang.IllegalAccessException: class com.google.gson.internal.bind.TypeAdapters$EnumTypeAdapter cannot access a member of class com.google.gson.functional.EnumTest$MyEnum with modifiers "public static final"
  testClassWithEnumFieldDeserialization(com.google.gson.functional.EnumTest): AssertionError (GSON 2.8.6-SNAPSHOT): java.lang.IllegalAccessException: class com.google.gson.internal.bind.TypeAdapters$EnumTypeAdapter cannot access a member of class com.google.gson.functional.EnumTest$MyEnum with modifiers "public static final"
  testCollectionOfEnumsSerialization(com.google.gson.functional.EnumTest): AssertionError (GSON 2.8.6-SNAPSHOT): java.lang.IllegalAccessException: class com.google.gson.internal.bind.TypeAdapters$EnumTypeAdapter cannot access a member of class com.google.gson.functional.EnumTest$MyEnum with modifiers "public static final"
  testTopLevelEnumSerialization(com.google.gson.functional.EnumTest): java.lang.IllegalAccessException: class com.google.gson.internal.bind.TypeAdapters$EnumTypeAdapter cannot access a member of class com.google.gson.functional.EnumTest$MyEnum with modifiers "public static final"

Copy link
Member

@eamonnmcmanus eamonnmcmanus Aug 4, 2021

Choose a reason for hiding this comment

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

Enum constants are public, but the enum class itself isn't necessarily. If it isn't, then you'll get IllegalAccessException without the field.setAccessible(true).

My concern here is that if there is a SecurityManager, the field.setAccessible(true) call might fail, whereas the code using classOfT.getEnumConstants() would have worked. So we'll have fixed one problem (getting enum constants in ProGuarded code) but introduced another (getting enum constants when there is a SecurityManager).

I think the code here should read something like this:

for (final Field field : classOfT.getDeclaredFields()) {
  if (!field.isEnumConstant()) {
    continue;
  }
  AccessController.doPrivileged(
      new PrivilegedAction<Void>() {
        @Override public Void run() {
          field.setAccessible(true);
          return null;
        }
      });
  @SuppressWarnings("unchecked")
  T constant = (T) field.get(null);

Copy link
Contributor Author

@ganadist ganadist Aug 4, 2021

Choose a reason for hiding this comment

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

Fixed at 59a8aed

@Marcono1234
Copy link
Contributor

@Marcono1234 Marcono1234 commented Sep 9, 2020

This pull request would fix #924 and #1776

continue;
}
field.setAccessible(true);
T constant = (T)(field.get(null));
Copy link
Contributor

@Marcono1234 Marcono1234 Sep 11, 2020

Choose a reason for hiding this comment

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

Please add a @SuppressWarnings("unchecked") for this variable.

Copy link
Contributor Author

@ganadist ganadist Sep 12, 2020

Choose a reason for hiding this comment

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

Fixed at ddee26e


package com.google.gson.functional;

import java.lang.reflect.Field;
Copy link
Contributor

@Marcono1234 Marcono1234 Sep 11, 2020

Choose a reason for hiding this comment

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

This import is unused

Copy link
Contributor Author

@ganadist ganadist Sep 12, 2020

Choose a reason for hiding this comment

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

Fixed at 7dbfce9

@@ -0,0 +1,20 @@
# Options from Android Gradle Plugins
Copy link
Contributor

@Marcono1234 Marcono1234 Sep 11, 2020

Choose a reason for hiding this comment

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

Would it be possible to move this to gson/src/test/resources? I think that might be better than having it directly in the gson folder.

Copy link
Contributor Author

@ganadist ganadist Sep 12, 2020

Choose a reason for hiding this comment

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

Fixed at a25f96b


public void testEnumClassWithObfuscated() {
for (Gender enumConstant: Gender.class.getEnumConstants()) {
try {
Copy link
Member

@eamonnmcmanus eamonnmcmanus Aug 4, 2021

Choose a reason for hiding this comment

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

Would you mind fixing the indentation on lines 48–50 so it uses the same two-space indentation as the rest of the code?

Copy link
Contributor Author

@ganadist ganadist Aug 4, 2021

Choose a reason for hiding this comment

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

Fixed at d8c5fcf

Copy link
Member

@eamonnmcmanus eamonnmcmanus left a comment

Thanks for putting this change together, especially the tests! Sorry it has taken so long for us to get to it.

There are just a couple of changes that I think would make sense.

I'm going to test this change against Google's many internal uses of Gson and if it comes through clean we should be good to go.

Copy link
Member

@eamonnmcmanus eamonnmcmanus left a comment

Thanks for the quick update!

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

Successfully merging this pull request may close these issues.

None yet

6 participants