Skip to content

Instantly share code, notes, and snippets.

@tabjy
Last active November 20, 2019 17:25
Show Gist options
  • Save tabjy/97ed086b674823d9568a32283a65c4c0 to your computer and use it in GitHub Desktop.
Save tabjy/97ed086b674823d9568a32283a65c4c0 to your computer and use it in GitHub Desktop.
import the_package.MyObject;
import the_package.OtherObject;
import the_package.SubMyObject;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Objects;
// for pre-java 11
public class AccessCheck {
public static boolean isAccessible(Class<?> targetClass, String prop, Class<?> currentClass) throws NoSuchFieldException {
Field field = targetClass.getDeclaredField(prop);
int modifiers = field.getModifiers();
Class<?> memberClass = field.getDeclaringClass();
if (Modifier.isStatic(modifiers)) {
targetClass = null;
}
if (currentClass == memberClass) {
return true;
}
if (!verifyModuleAccess(memberClass, currentClass)) {
return false;
}
boolean gotIsSameClassPackage = false;
boolean isSameClassPackage = false;
if (!Modifier.isPublic(getClassAccessFlags(memberClass))) {
isSameClassPackage = isSameClassPackage(currentClass, memberClass);
gotIsSameClassPackage = true;
if (!isSameClassPackage) {
return false;
}
}
// At this point we know that currentClass can access memberClass.
if (Modifier.isPublic(modifiers)) {
return true;
}
// Check for nestmate access if member is private
if (Modifier.isPrivate(modifiers)) {
// Note: targetClass may be outside the nest, but that is okay
// as long as memberClass is in the nest.
if (areNestMates(currentClass, memberClass)) {
return true;
}
}
boolean successSoFar = false;
if (Modifier.isProtected(modifiers)) {
// See if currentClass is a subclass of memberClass
if (isSubclassOf(currentClass, memberClass)) {
successSoFar = true;
}
}
if (!successSoFar && !Modifier.isPrivate(modifiers)) {
if (!gotIsSameClassPackage) {
isSameClassPackage = isSameClassPackage(currentClass,
memberClass);
gotIsSameClassPackage = true;
}
if (isSameClassPackage) {
successSoFar = true;
}
}
if (!successSoFar) {
return false;
}
// Additional test for protected instance members
// and protected constructors: JLS 6.6.2
if (targetClass != null && Modifier.isProtected(modifiers) &&
targetClass != currentClass)
{
if (!gotIsSameClassPackage) {
isSameClassPackage = isSameClassPackage(currentClass, memberClass);
gotIsSameClassPackage = true;
}
if (!isSameClassPackage) {
if (!isSubclassOf(targetClass, currentClass)) {
return false;
}
}
}
return true;
}
public static boolean verifyModuleAccess(Class<?> targetClass, Class<?> callerClass) {
String version = System.getProperty("java.version");
if (Integer.parseInt(version.substring(0, version.indexOf("."))) < 9) {
return true; // There is no module for pre-java 9
}
Object targetModule;
Object callerModule;
try {
Method getModuleMethod = Class.class.getDeclaredMethod("getModule");
targetModule = getModuleMethod.invoke(targetClass);
callerModule = getModuleMethod.invoke(callerClass);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e); // this should not happen
}
if (targetModule == callerModule) {
return true;
}
String pkg = getPackageName(targetClass);
try {
Method isExportedMethod = targetModule.getClass().getDeclaredMethod("isExported", String.class, Class.forName("java.lang.Module"));
return (boolean) isExportedMethod.invoke(targetModule, pkg, callerModule);
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e); // this should not happen
}
}
private static String getPackageName(Class<?> clazz) {
// TODO: verify same behaviour as Class.getPacakgeName()
while (clazz.isArray()) {
clazz = clazz.getComponentType();
}
if (clazz.isPrimitive()) {
return "java.lang";
}
String cn = clazz.getName();
int dot = cn.lastIndexOf('.');
return (dot != -1) ? cn.substring(0, dot).intern() : "";
}
private static int getClassAccessFlags(Class<?> c) {
return c.getModifiers(); // See Reflection.getClassAccessFlags(Class<?> c)
}
private static boolean isSameClassPackage(Class<?> lhs, Class<?> rhs) {
if (lhs.getClassLoader() != rhs.getClassLoader())
return false;
return Objects.equals(getPackageName(lhs), getPackageName(rhs));
}
private static boolean areNestMates(Class<?> currentClass, Class<?> memberClass) {
// TODO: verify same behaviour as Class.isNestmateOf
return currentClass.getName().startsWith(memberClass.getName() + "$") ||
memberClass.getName().startsWith(currentClass.getName() + "$");
}
private static boolean isSubclassOf(Class<?> queryClass,
Class<?> ofClass)
{
while (queryClass != null) {
if (queryClass == ofClass) {
return true;
}
queryClass = queryClass.getSuperclass();
}
return false;
}
public static void main(String[] args) throws NoSuchFieldException {
// same class
assertAccessible(MyObject.class, "thePublic", MyObject.class);
assertAccessible(MyObject.class, "theProtected", MyObject.class);
assertAccessible(MyObject.class, "thePrivate", MyObject.class);
assertAccessible(MyObject.class, "thePackagePrivate", MyObject.class);
// Nestmate class
// XXX: nestmate access must be polyfill with bridge methods before java 11
assertAccessible(MyObject.class, "thePublic", MyObject.MyInnerObject.class);
assertAccessible(MyObject.class, "theProtected", MyObject.MyInnerObject.class);
assertAccessible(MyObject.class, "thePrivate", MyObject.MyInnerObject.class);
assertAccessible(MyObject.class, "thePackagePrivate", MyObject.MyInnerObject.class);
// Same package subclass
assertAccessible(MyObject.class, "thePublic", SubMyObject.class);
assertAccessible(MyObject.class, "theProtected", SubMyObject.class);
assertNotAccessible(MyObject.class, "thePrivate", SubMyObject.class);
assertAccessible(MyObject.class, "thePackagePrivate", SubMyObject.class);
// Same package non-subclass
assertAccessible(MyObject.class, "thePublic", OtherObject.class);
assertAccessible(MyObject.class, "theProtected", OtherObject.class);
assertNotAccessible(MyObject.class, "thePrivate", OtherObject.class);
assertAccessible(MyObject.class, "thePackagePrivate", OtherObject.class);
// Different package subclass
assertAccessible(MyObject.class, "thePublic", OtherSubMyObject.class);
assertAccessible(MyObject.class, "theProtected", OtherSubMyObject.class);
assertNotAccessible(MyObject.class, "thePrivate", OtherSubMyObject.class);
assertNotAccessible(MyObject.class, "thePackagePrivate", OtherSubMyObject.class);
// Different package non-subclass
assertAccessible(MyObject.class, "thePublic", AccessCheck.class);
assertNotAccessible(MyObject.class, "theProtected", AccessCheck.class);
assertNotAccessible(MyObject.class, "thePrivate", AccessCheck.class);
assertNotAccessible(MyObject.class, "thePackagePrivate", AccessCheck.class);
// Different module non-subclass
assertAccessible(MyObject.class, "thePublic", String.class);
assertNotAccessible(MyObject.class, "theProtected", String.class);
assertNotAccessible(MyObject.class, "thePrivate", String.class);
assertNotAccessible(MyObject.class, "thePackagePrivate", String.class);
}
public static void assertAccessible(Class<?> to, String field, Class<?> from) throws NoSuchFieldException {
if (!isAccessible(to, field, from)) {
throw new RuntimeException(String.format("%s.%s should be accessible from %s", to.getName(), field, from.getName()));
}
System.out.printf("%s.%s is accessible from %s\n", to.getName(), field, from.getName());
}
public static void assertNotAccessible(Class<?> to, String field, Class<?> from) throws NoSuchFieldException {
if (isAccessible(to, field, from)) {
throw new RuntimeException(String.format("%s.%s should be accessible from %s", to.getName(), field, from.getName()));
}
System.out.printf("%s.%s is not accessible from %s\n", to.getName(), field, from.getName());
}
class MyMyObjection extends MyObject{}
}
package com.tabjy.snippets.reflection;
import com.tabjy.snippets.asm.path_syntax_evaluation.Pojo;
import com.tabjy.snippets.asm.path_syntax_evaluation.SubPojo;
import com.tabjy.snippets.asm.path_syntax_evaluation.Target;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Objects;
// for java 11
public class CheckAccess {
static boolean isAccessible(Class<?> targetClass, String prop, Class<?> currentClass) throws NoSuchFieldException {
Field field = targetClass.getDeclaredField(prop);
if (field == null) {
return false;
}
int modifiers = field.getModifiers();
Class<?> memberClass = field.getDeclaringClass();
if (Modifier.isStatic(modifiers)) {
targetClass = null;
}
return verifyMemberAccess(currentClass, memberClass, targetClass, modifiers);
}
public static boolean verifyMemberAccess(Class<?> currentClass,
Class<?> memberClass,
Class<?> targetClass,
int modifiers)
{
if (currentClass == memberClass) {
// Always succeeds
return true;
}
if (!verifyModuleAccess(currentClass.getModule(), memberClass)) {
return false;
}
boolean gotIsSameClassPackage = false;
boolean isSameClassPackage = false;
if (!Modifier.isPublic(getClassAccessFlags(memberClass))) {
isSameClassPackage = isSameClassPackage(currentClass, memberClass);
gotIsSameClassPackage = true;
if (!isSameClassPackage) {
return false;
}
}
// At this point we know that currentClass can access memberClass.
if (Modifier.isPublic(modifiers)) {
return true;
}
// Check for nestmate access if member is private
if (Modifier.isPrivate(modifiers)) {
// Note: targetClass may be outside the nest, but that is okay
// as long as memberClass is in the nest.
if (areNestMates(currentClass, memberClass)) {
return true;
}
}
boolean successSoFar = false;
if (Modifier.isProtected(modifiers)) {
// See if currentClass is a subclass of memberClass
if (isSubclassOf(currentClass, memberClass)) {
successSoFar = true;
}
}
if (!successSoFar && !Modifier.isPrivate(modifiers)) {
if (!gotIsSameClassPackage) {
isSameClassPackage = isSameClassPackage(currentClass,
memberClass);
gotIsSameClassPackage = true;
}
if (isSameClassPackage) {
successSoFar = true;
}
}
if (!successSoFar) {
return false;
}
// Additional test for protected instance members
// and protected constructors: JLS 6.6.2
if (targetClass != null && Modifier.isProtected(modifiers) &&
targetClass != currentClass)
{
if (!gotIsSameClassPackage) {
isSameClassPackage = isSameClassPackage(currentClass, memberClass);
gotIsSameClassPackage = true;
}
if (!isSameClassPackage) {
if (!isSubclassOf(targetClass, currentClass)) {
return false;
}
}
}
return true;
}
public static boolean verifyModuleAccess(Module currentModule, Class<?> memberClass) {
Module memberModule = memberClass.getModule();
if (currentModule == memberModule) {
// same module (named or unnamed) or both null if called
// before module system is initialized, which means we are
// dealing with java.base only.
return true;
} else {
String pkg = memberClass.getPackageName();
return memberModule.isExported(pkg, currentModule);
}
}
private static boolean isSameClassPackage(Class<?> c1, Class<?> c2) {
if (c1.getClassLoader() != c2.getClassLoader())
return false;
return Objects.equals(c1.getPackageName(), c2.getPackageName());
}
// non-native polyfill
public static int getClassAccessFlags(Class<?> c) {
return c.getModifiers(); // See Reflection.getClassAccessFlags(Class<?> c)
}
// non-native polyfill
public static boolean areNestMates(Class<?> currentClass, Class<?> memberClass) {
return currentClass.isNestmateOf(memberClass);
}
public static void main(String[] args) throws NoSuchFieldException {
// same class
assertAccessible(Pojo.class, "thePublic", Pojo.class);
assertAccessible(Pojo.class, "theProtected", Pojo.class);
assertAccessible(Pojo.class, "thePrivate", Pojo.class);
assertAccessible(Pojo.class, "thePackagePrivate", Pojo.class);
// Nestmate class
assertAccessible(Pojo.class, "thePublic", Pojo.NestPojo.class);
assertAccessible(Pojo.class, "theProtected", Pojo.NestPojo.class);
assertAccessible(Pojo.class, "thePrivate", Pojo.NestPojo.class);
assertAccessible(Pojo.class, "thePackagePrivate", Pojo.NestPojo.class);
// Same package subclass
assertAccessible(Pojo.class, "thePublic", SubPojo.class);
assertAccessible(Pojo.class, "theProtected", SubPojo.class);
assertNotAccessible(Pojo.class, "thePrivate", SubPojo.class);
assertAccessible(Pojo.class, "thePackagePrivate", SubPojo.class);
// Same package non-subclass
assertAccessible(Pojo.class, "thePublic", Target.class);
assertAccessible(Pojo.class, "theProtected", Target.class);
assertNotAccessible(Pojo.class, "thePrivate", Target.class);
assertAccessible(Pojo.class, "thePackagePrivate", Target.class);
// Different package subclass
assertAccessible(Pojo.class, "thePublic", MyPojo.class);
assertAccessible(Pojo.class, "theProtected", MyPojo.class);
assertNotAccessible(Pojo.class, "thePrivate", MyPojo.class);
assertNotAccessible(Pojo.class, "thePackagePrivate", MyPojo.class);
// Different package non-subclass
assertAccessible(Pojo.class, "thePublic", CheckAccess.class);
assertNotAccessible(Pojo.class, "theProtected", CheckAccess.class);
assertNotAccessible(Pojo.class, "thePrivate", CheckAccess.class);
assertNotAccessible(Pojo.class, "thePackagePrivate", CheckAccess.class);
}
public static void assertAccessible(Class<?> to, String field, Class<?> from) throws NoSuchFieldException {
if (!isAccessible(to, field, from)) {
throw new RuntimeException(String.format("%s.%s should be accessible from %s", Pojo.class.getName(), field, from.getName()));
}
System.out.printf("%s.%s is accessible from %s\n", to.getName(), field, from.getName());
}
public static void assertNotAccessible(Class<?> to, String field, Class<?> from) throws NoSuchFieldException {
if (isAccessible(to, field, from)) {
throw new RuntimeException(String.format("%s.%s should be accessible from %s", Pojo.class.getName(), field, from.getName()));
}
System.out.printf("%s.%s is accessible from %s\n", to.getName(), field, from.getName());
}
static boolean isSubclassOf(Class<?> queryClass,
Class<?> ofClass)
{
while (queryClass != null) {
if (queryClass == ofClass) {
return true;
}
queryClass = queryClass.getSuperclass();
}
return false;
}
class MyPojo extends Pojo {}
}
@tabjy
Copy link
Author

tabjy commented Nov 19, 2019

Language level 11.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment