validate member accessed via PrivateAccessor exists

This commit is contained in:
金戟 2021-01-08 22:56:21 +08:00
parent 66cb8f7732
commit 7ba44ae618
4 changed files with 121 additions and 41 deletions

View File

@ -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));
}
}
}

View File

@ -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 + "\"");
}
}

View File

@ -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);

View File

@ -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);
}
}
}
}
}
}