mirror of
https://github.com/alibaba/testable-mock.git
synced 2025-02-09 19:30:53 +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) {
|
private void processClassElement(Symbol.ClassSymbol clazz) {
|
||||||
JCTree tree = cx.trees.getTree(clazz);
|
if (cx.trees != null) {
|
||||||
tree.accept(new EnablePrivateAccessTranslator(clazz, cx));
|
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 ListBuffer<String> privateMethods = new ListBuffer<String>();
|
||||||
|
|
||||||
private final PrivateAccessStatementGenerator privateAccessStatementGenerator;
|
private final PrivateAccessStatementGenerator privateAccessStatementGenerator;
|
||||||
|
private final PrivateAccessChecker privateAccessChecker;
|
||||||
|
|
||||||
public EnablePrivateAccessTranslator(Symbol.ClassSymbol clazz, TestableContext cx) {
|
public EnablePrivateAccessTranslator(Symbol.ClassSymbol clazz, TestableContext cx) {
|
||||||
String pkgName = ((Symbol.PackageSymbol)clazz.owner).fullname.toString();
|
String pkgName = ((Symbol.PackageSymbol)clazz.owner).fullname.toString();
|
||||||
@ -56,51 +57,18 @@ public class EnablePrivateAccessTranslator extends BaseTranslator {
|
|||||||
this.privateAccessStatementGenerator = new PrivateAccessStatementGenerator(cx);
|
this.privateAccessStatementGenerator = new PrivateAccessStatementGenerator(cx);
|
||||||
this.sourceClassName = cx.names.fromString(sourceClass);
|
this.sourceClassName = cx.names.fromString(sourceClass);
|
||||||
try {
|
try {
|
||||||
Class<?> cls = null;
|
|
||||||
String sourceClassFullName = pkgName + "." + sourceClass;
|
String sourceClassFullName = pkgName + "." + sourceClass;
|
||||||
try {
|
Class<?> cls = getSourceClass(clazz, sourceClassFullName);
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (cls == null) {
|
if (cls == null) {
|
||||||
System.err.println("Failed to load source class: " + sourceClassFullName);
|
cx.logger.error("Failed to load source class: " + sourceClassFullName);
|
||||||
return;
|
} else {
|
||||||
}
|
findAllPrivateMembers(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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
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
|
// check is invoking a private method of source class
|
||||||
if (expr instanceof JCMethodInvocation) {
|
if (expr instanceof JCMethodInvocation) {
|
||||||
JCMethodInvocation invocation = (JCMethodInvocation)expr;
|
JCMethodInvocation invocation = (JCMethodInvocation)expr;
|
||||||
|
privateAccessChecker.validate(invocation);
|
||||||
MemberType memberType = checkInvokeType(invocation);
|
MemberType memberType = checkInvokeType(invocation);
|
||||||
if (memberType.equals(MemberType.PRIVATE_OR_FINAL)) {
|
if (memberType.equals(MemberType.PRIVATE_OR_FINAL)) {
|
||||||
expr = privateAccessStatementGenerator.fetchInvokeStatement(invocation);
|
expr = privateAccessStatementGenerator.fetchInvokeStatement(invocation);
|
||||||
@ -194,11 +163,56 @@ public class EnablePrivateAccessTranslator extends BaseTranslator {
|
|||||||
return expr;
|
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)
|
private Class<?> loadClass(String targetFolderPath, String sourceClassFullName)
|
||||||
throws ClassNotFoundException, MalformedURLException {
|
throws ClassNotFoundException, MalformedURLException {
|
||||||
return new URLClassLoader(new URL[] {new URL(targetFolderPath)}).loadClass(sourceClassFullName);
|
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) {
|
private MemberType checkGetterType(JCFieldAccess access) {
|
||||||
if (access.selected instanceof JCIdent && privateOrFinalFields.contains(access.name.toString())) {
|
if (access.selected instanceof JCIdent && privateOrFinalFields.contains(access.name.toString())) {
|
||||||
return checkSourceClassOrIns(((JCIdent)access.selected).name);
|
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