diff --git a/testable-processor/src/main/java/com/alibaba/testable/processor/EnablePrivateAccessProcessor.java b/testable-processor/src/main/java/com/alibaba/testable/processor/EnablePrivateAccessProcessor.java index 20cac55..ddd44f1 100644 --- a/testable-processor/src/main/java/com/alibaba/testable/processor/EnablePrivateAccessProcessor.java +++ b/testable-processor/src/main/java/com/alibaba/testable/processor/EnablePrivateAccessProcessor.java @@ -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)); + } } } diff --git a/testable-processor/src/main/java/com/alibaba/testable/processor/exception/MemberNotExistException.java b/testable-processor/src/main/java/com/alibaba/testable/processor/exception/MemberNotExistException.java new file mode 100644 index 0000000..ea22341 --- /dev/null +++ b/testable-processor/src/main/java/com/alibaba/testable/processor/exception/MemberNotExistException.java @@ -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 + "\""); + } + +} diff --git a/testable-processor/src/main/java/com/alibaba/testable/processor/translator/EnablePrivateAccessTranslator.java b/testable-processor/src/main/java/com/alibaba/testable/processor/translator/EnablePrivateAccessTranslator.java index ca70de3..ac04ed5 100644 --- a/testable-processor/src/main/java/com/alibaba/testable/processor/translator/EnablePrivateAccessTranslator.java +++ b/testable-processor/src/main/java/com/alibaba/testable/processor/translator/EnablePrivateAccessTranslator.java @@ -48,6 +48,7 @@ public class EnablePrivateAccessTranslator extends BaseTranslator { private final ListBuffer privateMethods = new ListBuffer(); 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); diff --git a/testable-processor/src/main/java/com/alibaba/testable/processor/translator/PrivateAccessChecker.java b/testable-processor/src/main/java/com/alibaba/testable/processor/translator/PrivateAccessChecker.java new file mode 100644 index 0000000..f72f03d --- /dev/null +++ b/testable-processor/src/main/java/com/alibaba/testable/processor/translator/PrivateAccessChecker.java @@ -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 FIELD_ACCESS_METHOD = Arrays.asList(new String[] + { "get", "set", "getStatic", "setStatic" }.clone()); + private static final List 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 privateOrFinalFields; + private final List privateMethods; + + public PrivateAccessChecker(String className, List privateOrFinalFields, List 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); + } + } + } + } + } + +}