Skip to content

Instantly share code, notes, and snippets.

@tabjy
Last active November 20, 2019 17:25
Show Gist options
  • Select an option

  • Save tabjy/97ed086b674823d9568a32283a65c4c0 to your computer and use it in GitHub Desktop.

Select an option

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

tabjy commented Nov 19, 2019

Copy link
Copy Markdown
Author

Language level 11.

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