소스 검색

#102 #105 IClass: ignore not accessible members

Ranides Atterwim 1 년 전
부모
커밋
b743c9f92c

+ 2 - 0
assira.core/src/main/java/net/ranides/assira/reflection/IExecutable.java

@@ -358,6 +358,8 @@ public interface IExecutable extends IElement {
      */
     IExecutable accessible();
 
+    boolean isAccessible();
+
     /**
      * Alias for {@code attribute(IAttribute.ABSTRACT)}.
      *

+ 2 - 0
assira.core/src/main/java/net/ranides/assira/reflection/IField.java

@@ -123,6 +123,8 @@ public interface IField extends IElement {
      */
     IField accessible();
 
+    boolean isAccessible();
+
     /**
      * Returns MethodHandle for read operation.
      *

+ 9 - 4
assira.core/src/main/java/net/ranides/assira/reflection/impl/RClass.java

@@ -13,6 +13,7 @@ import net.ranides.assira.generic.LazyCaller;
 import net.ranides.assira.generic.LazyCallers;
 import net.ranides.assira.generic.SerializationUtils;
 import net.ranides.assira.reflection.*;
+import net.ranides.assira.reflection.util.ReflectUtils;
 
 import java.io.Serializable;
 import java.lang.annotation.Annotation;
@@ -145,13 +146,14 @@ public class RClass<T> extends AClass<T> implements Serializable {
     private CQuery<IMethod> getRMethods(AHints hints) {
         IContext c = context();
         if(hints.isDeclaredHint()) {
-            return CQuery.from().array(type.getDeclaredMethods()).map(m -> FMethod.newMethod(true, c, m));
+            return CQuery.from().array(type.getDeclaredMethods()).filter(ReflectUtils::isAccessible).map(m -> FMethod.newMethod(true, c, m));
         } else if(hints.isInheritedHint()) {
             return CQuery.from()
                 .iterable(() -> new RawAncestors(type)).flatArray(Class::getDeclaredMethods)
+                .filter(ReflectUtils::isAccessible)
                 .map(m -> FMethod.newMethod(isdecl(m), c, m));
         } else {
-            return CQuery.from().array(type.getMethods()).map(m -> FMethod.newMethod(isdecl(m), c, m));
+            return CQuery.from().array(type.getMethods()).filter(ReflectUtils::isAccessible).map(m -> FMethod.newMethod(isdecl(m), c, m));
         }
     }
     
@@ -162,9 +164,9 @@ public class RClass<T> extends AClass<T> implements Serializable {
     private CQuery getRConstructors(AHints hints) {
         IContext c = context();
         if(hints.isInheritedHint() || hints.isDeclaredHint()) {
-            return CQuery.from().array(type.getDeclaredConstructors()).map(m -> FMethod.newConstructor(c,m));
+            return CQuery.from().array(type.getDeclaredConstructors()).filter(ReflectUtils::isAccessible).map(m -> FMethod.newConstructor(c,m));
         } else {
-            return CQuery.from().array(type.getConstructors()).map(m -> FMethod.newConstructor(c,m));
+            return CQuery.from().array(type.getConstructors()).filter(ReflectUtils::isAccessible).map(m -> FMethod.newConstructor(c,m));
         }
     }
     
@@ -215,14 +217,17 @@ public class RClass<T> extends AClass<T> implements Serializable {
         if(hints.isDeclaredHint()) {
             return CQuery.from()
                 .array(type.getDeclaredFields())
+                .filter(ReflectUtils::isAccessible)
                 .map(f -> FField.newField(true, c, f));
         } else if(hints.isInheritedHint()) {
             return CQuery.from()
                 .iterable(() -> new RawAncestors(type)).flatArray(Class::getDeclaredFields)
+                .filter(ReflectUtils::isAccessible)
                 .map(f -> FField.newField(isdecl(f), c, f));
         } else {
             return CQuery.from()
                 .array(type.getFields())
+                .filter(ReflectUtils::isAccessible)
                 .map(f -> FField.newField(isdecl(f), c, f));
         }
     }

+ 6 - 0
assira.core/src/main/java/net/ranides/assira/reflection/impl/RConstructor.java

@@ -130,6 +130,11 @@ public final class RConstructor<T> extends AConstructor<T> implements Serializab
 		return this;
 	}
 
+	@Override
+	public boolean isAccessible() {
+		return ReflectUtils.isAccessible(method);
+	}
+
 	@Override
 	public MethodHandle handle() {
         try {
@@ -141,6 +146,7 @@ public final class RConstructor<T> extends AConstructor<T> implements Serializab
 
 	@Override
 	public AnyFunction<T> accessor() {
+
         Object accessor = IClass.typefor(method)
             .method("acquireConstructorAccessor")
             .orElseThrow(() -> new IReflectiveException("Incompatible JDK: no #acquireConstructorAccessor"))

+ 5 - 0
assira.core/src/main/java/net/ranides/assira/reflection/impl/RField.java

@@ -99,6 +99,11 @@ public class RField extends AField implements IField, Serializable {
         return this;
     }
 
+    @Override
+    public boolean isAccessible() {
+        return ReflectUtils.isAccessible(field);
+    }
+
     @Override
     public IField rewritable() {
         ReflectUtils.rewritable(field);

+ 5 - 0
assira.core/src/main/java/net/ranides/assira/reflection/impl/RMethod.java

@@ -125,6 +125,11 @@ public class RMethod extends AMethod implements IMethod, Serializable {
 		return this;
 	}
 
+	@Override
+	public boolean isAccessible() {
+		return ReflectUtils.isAccessible(method);
+	}
+
 	@Override
 	public MethodHandle handle() {
         return handle.compute();

+ 5 - 0
assira.core/src/main/java/net/ranides/assira/reflection/impl/SMethod.java

@@ -50,6 +50,11 @@ public class SMethod extends AMethod implements IMethod, Serializable {
         return this;
     }
 
+    @Override
+    public boolean isAccessible() {
+        return true;
+    }
+
     @Override
     public Method reflective() {
         throw new IReflectiveException(NSE);

+ 1 - 26
assira.core/src/main/java/net/ranides/assira/reflection/impl/bean/RBeanModel.java

@@ -18,7 +18,6 @@ import net.ranides.assira.generic.SerializationUtils;
 import net.ranides.assira.generic.Wrappers.AbstractWrapper;
 import net.ranides.assira.reflection.*;
 import net.ranides.assira.reflection.util.MemberUtils;
-import net.ranides.assira.system.RuntimeConfiguration;
 
 import java.io.Serializable;
 import java.util.*;
@@ -61,16 +60,7 @@ public class RBeanModel implements BeanModel, Serializable {
             errors.add(new BeanException(type, "Unsafe reflective non-public scan: " + type));
         }
 
-        CQuery<IMethod> imethods;
-        if(RuntimeConfiguration.FEATURE_BEAN_MODEL_FOR_PRIVATE() || type.isPublic()) {
-            imethods = type.methods().stream();
-        } else {
-            Set<IClass<?>> types = new HashSet<>();
-            collectPublicTypes(type, types);
-            imethods = CQuery.from(types).flat(v -> v.methods().stream()).distinct();
-        }
-
-        for (IMethod m : imethods) {
+        for (IMethod m : type.methods().stream()) {
             if (m.parent().equals(IClass.OBJECT) && !OBJECT_METHODS.contains(m.name())) {
                 continue;
             }
@@ -113,21 +103,6 @@ public class RBeanModel implements BeanModel, Serializable {
             .join(fluentSet.keySet());
     }
 
-    private static void collectPublicTypes(IClass<?> type, Set<IClass<?>> target) {
-        if(type.equals(IClass.OBJECT)) {
-            return;
-        }
-        if(type.isPublic()) {
-            target.add(type);
-            return;
-        }
-
-        collectPublicTypes(type.parent(), target);
-        for(IClass<?> x : type.interfaces().require(IAttribute.DECLARED)) {
-            collectPublicTypes(x, target);
-        }
-    }
-    
     @Override
     public IClass<?> type() {
         return type;

+ 33 - 0
assira.core/src/main/java/net/ranides/assira/reflection/util/FReflectSupport6.java

@@ -0,0 +1,33 @@
+package net.ranides.assira.reflection.util;
+
+import net.ranides.assira.reflection.IReflectiveException;
+import net.ranides.assira.system.RuntimeUtils;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Member;
+
+public class FReflectSupport6 implements ReflectUtils.IReflectSupport {
+
+    @Override
+    public boolean isStable() {
+        return RuntimeUtils.getJavaVersion() <= 8;
+    }
+
+    @Override
+    public boolean isAccessible(Member object) {
+        return true;
+    }
+
+    @Override
+    public <T extends AccessibleObject> T access(T object) {
+        if( object.isAccessible() ) {
+            return object;
+        }
+        try {
+            RuntimeUtils.doPrivileged(() -> object.setAccessible(true));
+        } catch(SecurityException cause) {
+            throw new IReflectiveException(cause);
+        }
+        return object;
+    }
+}

+ 36 - 9
assira.core/src/main/java/net/ranides/assira/reflection/util/ReflectUtils.java

@@ -8,6 +8,7 @@ package net.ranides.assira.reflection.util;
 
 import lombok.experimental.UtilityClass;
 
+import net.ranides.assira.generic.Ref;
 import net.ranides.assira.io.IOStreams;
 import net.ranides.assira.io.PathUtils;
 import net.ranides.assira.io.ResourceUtils;
@@ -21,6 +22,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.lang.reflect.AccessibleObject;
 import java.lang.reflect.Field;
+import java.lang.reflect.Member;
 import java.lang.reflect.Modifier;
 import java.nio.file.Path;
 import java.security.CodeSource;
@@ -34,6 +36,39 @@ import java.util.MissingResourceException;
 @UtilityClass
 public final class ReflectUtils {
 
+    interface IReflectSupport {
+
+        boolean isStable();
+
+        boolean isAccessible(Member object);
+
+        <T extends AccessibleObject> T access(T object);
+
+    }
+
+    private static final IReflectSupport SUPPORT = ServiceScanner
+        .scanner(IReflectSupport.class)
+        .resolver(name -> Ref.of(IReflectSupport.class.cast(Class.forName(name).newInstance())))
+        .services()
+        .first()
+        .orElse(null);
+
+    static {
+        if(!SUPPORT.isStable()) {
+            String message = String.format(
+                "%s is unstable on JDK %s. Please use correct version of assira.core for your JDK.",
+                SUPPORT.getClass().getName(),
+                RuntimeUtils.getJavaVersion()
+            );
+
+            throw new IllegalStateException(message);
+        }
+    }
+
+    public static boolean isAccessible(Member object) {
+        return SUPPORT.isAccessible(object);
+    }
+
     /**
      * Forces  reflective access to provided non-public object (field, method, etc).
      * It can throw exception, if it is not allowed because of SecurityException.
@@ -43,15 +78,7 @@ public final class ReflectUtils {
      * @return object
      */
     public static <T extends AccessibleObject> T access(T object) {
-        if( object.isAccessible() ) {
-            return object;
-        }
-        try {
-            RuntimeUtils.doPrivileged(() -> object.setAccessible(true));
-        } catch(SecurityException cause) {
-            throw new IReflectiveException(cause);
-        }
-        return object;
+        return SUPPORT.access(object);
     }
 
     /**

+ 0 - 8
assira.core/src/main/java/net/ranides/assira/system/RuntimeConfiguration.java

@@ -17,8 +17,6 @@ public class RuntimeConfiguration {
 
     private static final String FIX_FILES_WALKER = "assira.fix:java.nio.file.Files.newDirectoryStream";
 
-    private static final String FEATURE_BEAN_MODEL_FOR_PRIVATE = "assira.feature:bean-model-for-private-classes";
-
     private static final boolean FEATURE_CACHE_DAEMON_VALUE =
         getBoolean(FEATURE_CACHE_DAEMON, false);
 
@@ -34,8 +32,6 @@ public class RuntimeConfiguration {
     private static final boolean FEATURE_STRICT_REFLECTIVE_RESOLVE_VALUE =
         getBoolean(FEATURE_STRICT_REFLECTIVE_RESOLVE, false);
 
-    private static final boolean FEATURE_BEAN_MODEL_FOR_PRIVATE_VALUE = true;
-
     public static boolean FEATURE_CACHE_DAEMON() {
         return FEATURE_CACHE_DAEMON_VALUE;
     }
@@ -79,10 +75,6 @@ public class RuntimeConfiguration {
         return FEATURE_STRICT_REFLECTIVE_RESOLVE_VALUE;
     }
 
-    public static boolean FEATURE_BEAN_MODEL_FOR_PRIVATE() {
-        return FEATURE_BEAN_MODEL_FOR_PRIVATE_VALUE;
-    }
-
     private static Boolean getBoolean(String name, boolean other) {
         return RuntimeUtils.getProperty(name).map(Boolean::parseBoolean).orElse(other);
     }

+ 2 - 0
assira.core/src/main/resources/META-INF/services/net.ranides.assira.reflection.util.ReflectUtils$IReflectSupport

@@ -0,0 +1,2 @@
+net.ranides.assira.reflection.util.FReflectSupport11
+net.ranides.assira.reflection.util.FReflectSupport6

+ 2 - 34
assira.core/src/test/java/net/ranides/assira/reflection/impl/bean/RBeanModelTest.java

@@ -19,7 +19,6 @@ import static net.ranides.assira.junit.NewAssert.*;
 import net.ranides.test.mockup.reflection.ForBeanModel2;
 import net.ranides.test.mockup.reflection.ForFluentBean;
 import net.ranides.test.mockup.reflection.ForIClass;
-import org.junit.Assume;
 import org.junit.Test;
 
 /**
@@ -332,9 +331,7 @@ public class RBeanModelTest {
     }
 
     @Test
-    public void testPrivateAccess_unsafe() {
-        Assume.assumeTrue(RuntimeConfiguration.FEATURE_BEAN_MODEL_FOR_PRIVATE());
-
+    public void testPrivateAccess() {
         Object v = ForFluentBean.newHidden("hidden value");
         BeanModel.FluentMap f = BeanModel.typefor(v).fluent(v);
 
@@ -345,36 +342,7 @@ public class RBeanModelTest {
     }
 
     @Test
-    public void testPrivateAccess_safe() {
-        Assume.assumeFalse(RuntimeConfiguration.FEATURE_BEAN_MODEL_FOR_PRIVATE());
-
-        Object v = ForFluentBean.newHidden("hidden value");
-        BeanModel.FluentMap f = BeanModel.typefor(v).fluent(v);
-
-        assertNull(f.get("name"));
-        assertNull(f.get("value"));
-    }
-
-    @Test
-    public void testPrivateAccessParents_safe() {
-        Assume.assumeFalse(RuntimeConfiguration.FEATURE_BEAN_MODEL_FOR_PRIVATE());
-
-        Object v = ForBeanModel2.newHidden();
-        BeanModel.FluentMap f = BeanModel.typefor(v).fluent(v);
-
-        ForBeanModel2.PublicIntermediate z = new ForBeanModel2.PublicIntermediate();
-
-        assertNotNull(z.something());
-        assertNotNull(z.age());
-        assertNotNull(z.name());
-
-        assertEquals(new TreeSet<>(Arrays.asList("something", "age", "name")), new TreeSet<>(f.keySet()));
-    }
-
-    @Test
-    public void testPrivateAccessParents_unsafe() {
-        Assume.assumeTrue(RuntimeConfiguration.FEATURE_BEAN_MODEL_FOR_PRIVATE());
-
+    public void testPrivateAccessParents() {
         Object v = ForBeanModel2.newHidden();
         BeanModel.FluentMap f = BeanModel.typefor(v).fluent(v);
 

+ 87 - 0
assira.core11/src/main/java/net/ranides/assira/reflection/util/FReflectSupport11.java

@@ -0,0 +1,87 @@
+package net.ranides.assira.reflection.util;
+
+import net.ranides.assira.trace.FStackWalker11;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Modifier;
+
+public class FReflectSupport11 implements ReflectUtils.IReflectSupport {
+
+    private final FStackWalker11 walker = new FStackWalker11();
+
+    @Override
+    public boolean isStable() {
+        return true;
+    }
+
+    @Override
+    public boolean isAccessible(Member object) {
+        return object != null && checkCanSetAccessible(object, walker.getType(0), object.getDeclaringClass());
+    }
+
+    @Override
+    public <T extends AccessibleObject> T access(T object) {
+        object.trySetAccessible();
+        return object;
+    }
+
+    private boolean checkCanSetAccessible(Member that, Class<?> caller, Class<?> declaringClass) {
+        // semantic from JDK 11
+        //      java.lang.reflect.AccessibleObject.checkCanSetAccessible
+        //      java.lang.reflect.AccessibleObject.logIfOpenedForIllegalAccess
+
+        final Module callerModule = caller.getModule();
+        final Module declaringModule = declaringClass.getModule();
+
+        if (callerModule == declaringModule) {
+            return true;
+        }
+        if (callerModule == Object.class.getModule()) {
+            return true;
+        }
+        if (!declaringModule.isNamed()) {
+            return true;
+        }
+
+
+        String pn = declaringClass.getPackageName();
+        final boolean isClassPublic = Modifier.isPublic(declaringClass.getModifiers());
+
+        // class is public and package is exported to caller
+        if (isClassPublic && declaringModule.isExported(pn, callerModule)) {
+            final int modifiers = that.getModifiers();
+
+            // member is public
+            if (Modifier.isPublic(modifiers)) {
+                return true;
+            }
+
+            // member is protected-static
+            if (Modifier.isProtected(modifiers)
+                && Modifier.isStatic(modifiers)
+                && isSubclassOf(caller, declaringClass)) {
+                return true;
+            }
+        }
+
+        // package is <<legally>> open to caller
+        if (declaringModule.isOpen(pn, callerModule) && callerModule.isNamed()) {
+            return true;
+        }
+
+        return false;
+    }
+
+    private boolean isSubclassOf(Class<?> queryClass, Class<?> ofClass) {
+        while (queryClass != null) {
+            if (queryClass == ofClass) {
+                return true;
+            }
+            queryClass = queryClass.getSuperclass();
+        }
+        return false;
+    }
+}

+ 5 - 0
assira.lambda/src/main/java/net/ranides/assira/reflection/impl/LMethod.java

@@ -123,6 +123,11 @@ public final class LMethod extends AMethod implements IMethod {
         return this;
     }
 
+    @Override
+    public boolean isAccessible() {
+        return ReflectUtils.isAccessible(method.reflective());
+    }
+
 	@Override
 	public MethodHandle handle() {
         return handle;