| /* | |
| * Copyright (c) 2016, 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. | |
| */ | |
| import util.ClassSupplier; | |
| import util.MemberFactory; | |
| import java.lang.reflect.AccessibleObject; | |
| import java.lang.reflect.Constructor; | |
| import java.lang.reflect.Field; | |
| import java.lang.reflect.InvocationTargetException; | |
| import java.lang.reflect.Method; | |
| import java.lang.reflect.Modifier; | |
| import java.util.EnumSet; | |
| import java.util.Iterator; | |
| import java.util.Map; | |
| import java.util.Optional; | |
| import java.util.stream.Stream; | |
| import static java.util.stream.Collectors.groupingBy; | |
| import static java.util.stream.Collectors.joining; | |
| import static java.util.stream.Collectors.mapping; | |
| import static java.util.stream.Collectors.toCollection; | |
| import static util.MemberFactory.*; | |
| import static util.MemberFactory.Group.*; | |
| import static util.ClassSupplier.*; | |
| /** | |
| * @test | |
| * @summary An exhaustive test of reflective access controls | |
| * @bug 6378384 | |
| * @build a.PublicSuper a.Package b.PublicSub b.Package | |
| * util.MemberFactory util.ClassSupplier | |
| * @run main AccessControlTest | |
| */ | |
| public class AccessControlTest { | |
| public static void main(String[] args) throws Exception { | |
| boolean ok = true; | |
| ok &= new Test() | |
| .current(PACKAGE_CLASS_IN_PKG_A) | |
| .member (PACKAGE_CLASS_IN_PKG_A).target(PACKAGE_CLASS_IN_PKG_A) | |
| .allowed(ALL) | |
| .perform(); | |
| ok &= new Test() | |
| .current(PACKAGE_CLASS_IN_PKG_A) | |
| .member (PUBLIC_SUPERCLASS_IN_PKG_A).target(PUBLIC_SUPERCLASS_IN_PKG_A) | |
| .allowed(PACKAGE_MEMBERS, PROTECTED_MEMBERS, PUBLIC_MEMBERS) | |
| .denied (PRIVATE_MEMBERS) | |
| .perform(); | |
| ok &= new Test() | |
| .current(PACKAGE_CLASS_IN_PKG_A) | |
| .member (PUBLIC_SUPERCLASS_IN_PKG_A).target(PUBLIC_SUBCLASS_IN_PKG_B) | |
| .allowed(PACKAGE_MEMBERS, PROTECTED_MEMBERS, PUBLIC_MEMBERS) | |
| .denied (PRIVATE_MEMBERS) | |
| .perform(); | |
| ok &= new Test() | |
| .current(PACKAGE_CLASS_IN_PKG_A) | |
| .member (PACKAGE_CLASS_IN_PKG_B).target(PACKAGE_CLASS_IN_PKG_B) | |
| .denied (ALL) | |
| .perform(); | |
| ok &= new Test() | |
| .current(PACKAGE_CLASS_IN_PKG_A) | |
| .member (PUBLIC_SUBCLASS_IN_PKG_B).target(PUBLIC_SUBCLASS_IN_PKG_B) | |
| .allowed(PUBLIC_MEMBERS) | |
| .denied (PRIVATE_MEMBERS, PACKAGE_MEMBERS, PROTECTED_MEMBERS) | |
| .perform(); | |
| ok &= new Test() | |
| .current(PUBLIC_SUPERCLASS_IN_PKG_A) | |
| .member (PACKAGE_CLASS_IN_PKG_A).target(PACKAGE_CLASS_IN_PKG_A) | |
| .allowed(PACKAGE_MEMBERS, PROTECTED_MEMBERS, PUBLIC_MEMBERS) | |
| .denied (PRIVATE_MEMBERS) | |
| .perform(); | |
| ok &= new Test() | |
| .current(PUBLIC_SUPERCLASS_IN_PKG_A) | |
| .member (PUBLIC_SUPERCLASS_IN_PKG_A).target(PUBLIC_SUPERCLASS_IN_PKG_A) | |
| .allowed(ALL) | |
| .perform(); | |
| ok &= new Test() | |
| .current(PUBLIC_SUPERCLASS_IN_PKG_A) | |
| .member (PUBLIC_SUPERCLASS_IN_PKG_A).target(PUBLIC_SUBCLASS_IN_PKG_B) | |
| .allowed(ALL) | |
| .perform(); | |
| ok &= new Test() | |
| .current(PUBLIC_SUPERCLASS_IN_PKG_A) | |
| .member (PACKAGE_CLASS_IN_PKG_B).target(PACKAGE_CLASS_IN_PKG_B) | |
| .denied (ALL) | |
| .perform(); | |
| ok &= new Test() | |
| .current(PUBLIC_SUPERCLASS_IN_PKG_A) | |
| .member (PUBLIC_SUBCLASS_IN_PKG_B).target(PUBLIC_SUBCLASS_IN_PKG_B) | |
| .allowed(PUBLIC_MEMBERS) | |
| .denied (PRIVATE_MEMBERS, PACKAGE_MEMBERS, PROTECTED_MEMBERS) | |
| .perform(); | |
| ok &= new Test() | |
| .current(PACKAGE_CLASS_IN_PKG_B) | |
| .member (PACKAGE_CLASS_IN_PKG_A).target(PACKAGE_CLASS_IN_PKG_A) | |
| .denied (ALL) | |
| .perform(); | |
| ok &= new Test() | |
| .current(PACKAGE_CLASS_IN_PKG_B) | |
| .member (PUBLIC_SUPERCLASS_IN_PKG_A).target(PUBLIC_SUPERCLASS_IN_PKG_A) | |
| .allowed(PUBLIC_MEMBERS) | |
| .denied (PRIVATE_MEMBERS, PACKAGE_MEMBERS, PROTECTED_MEMBERS) | |
| .perform(); | |
| ok &= new Test() | |
| .current(PACKAGE_CLASS_IN_PKG_B) | |
| .member (PUBLIC_SUPERCLASS_IN_PKG_A).target(PUBLIC_SUBCLASS_IN_PKG_B) | |
| .allowed(PUBLIC_MEMBERS) | |
| .denied (PRIVATE_MEMBERS, PACKAGE_MEMBERS, PROTECTED_MEMBERS) | |
| .perform(); | |
| ok &= new Test() | |
| .current(PACKAGE_CLASS_IN_PKG_B) | |
| .member (PACKAGE_CLASS_IN_PKG_B).target(PACKAGE_CLASS_IN_PKG_B) | |
| .allowed(ALL) | |
| .perform(); | |
| ok &= new Test() | |
| .current(PACKAGE_CLASS_IN_PKG_B) | |
| .member (PUBLIC_SUBCLASS_IN_PKG_B).target(PUBLIC_SUBCLASS_IN_PKG_B) | |
| .allowed(PACKAGE_MEMBERS, PROTECTED_MEMBERS, PUBLIC_MEMBERS) | |
| .denied (PRIVATE_MEMBERS) | |
| .perform(); | |
| ok &= new Test() | |
| .current(PUBLIC_SUBCLASS_IN_PKG_B) | |
| .member (PACKAGE_CLASS_IN_PKG_A).target(PACKAGE_CLASS_IN_PKG_A) | |
| .denied (ALL) | |
| .perform(); | |
| ok &= new Test() | |
| .current(PUBLIC_SUBCLASS_IN_PKG_B) | |
| .member (PUBLIC_SUPERCLASS_IN_PKG_A).target(PUBLIC_SUPERCLASS_IN_PKG_A) | |
| .allowed(PUBLIC_MEMBERS, PROTECTED_STATIC_F_M) | |
| .denied (PRIVATE_MEMBERS, PACKAGE_MEMBERS, PROTECTED_INSTANCE_F_M, | |
| PROTECTED_C) | |
| .perform(); | |
| ok &= new Test() | |
| .current(PUBLIC_SUBCLASS_IN_PKG_B) | |
| .member (PUBLIC_SUPERCLASS_IN_PKG_A).target(PUBLIC_SUBCLASS_IN_PKG_B) | |
| .allowed(PUBLIC_MEMBERS, PROTECTED_INSTANCE_F_M, PROTECTED_STATIC_F_M) | |
| .denied (PRIVATE_MEMBERS, PACKAGE_MEMBERS, PROTECTED_C) | |
| .perform(); | |
| ok &= new Test() | |
| .current(PUBLIC_SUBCLASS_IN_PKG_B) | |
| .member (PACKAGE_CLASS_IN_PKG_B).target(PACKAGE_CLASS_IN_PKG_B) | |
| .allowed(PACKAGE_MEMBERS, PROTECTED_MEMBERS, PUBLIC_MEMBERS) | |
| .denied (PRIVATE_MEMBERS) | |
| .perform(); | |
| ok &= new Test() | |
| .current(PUBLIC_SUBCLASS_IN_PKG_B) | |
| .member (PUBLIC_SUBCLASS_IN_PKG_B).target(PUBLIC_SUBCLASS_IN_PKG_B) | |
| .allowed(ALL) | |
| .perform(); | |
| if (ok) { | |
| System.out.println("\nAll cases passed."); | |
| } else { | |
| throw new RuntimeException("Some cases failed - see log."); | |
| } | |
| } | |
| // use this for generating an exhaustive set of test cases on stdout | |
| public static class Generate { | |
| public static void main(String[] args) { | |
| for (ClassSupplier current : ClassSupplier.values()) { | |
| for (ClassSupplier member : ClassSupplier.values()) { | |
| for (ClassSupplier target : ClassSupplier.values()) { | |
| if (member.get().isAssignableFrom(target.get())) { | |
| new Test() | |
| .current(current).member(member).target(target) | |
| .allowed(ALL) | |
| .perform(true); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| static class Test { | |
| ClassSupplier currentClassSupplier, memberClassSupplier, targetClassSupplier; | |
| EnumSet<MemberFactory> expectAllowedMembers = EnumSet.noneOf(MemberFactory.class); | |
| EnumSet<MemberFactory> expectDeniedMembers = EnumSet.noneOf(MemberFactory.class); | |
| Test current(ClassSupplier current) { | |
| currentClassSupplier = current; | |
| return this; | |
| } | |
| Test member(ClassSupplier member) { | |
| memberClassSupplier = member; | |
| return this; | |
| } | |
| Test target(ClassSupplier target) { | |
| targetClassSupplier = target; | |
| return this; | |
| } | |
| Test allowed(MemberFactory... allowed) { | |
| expectAllowedMembers = MemberFactory.asSet(allowed); | |
| return this; | |
| } | |
| Test allowed(MemberFactory.Group... allowedGroups) { | |
| expectAllowedMembers = MemberFactory.groupsToMembers( | |
| MemberFactory.Group.asSet(allowedGroups)); | |
| return this; | |
| } | |
| Test denied(MemberFactory... denied) { | |
| expectDeniedMembers = MemberFactory.asSet(denied); | |
| return this; | |
| } | |
| Test denied(MemberFactory.Group... deniedGroups) { | |
| expectDeniedMembers = MemberFactory.groupsToMembers( | |
| MemberFactory.Group.asSet(deniedGroups)); | |
| return this; | |
| } | |
| boolean perform() { | |
| return perform(false); | |
| } | |
| boolean perform(boolean generateCases) { | |
| // some validation 1st | |
| EnumSet<MemberFactory> intersection = EnumSet.copyOf(expectAllowedMembers); | |
| intersection.retainAll(expectDeniedMembers); | |
| if (!intersection.isEmpty()) { | |
| throw new IllegalArgumentException( | |
| "Expected allowed and denied MemberFactories have non-empty intersection: " + | |
| intersection); | |
| } | |
| EnumSet<MemberFactory> missing = EnumSet.allOf(MemberFactory.class); | |
| missing.removeAll(expectAllowedMembers); | |
| missing.removeAll(expectDeniedMembers); | |
| if (!missing.isEmpty()) { | |
| throw new IllegalArgumentException( | |
| "Union of expected allowed and denied MemberFactories is missing elements: " + | |
| missing); | |
| } | |
| // retrieve method that will perform reflective access | |
| Method checkAccessMethod; | |
| try { | |
| checkAccessMethod = currentClassSupplier.get().getDeclaredMethod( | |
| "checkAccess", AccessibleObject.class, Object.class); | |
| // in case of inaccessible currentClass | |
| checkAccessMethod.setAccessible(true); | |
| } catch (NoSuchMethodException e) { | |
| throw new RuntimeException(e); | |
| } | |
| // construct a target object (for instance field/method) | |
| Object target; | |
| Constructor<?> targetConstructor = | |
| (Constructor<?>) PUBLIC_CONSTRUCTOR.apply(targetClassSupplier.get()); | |
| // in case of inaccessible targetClass | |
| targetConstructor.setAccessible(true); | |
| try { | |
| target = targetConstructor.newInstance( | |
| new Object[targetConstructor.getParameterCount()]); | |
| } catch (ReflectiveOperationException e) { | |
| throw new RuntimeException(e); | |
| } | |
| Class<?> memberClass = memberClassSupplier.get(); | |
| Map<Boolean, EnumSet<MemberFactory>> actualMembers = Stream.concat( | |
| expectAllowedMembers.stream().map(member -> new Trial(member, true)), | |
| expectDeniedMembers.stream().map(member -> new Trial(member, false)) | |
| ).map(trial -> { | |
| // obtain AccessibleObject to be used to perform reflective access | |
| AccessibleObject accessibleObject = trial.member.apply(memberClass); | |
| // only need target 'obj' for instance fields and methods | |
| Object obj = | |
| (accessibleObject instanceof Field && | |
| !Modifier.isStatic(((Field) accessibleObject).getModifiers()) | |
| || | |
| accessibleObject instanceof Method && | |
| !Modifier.isStatic(((Method) accessibleObject).getModifiers()) | |
| ) | |
| ? target : null; | |
| // invoke checkAccess method and let it perform the reflective access | |
| try { | |
| checkAccessMethod.invoke(null, accessibleObject, obj); | |
| trial.actualAllowed = true; | |
| } catch (IllegalAccessException e) { | |
| // should not happen as checkAccessMethod.isAccessible() | |
| throw new RuntimeException(e); | |
| } catch (InvocationTargetException e) { | |
| if (e.getTargetException() instanceof IllegalAccessException) { | |
| trial.actualAllowed = false; | |
| } else { | |
| // any other Exception is a fault in test or infrastructure - fail fast | |
| throw new RuntimeException(e.getTargetException()); | |
| } | |
| } | |
| if (!generateCases) { | |
| System.out.printf( | |
| "%-26s accessing %26s's %-25s %-43s - expected %s, actual %s: %s\n", | |
| currentClassSupplier, memberClassSupplier, trial.member.name(), | |
| (obj == null ? "" : "with instance of " + targetClassSupplier), | |
| (trial.expectAllowed ? "allowed" : "denied "), | |
| (trial.actualAllowed ? "allowed" : "denied "), | |
| (trial.expectAllowed == trial.actualAllowed ? "OK" : "FAILURE") | |
| ); | |
| } | |
| return trial; | |
| }).collect( | |
| groupingBy( | |
| Trial::isActualAllowed, | |
| mapping( | |
| Trial::getMember, | |
| toCollection(() -> EnumSet.noneOf(MemberFactory.class)))) | |
| ); | |
| EnumSet<MemberFactory> actualAllowedMembers = | |
| Optional.ofNullable(actualMembers.get(true)) | |
| .orElse(EnumSet.noneOf(MemberFactory.class)); | |
| EnumSet<MemberFactory> actualDeniedMembers = | |
| Optional.ofNullable(actualMembers.get(false)) | |
| .orElse(EnumSet.noneOf(MemberFactory.class)); | |
| if (generateCases) { | |
| System.out.printf( | |
| " ok &= new Test()\n" + | |
| " .current(%s)\n" + | |
| " .member (%s).target(%s)\n", | |
| currentClassSupplier, | |
| memberClassSupplier, targetClassSupplier | |
| ); | |
| if (!actualAllowedMembers.isEmpty()) { | |
| EnumSet<? extends Enum> actualAllowed = | |
| MemberFactory.membersToGroupsOrNull(actualAllowedMembers); | |
| if (actualAllowed == null) | |
| actualAllowed = actualAllowedMembers; | |
| System.out.print( | |
| chunkBy(3, actualAllowed.stream().map(Enum::name)) | |
| .map(chunk -> chunk.collect(joining(", "))) | |
| .collect(joining(",\n" + | |
| " ", | |
| " .allowed(", | |
| ")\n")) | |
| ); | |
| } | |
| if (!actualDeniedMembers.isEmpty()) { | |
| EnumSet<? extends Enum> actualDenied = | |
| MemberFactory.membersToGroupsOrNull(actualDeniedMembers); | |
| if (actualDenied == null) | |
| actualDenied = actualAllowedMembers; | |
| System.out.print( | |
| chunkBy(3, actualDenied.stream().map(Enum::name)) | |
| .map(chunk -> chunk.collect(joining(", "))) | |
| .collect(joining(",\n" + | |
| " ", | |
| " .denied (", | |
| ")\n")) | |
| ); | |
| } | |
| System.out.print( | |
| " .perform();\n" | |
| ); | |
| } | |
| return expectAllowedMembers.equals(actualAllowedMembers) && | |
| expectDeniedMembers.equals(actualDeniedMembers); | |
| } | |
| } | |
| private static <T> Stream<Stream<T>> chunkBy(int chunkSize, Stream<T> stream) { | |
| Iterator<T> elements = stream.iterator(); | |
| Stream.Builder<Stream<T>> b1 = Stream.builder(); | |
| while (elements.hasNext()) { | |
| Stream.Builder<T> b2 = Stream.builder(); | |
| for (int i = 0; i < chunkSize && elements.hasNext(); i++) { | |
| b2.accept(elements.next()); | |
| } | |
| b1.accept(b2.build()); | |
| } | |
| return b1.build(); | |
| } | |
| private static class Trial { | |
| final MemberFactory member; | |
| final boolean expectAllowed; | |
| boolean actualAllowed; | |
| Trial(MemberFactory member, boolean expectAllowed) { | |
| this.member = member; | |
| this.expectAllowed = expectAllowed; | |
| } | |
| MemberFactory getMember() { | |
| return member; | |
| } | |
| boolean isActualAllowed() { | |
| return actualAllowed; | |
| } | |
| } | |
| } |