mirror of
https://github.com/alibaba/testable-mock.git
synced 2025-02-03 16:20:54 +08:00
validate member accessed via PrivateAccessor exists
This commit is contained in:
parent
66cb8f7732
commit
7ba44ae618
@ -79,8 +79,10 @@ public class EnablePrivateAccessProcessor extends AbstractProcessor {
|
||||
}
|
||||
|
||||
private void processClassElement(Symbol.ClassSymbol clazz) {
|
||||
JCTree tree = cx.trees.getTree(clazz);
|
||||
tree.accept(new EnablePrivateAccessTranslator(clazz, cx));
|
||||
if (cx.trees != null) {
|
||||
JCTree tree = cx.trees.getTree(clazz);
|
||||
tree.accept(new EnablePrivateAccessTranslator(clazz, cx));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,12 @@
|
||||
package com.alibaba.testable.processor.exception;
|
||||
|
||||
/**
|
||||
* @author flin
|
||||
*/
|
||||
public class MemberNotExistException extends RuntimeException {
|
||||
|
||||
public MemberNotExistException(String type, String className, String target) {
|
||||
super(type + " \"" + target + "\" not exist in class \"" + className + "\"");
|
||||
}
|
||||
|
||||
}
|
@ -48,6 +48,7 @@ public class EnablePrivateAccessTranslator extends BaseTranslator {
|
||||
private final ListBuffer<String> privateMethods = new ListBuffer<String>();
|
||||
|
||||
private final PrivateAccessStatementGenerator privateAccessStatementGenerator;
|
||||
private final PrivateAccessChecker privateAccessChecker;
|
||||
|
||||
public EnablePrivateAccessTranslator(Symbol.ClassSymbol clazz, TestableContext cx) {
|
||||
String pkgName = ((Symbol.PackageSymbol)clazz.owner).fullname.toString();
|
||||
@ -56,51 +57,18 @@ public class EnablePrivateAccessTranslator extends BaseTranslator {
|
||||
this.privateAccessStatementGenerator = new PrivateAccessStatementGenerator(cx);
|
||||
this.sourceClassName = cx.names.fromString(sourceClass);
|
||||
try {
|
||||
Class<?> cls = null;
|
||||
String sourceClassFullName = pkgName + "." + sourceClass;
|
||||
try {
|
||||
// maven build goes here
|
||||
cls = Class.forName(sourceClassFullName);
|
||||
} catch (ClassNotFoundException e) {
|
||||
if (System.getProperty(IDEA_PATHS_SELECTOR) != null) {
|
||||
// fit for intellij 2020.3+
|
||||
String sourceFileWrapperString = clazz.sourcefile.toString();
|
||||
String sourceFilePath = sourceFileWrapperString.substring(
|
||||
sourceFileWrapperString.lastIndexOf("[") + 1, sourceFileWrapperString.indexOf("]"));
|
||||
int indexOfSrc = sourceFilePath.lastIndexOf(File.separator + "src" + File.separator);
|
||||
String basePath = sourceFilePath.substring(0, indexOfSrc);
|
||||
String targetFolderPath = PathUtil.fitPathString(basePath + MAVEN_CLASS_FOLDER);
|
||||
try {
|
||||
cls = loadClass(targetFolderPath, sourceClassFullName);
|
||||
} catch (ClassNotFoundException e2) {
|
||||
targetFolderPath = PathUtil.fitPathString(basePath + GRADLE_CLASS_FOLDER);
|
||||
cls = loadClass(targetFolderPath, sourceClassFullName);
|
||||
}
|
||||
} else {
|
||||
// fit for gradle build
|
||||
String path = PathUtil.fitPathString("file:" + System.getProperty(USER_DIR) + GRADLE_CLASS_FOLDER);
|
||||
cls = loadClass(path, sourceClassFullName);
|
||||
}
|
||||
}
|
||||
Class<?> cls = getSourceClass(clazz, sourceClassFullName);
|
||||
if (cls == null) {
|
||||
System.err.println("Failed to load source class: " + sourceClassFullName);
|
||||
return;
|
||||
}
|
||||
Field[] fields = cls.getDeclaredFields();
|
||||
for (Field f : fields) {
|
||||
if (Modifier.isFinal(f.getModifiers()) || Modifier.isPrivate(f.getModifiers())) {
|
||||
privateOrFinalFields.add(f.getName());
|
||||
}
|
||||
}
|
||||
Method[] methods = cls.getDeclaredMethods();
|
||||
for (Method m : methods) {
|
||||
if (Modifier.isPrivate(m.getModifiers())) {
|
||||
privateMethods.add(m.getName());
|
||||
}
|
||||
cx.logger.error("Failed to load source class: " + sourceClassFullName);
|
||||
} else {
|
||||
findAllPrivateMembers(cls);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
this.privateAccessChecker = new PrivateAccessChecker(sourceClassName.toString(),
|
||||
privateOrFinalFields.toList(), privateMethods.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -179,6 +147,7 @@ public class EnablePrivateAccessTranslator extends BaseTranslator {
|
||||
// check is invoking a private method of source class
|
||||
if (expr instanceof JCMethodInvocation) {
|
||||
JCMethodInvocation invocation = (JCMethodInvocation)expr;
|
||||
privateAccessChecker.validate(invocation);
|
||||
MemberType memberType = checkInvokeType(invocation);
|
||||
if (memberType.equals(MemberType.PRIVATE_OR_FINAL)) {
|
||||
expr = privateAccessStatementGenerator.fetchInvokeStatement(invocation);
|
||||
@ -194,11 +163,56 @@ public class EnablePrivateAccessTranslator extends BaseTranslator {
|
||||
return expr;
|
||||
}
|
||||
|
||||
private Class<?> getSourceClass(Symbol.ClassSymbol clazz, String sourceClassFullName)
|
||||
throws MalformedURLException, ClassNotFoundException {
|
||||
Class<?> cls;
|
||||
try {
|
||||
// maven build goes here
|
||||
cls = Class.forName(sourceClassFullName);
|
||||
} catch (ClassNotFoundException e) {
|
||||
if (System.getProperty(IDEA_PATHS_SELECTOR) != null) {
|
||||
// fit for intellij 2020.3+
|
||||
String sourceFileWrapperString = clazz.sourcefile.toString();
|
||||
String sourceFilePath = sourceFileWrapperString.substring(
|
||||
sourceFileWrapperString.lastIndexOf("[") + 1, sourceFileWrapperString.indexOf("]"));
|
||||
int indexOfSrc = sourceFilePath.lastIndexOf(File.separator + "src" + File.separator);
|
||||
String basePath = sourceFilePath.substring(0, indexOfSrc);
|
||||
String targetFolderPath = PathUtil.fitPathString(basePath + MAVEN_CLASS_FOLDER);
|
||||
try {
|
||||
cls = loadClass(targetFolderPath, sourceClassFullName);
|
||||
} catch (ClassNotFoundException e2) {
|
||||
targetFolderPath = PathUtil.fitPathString(basePath + GRADLE_CLASS_FOLDER);
|
||||
cls = loadClass(targetFolderPath, sourceClassFullName);
|
||||
}
|
||||
} else {
|
||||
// fit for gradle build
|
||||
String path = PathUtil.fitPathString("file:" + System.getProperty(USER_DIR) + GRADLE_CLASS_FOLDER);
|
||||
cls = loadClass(path, sourceClassFullName);
|
||||
}
|
||||
}
|
||||
return cls;
|
||||
}
|
||||
|
||||
private Class<?> loadClass(String targetFolderPath, String sourceClassFullName)
|
||||
throws ClassNotFoundException, MalformedURLException {
|
||||
return new URLClassLoader(new URL[] {new URL(targetFolderPath)}).loadClass(sourceClassFullName);
|
||||
}
|
||||
|
||||
private void findAllPrivateMembers(Class<?> cls) {
|
||||
Field[] fields = cls.getDeclaredFields();
|
||||
for (Field f : fields) {
|
||||
if (Modifier.isFinal(f.getModifiers()) || Modifier.isPrivate(f.getModifiers())) {
|
||||
privateOrFinalFields.add(f.getName());
|
||||
}
|
||||
}
|
||||
Method[] methods = cls.getDeclaredMethods();
|
||||
for (Method m : methods) {
|
||||
if (Modifier.isPrivate(m.getModifiers())) {
|
||||
privateMethods.add(m.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private MemberType checkGetterType(JCFieldAccess access) {
|
||||
if (access.selected instanceof JCIdent && privateOrFinalFields.contains(access.name.toString())) {
|
||||
return checkSourceClassOrIns(((JCIdent)access.selected).name);
|
||||
|
@ -0,0 +1,52 @@
|
||||
package com.alibaba.testable.processor.translator;
|
||||
|
||||
import com.alibaba.testable.processor.exception.MemberNotExistException;
|
||||
import com.sun.tools.javac.tree.JCTree;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Validate parameter of PrivateAccessor methods to prevent broken by refactor
|
||||
*
|
||||
* @author flin
|
||||
*/
|
||||
public class PrivateAccessChecker {
|
||||
|
||||
private static final String CLASS_NAME_PRIVATE_ACCESSOR = "PrivateAccessor";
|
||||
private static final List<String> FIELD_ACCESS_METHOD = Arrays.asList(new String[]
|
||||
{ "get", "set", "getStatic", "setStatic" }.clone());
|
||||
private static final List<String> FIELD_INVOKE_METHOD = Arrays.asList(new String[]
|
||||
{ "invoke", "invokeStatic" }.clone());
|
||||
private static final String TYPE_FIELD = "Field";
|
||||
private static final String TYPE_METHOD = "Method";
|
||||
|
||||
private final String className;
|
||||
private final List<String> privateOrFinalFields;
|
||||
private final List<String> privateMethods;
|
||||
|
||||
public PrivateAccessChecker(String className, List<String> privateOrFinalFields, List<String> privateMethods) {
|
||||
this.className = className;
|
||||
this.privateOrFinalFields = privateOrFinalFields;
|
||||
this.privateMethods = privateMethods;
|
||||
}
|
||||
|
||||
public void validate(JCTree.JCMethodInvocation invocation) {
|
||||
if (invocation.meth instanceof JCTree.JCFieldAccess && invocation.args.length() >= 2) {
|
||||
JCTree.JCFieldAccess fieldAccess = (JCTree.JCFieldAccess)invocation.meth;
|
||||
if (fieldAccess.selected instanceof JCTree.JCIdent && invocation.args.get(1) instanceof JCTree.JCLiteral &&
|
||||
((JCTree.JCIdent)fieldAccess.selected).name.toString().equals(CLASS_NAME_PRIVATE_ACCESSOR)) {
|
||||
Object target = ((JCTree.JCLiteral)invocation.args.get(1)).getValue();
|
||||
if (target instanceof String) {
|
||||
String methodName = fieldAccess.name.toString();
|
||||
if (FIELD_ACCESS_METHOD.contains(methodName) && !privateOrFinalFields.contains(target)) {
|
||||
throw new MemberNotExistException(TYPE_FIELD, className, (String)target);
|
||||
} else if (FIELD_INVOKE_METHOD.contains(methodName) && !privateMethods.contains(target)) {
|
||||
throw new MemberNotExistException(TYPE_METHOD, className, (String)target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user