| // Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| package com.android.tools.r8.accessrelaxation; |
| |
| import static java.lang.System.exit; |
| import static org.hamcrest.CoreMatchers.containsString; |
| import static org.junit.Assume.assumeTrue; |
| |
| import com.android.tools.r8.NeverInline; |
| import com.android.tools.r8.NeverMerge; |
| import com.android.tools.r8.R8TestCompileResult; |
| import com.android.tools.r8.TestParameters; |
| import com.android.tools.r8.naming.MemberNaming.MethodSignature; |
| import com.android.tools.r8.utils.BooleanUtils; |
| import com.android.tools.r8.utils.FileUtils; |
| import com.android.tools.r8.utils.StringUtils; |
| import com.android.tools.r8.utils.codeinspector.CodeInspector; |
| import com.google.common.collect.ImmutableList; |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.ObjectInputStream; |
| import java.io.ObjectOutputStream; |
| import java.io.Serializable; |
| import java.nio.file.Path; |
| import java.util.List; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| |
| @NeverMerge |
| class MySerializable implements Serializable { |
| transient int value; |
| |
| MySerializable(int value) { |
| this.value = value; |
| } |
| |
| @NeverInline |
| private void writeObject(ObjectOutputStream out) throws IOException { |
| System.out.println("Serializable::write"); |
| out.writeInt(value); |
| } |
| |
| @NeverInline |
| private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { |
| System.out.println("Serializable::read"); |
| value = in.readInt(); |
| } |
| } |
| |
| class NoRelaxationForSerializableTestRunner { |
| |
| public static void main(String[] args) { |
| MySerializable instance = new MySerializable(8); |
| byte[] bytes = {}; |
| try(ByteArrayOutputStream bas = new ByteArrayOutputStream(); |
| ObjectOutputStream oos = new ObjectOutputStream(bas)) { |
| oos.writeObject(instance); |
| oos.flush(); |
| bytes = bas.toByteArray(); |
| } catch(IOException e) { |
| e.printStackTrace(System.err); |
| exit(1); |
| } |
| try(ByteArrayInputStream bis = new ByteArrayInputStream(bytes); |
| ObjectInputStream ois = new ObjectInputStream(bis)) { |
| MySerializable obj = (MySerializable) ois.readObject(); |
| if (obj.value != 8) { |
| throw new AssertionError("Could not deserialize"); |
| } |
| System.out.println(obj.value); |
| } catch(IOException | ClassNotFoundException e) { |
| e.printStackTrace(System.err); |
| exit(1); |
| } |
| } |
| } |
| |
| @RunWith(Parameterized.class) |
| public class NoRelaxationForSerializableTest extends AccessRelaxationTestBase { |
| private static final Class<?> MAIN = NoRelaxationForSerializableTestRunner.class; |
| private static final List<Class<?>> CLASSES = ImmutableList.of(MySerializable.class, MAIN); |
| private static final String KEEPMEMBER_RULES = StringUtils.lines( |
| "-keepclassmembers class * implements java.io.Serializable {", |
| " private void writeObject(java.io.ObjectOutputStream);", |
| " private void readObject(java.io.ObjectInputStream);", |
| "}" |
| ); |
| private static final String EXPECTED_OUTPUT = StringUtils.lines( |
| "Serializable::write", |
| "Serializable::read", |
| "8" |
| ); |
| |
| private final boolean accessModification; |
| private Path configuration; |
| |
| @Parameterized.Parameters(name = "{0}, access-modification: {1}") |
| public static List<Object[]> data() { |
| return buildParameters( |
| getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values()); |
| } |
| |
| public NoRelaxationForSerializableTest(TestParameters parameters, boolean accessModification) { |
| super(parameters); |
| this.accessModification = accessModification; |
| } |
| |
| @Before |
| public void setUpConfiguration() throws Exception { |
| configuration = temp.newFile("pg.conf").toPath().toAbsolutePath(); |
| FileUtils.writeTextFile(configuration, StringUtils.lines( |
| keepMainProguardConfiguration(MAIN), |
| accessModification ? "-allowaccessmodification" : "" |
| )); |
| } |
| |
| @Test |
| public void testProguard_withKeepRules() throws Exception { |
| assumeTrue(parameters.isCfRuntime()); |
| testForProguard() |
| .addProgramClasses(CLASSES) |
| .addTestingAnnotationsAsProgramClasses() |
| .addKeepRuleFiles(configuration) |
| .addKeepRules(KEEPMEMBER_RULES) |
| .compile() |
| .run(parameters.getRuntime(), MAIN) |
| .assertSuccessWithOutput(EXPECTED_OUTPUT) |
| .inspect(this::inspect); |
| } |
| |
| @Test |
| public void testR8_withKeepRules() throws Exception { |
| R8TestCompileResult result = |
| testForR8(parameters.getBackend()) |
| .addProgramClasses(CLASSES) |
| .enableInliningAnnotations() |
| .addKeepRuleFiles(configuration) |
| .addKeepRules(KEEPMEMBER_RULES) |
| .setMinApi(parameters.getApiLevel()) |
| .compile() |
| .inspect(this::inspect); |
| // TODO(b/117302947): Need to update ART binary. |
| if (parameters.isCfRuntime()) { |
| result |
| .run(parameters.getRuntime(), MAIN) |
| .assertSuccessWithOutput(EXPECTED_OUTPUT); |
| } |
| } |
| |
| @Test |
| public void testProguard_withoutKeepRules() throws Exception { |
| assumeTrue(parameters.isCfRuntime()); |
| testForProguard() |
| .addProgramClasses(CLASSES) |
| .addTestingAnnotationsAsProgramClasses() |
| .addKeepRuleFiles(configuration) |
| .compile() |
| .run(parameters.getRuntime(), MAIN) |
| .assertFailureWithErrorThatMatches(containsString("Could not deserialize")); |
| } |
| |
| @Test |
| public void testR8_withoutKeepRules() throws Exception { |
| R8TestCompileResult result = |
| testForR8(parameters.getBackend()) |
| .addProgramClasses(CLASSES) |
| .enableInliningAnnotations() |
| .addKeepRuleFiles(configuration) |
| .setMinApi(parameters.getApiLevel()) |
| .compile(); |
| // TODO(b/117302947): Need to update ART binary. |
| if (parameters.isCfRuntime()) { |
| result |
| .run(parameters.getRuntime(), MAIN) |
| .assertFailureWithErrorThatMatches(containsString("Could not deserialize")); |
| } |
| } |
| |
| private void inspect(CodeInspector inspector) { |
| assertNotPublic(inspector, MySerializable.class, |
| new MethodSignature("writeObject", "void", ImmutableList.of("java.io.ObjectOutputStream"))); |
| assertNotPublic(inspector, MySerializable.class, |
| new MethodSignature("readObject", "void", ImmutableList.of("java.io.ObjectInputStream"))); |
| } |
| } |