| /* | |
| * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| /* | |
| * @test | |
| * @bug 8333748 8349536 | |
| * @summary javap should not fail if reserved access flag bits are set to 1 | |
| * @library /tools/lib | |
| * @modules jdk.jdeps/com.sun.tools.javap | |
| * @run junit UndefinedAccessFlagTest | |
| */ | |
| import org.junit.jupiter.params.ParameterizedTest; | |
| import org.junit.jupiter.params.provider.EnumSource; | |
| import toolbox.JavapTask; | |
| import toolbox.Task; | |
| import toolbox.ToolBox; | |
| import java.lang.classfile.AccessFlags; | |
| import java.lang.classfile.ClassModel; | |
| import java.lang.classfile.FieldModel; | |
| import java.lang.classfile.MethodModel; | |
| import java.lang.classfile.attribute.InnerClassInfo; | |
| import java.lang.classfile.attribute.InnerClassesAttribute; | |
| import java.nio.file.Files; | |
| import java.nio.file.Path; | |
| import static java.lang.classfile.ClassFile.*; | |
| import static org.junit.jupiter.api.Assertions.assertTrue; | |
| public class UndefinedAccessFlagTest { | |
| final ToolBox toolBox = new ToolBox(); | |
| enum TestLocation { | |
| NONE(false), CLASS, FIELD, METHOD, INNER_CLASS(false); | |
| final boolean fails; | |
| TestLocation() { this(true); } | |
| TestLocation(boolean fails) { this.fails = fails; } | |
| } | |
| @ParameterizedTest | |
| @EnumSource(TestLocation.class) | |
| void test(TestLocation location) throws Throwable { | |
| var cf = of(); | |
| ClassModel cm; | |
| try (var is = UndefinedAccessFlagTest.class.getResourceAsStream( | |
| "/UndefinedAccessFlagTest$SampleInnerClass.class" | |
| )) { | |
| cm = cf.parse(is.readAllBytes()); | |
| } | |
| var bytes = cf.transformClass(cm, (cb, ce) -> { | |
| switch (ce) { | |
| case AccessFlags flags when location == TestLocation.CLASS -> cb | |
| .withFlags(flags.flagsMask() | ACC_PRIVATE); | |
| case FieldModel f when location == TestLocation.FIELD -> cb | |
| .transformField(f, (fb, fe) -> { | |
| if (fe instanceof AccessFlags flags) { | |
| fb.withFlags(flags.flagsMask() | ACC_SYNCHRONIZED); | |
| } else { | |
| fb.with(fe); | |
| } | |
| }); | |
| case MethodModel m when location == TestLocation.METHOD -> cb | |
| .transformMethod(m, (mb, me) -> { | |
| if (me instanceof AccessFlags flags) { | |
| mb.withFlags(flags.flagsMask() | ACC_INTERFACE); | |
| } else { | |
| mb.with(me); | |
| } | |
| }); | |
| case InnerClassesAttribute attr when location == TestLocation.INNER_CLASS -> cb | |
| .with(InnerClassesAttribute.of(attr.classes().stream() | |
| .map(ic -> InnerClassInfo.of(ic.innerClass(), ic.outerClass(), ic.innerName(), ic.flagsMask() | ACC_SUPER)) | |
| .toList())); | |
| default -> cb.with(ce); | |
| } | |
| }); | |
| Files.write(Path.of("transformed.class"), bytes); | |
| var lines = new JavapTask(toolBox) | |
| .classes("transformed.class") | |
| .options("-c", "-p", "-v") | |
| .run(location.fails ? Task.Expect.FAIL : Task.Expect.SUCCESS) | |
| .writeAll() | |
| .getOutputLines(Task.OutputKind.DIRECT); | |
| // No termination when access flag error happens | |
| assertTrue(lines.stream().anyMatch(l -> l.contains("java.lang.String field;"))); | |
| assertTrue(lines.stream().anyMatch(l -> l.contains("UndefinedAccessFlagTest$SampleInnerClass();"))); | |
| assertTrue(lines.stream().anyMatch(l -> l.contains("void method();"))); | |
| assertTrue(lines.stream().anyMatch(l -> l.contains("SampleInnerClass=class UndefinedAccessFlagTest$SampleInnerClass of class UndefinedAccessFlagTest"))); | |
| // Remove non-error lines | |
| assertTrue(lines.removeIf(st -> !st.startsWith("Error:"))); | |
| // Desired locations has errors | |
| assertTrue(location == TestLocation.NONE || !lines.isEmpty()); | |
| // Access Flag errors only | |
| assertTrue(lines.stream().allMatch(l -> l.contains("Access Flags:")), () -> String.join("\n", lines)); | |
| } | |
| static class SampleInnerClass { | |
| String field; | |
| void method() {} | |
| } | |
| } |