diff --git a/access-modifier-annotation/src/main/java/org/kohsuke/accmod/AccessRestriction.java b/access-modifier-annotation/src/main/java/org/kohsuke/accmod/AccessRestriction.java index 1f9fa6a..1509196 100644 --- a/access-modifier-annotation/src/main/java/org/kohsuke/accmod/AccessRestriction.java +++ b/access-modifier-annotation/src/main/java/org/kohsuke/accmod/AccessRestriction.java @@ -38,7 +38,7 @@ *

* Single execution of the enforcement check would create at most one instance * of a given {@link AccessRestriction} type, so instance fields can be used to store - * heavy-weight objects or other indicies that you might need for implementing + * heavy-weight objects or other indices that you might need for implementing * access control checks. * * @author Kohsuke Kawaguchi diff --git a/access-modifier-checker/pom.xml b/access-modifier-checker/pom.xml index fe0278b..3b2e3f1 100644 --- a/access-modifier-checker/pom.xml +++ b/access-modifier-checker/pom.xml @@ -22,6 +22,11 @@ access-modifier-annotation ${project.version} + + ${project.groupId} + access-modifier-suppressions + ${project.version} + org.ow2.asm asm-debug-all diff --git a/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/api/pom.xml b/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/api/pom.xml new file mode 100644 index 0000000..be7ee62 --- /dev/null +++ b/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/api/pom.xml @@ -0,0 +1,17 @@ + + + 4.0.0 + + test + disable-restrictions-wrong-annotation + 1.0-SNAPSHOT + + api + + + org.kohsuke + access-modifier-annotation + @project.version@ + + + \ No newline at end of file diff --git a/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/api/src/main/java/api/ApiWithRestrictedMethodAndField.java b/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/api/src/main/java/api/ApiWithRestrictedMethodAndField.java new file mode 100644 index 0000000..82faef5 --- /dev/null +++ b/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/api/src/main/java/api/ApiWithRestrictedMethodAndField.java @@ -0,0 +1,13 @@ +package api; + +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +public class ApiWithRestrictedMethodAndField { + + @Restricted(NoExternalUse.class) + public String field; + + @Restricted(NoExternalUse.class) + public static void notReallyPublic() {} +} diff --git a/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/api/src/main/java/api/RestrictedApi.java b/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/api/src/main/java/api/RestrictedApi.java new file mode 100644 index 0000000..ef23039 --- /dev/null +++ b/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/api/src/main/java/api/RestrictedApi.java @@ -0,0 +1,12 @@ +package api; + +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +@Restricted(NoExternalUse.class) +public class RestrictedApi { + + public String field; + + public void doNotUse() {} +} diff --git a/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/api/src/main/java/api/RestrictedInterface.java b/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/api/src/main/java/api/RestrictedInterface.java new file mode 100644 index 0000000..c51fd14 --- /dev/null +++ b/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/api/src/main/java/api/RestrictedInterface.java @@ -0,0 +1,8 @@ +package api; + +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +@Restricted(NoExternalUse.class) +public interface RestrictedInterface { +} diff --git a/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/caller/pom.xml b/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/caller/pom.xml new file mode 100644 index 0000000..55c0989 --- /dev/null +++ b/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/caller/pom.xml @@ -0,0 +1,33 @@ + + + 4.0.0 + + test + disable-restrictions-wrong-annotation + 1.0-SNAPSHOT + + caller + + + ${project.groupId} + api + ${project.version} + + + + + + org.kohsuke + access-modifier-checker + @project.version@ + + + + enforce + + + + + + + \ No newline at end of file diff --git a/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/caller/src/main/java/caller/Caller.java b/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/caller/src/main/java/caller/Caller.java new file mode 100644 index 0000000..6591965 --- /dev/null +++ b/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/caller/src/main/java/caller/Caller.java @@ -0,0 +1,11 @@ +package caller; + +import api.ApiWithRestrictedMethodAndField; + +public class Caller { + + @WrongAnnotation(ApiWithRestrictedMethodAndField.class) + public Caller() { + ApiWithRestrictedMethodAndField.notReallyPublic(); // illegal + } +} diff --git a/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/caller/src/main/java/caller/CallerDisabledAtClassLevel.java b/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/caller/src/main/java/caller/CallerDisabledAtClassLevel.java new file mode 100644 index 0000000..b912657 --- /dev/null +++ b/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/caller/src/main/java/caller/CallerDisabledAtClassLevel.java @@ -0,0 +1,10 @@ +package caller; + +import api.ApiWithRestrictedMethodAndField; + +@WrongAnnotation(ApiWithRestrictedMethodAndField.class) +public class CallerDisabledAtClassLevel { + public CallerDisabledAtClassLevel() { + ApiWithRestrictedMethodAndField.notReallyPublic(); // illegal + } +} \ No newline at end of file diff --git a/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/caller/src/main/java/caller/WrongAnnotation.java b/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/caller/src/main/java/caller/WrongAnnotation.java new file mode 100644 index 0000000..3ff929f --- /dev/null +++ b/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/caller/src/main/java/caller/WrongAnnotation.java @@ -0,0 +1,38 @@ +/* + * The MIT License + * + * Copyright (c) 2018, Steve Arch + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package caller; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Retention(RUNTIME) +@Documented +@Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE}) +public @interface WrongAnnotation { + Class[] value(); +} diff --git a/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/invoker.properties b/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/invoker.properties new file mode 100644 index 0000000..8b87cc7 --- /dev/null +++ b/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/invoker.properties @@ -0,0 +1,2 @@ +invoker.goals=clean package +invoker.buildResult = failure diff --git a/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/pom.xml b/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/pom.xml new file mode 100644 index 0000000..4886872 --- /dev/null +++ b/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/pom.xml @@ -0,0 +1,17 @@ + + + 4.0.0 + test + disable-restrictions-wrong-annotation + 1.0-SNAPSHOT + pom + + UTF-8 + 1.7 + 1.7 + + + api + caller + + \ No newline at end of file diff --git a/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/postbuild.groovy b/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/postbuild.groovy new file mode 100644 index 0000000..ae75e56 --- /dev/null +++ b/access-modifier-checker/src/it/disable-restrictions-wrong-annotation/postbuild.groovy @@ -0,0 +1,2 @@ +assert new File(basedir, 'build.log').text.contains('[ERROR] caller/Caller:9 api/ApiWithRestrictedMethodAndField.notReallyPublic()V must not be used') +assert new File(basedir, 'build.log').text.contains('[ERROR] caller/CallerDisabledAtClassLevel:8 api/ApiWithRestrictedMethodAndField.notReallyPublic()V must not be used') diff --git a/access-modifier-checker/src/it/disable-restrictions/api/pom.xml b/access-modifier-checker/src/it/disable-restrictions/api/pom.xml new file mode 100644 index 0000000..bd06be5 --- /dev/null +++ b/access-modifier-checker/src/it/disable-restrictions/api/pom.xml @@ -0,0 +1,17 @@ + + + 4.0.0 + + test + disable-restrictions + 1.0-SNAPSHOT + + api + + + org.kohsuke + access-modifier-annotation + @project.version@ + + + \ No newline at end of file diff --git a/access-modifier-checker/src/it/disable-restrictions/api/src/main/java/api/ApiWithRestrictedMethodAndField.java b/access-modifier-checker/src/it/disable-restrictions/api/src/main/java/api/ApiWithRestrictedMethodAndField.java new file mode 100644 index 0000000..82faef5 --- /dev/null +++ b/access-modifier-checker/src/it/disable-restrictions/api/src/main/java/api/ApiWithRestrictedMethodAndField.java @@ -0,0 +1,13 @@ +package api; + +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +public class ApiWithRestrictedMethodAndField { + + @Restricted(NoExternalUse.class) + public String field; + + @Restricted(NoExternalUse.class) + public static void notReallyPublic() {} +} diff --git a/access-modifier-checker/src/it/disable-restrictions/api/src/main/java/api/RestrictedApi.java b/access-modifier-checker/src/it/disable-restrictions/api/src/main/java/api/RestrictedApi.java new file mode 100644 index 0000000..ef23039 --- /dev/null +++ b/access-modifier-checker/src/it/disable-restrictions/api/src/main/java/api/RestrictedApi.java @@ -0,0 +1,12 @@ +package api; + +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +@Restricted(NoExternalUse.class) +public class RestrictedApi { + + public String field; + + public void doNotUse() {} +} diff --git a/access-modifier-checker/src/it/disable-restrictions/api/src/main/java/api/RestrictedInterface.java b/access-modifier-checker/src/it/disable-restrictions/api/src/main/java/api/RestrictedInterface.java new file mode 100644 index 0000000..c51fd14 --- /dev/null +++ b/access-modifier-checker/src/it/disable-restrictions/api/src/main/java/api/RestrictedInterface.java @@ -0,0 +1,8 @@ +package api; + +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +@Restricted(NoExternalUse.class) +public interface RestrictedInterface { +} diff --git a/access-modifier-checker/src/it/disable-restrictions/caller/pom.xml b/access-modifier-checker/src/it/disable-restrictions/caller/pom.xml new file mode 100644 index 0000000..95d97b3 --- /dev/null +++ b/access-modifier-checker/src/it/disable-restrictions/caller/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + + test + disable-restrictions + 1.0-SNAPSHOT + + caller + + + ${project.groupId} + api + ${project.version} + + + org.kohsuke + access-modifier-suppressions + @project.version@ + + + + + + org.kohsuke + access-modifier-checker + @project.version@ + + + + enforce + + + + + + + \ No newline at end of file diff --git a/access-modifier-checker/src/it/disable-restrictions/caller/src/main/java/caller/Caller.java b/access-modifier-checker/src/it/disable-restrictions/caller/src/main/java/caller/Caller.java new file mode 100644 index 0000000..040d5f9 --- /dev/null +++ b/access-modifier-checker/src/it/disable-restrictions/caller/src/main/java/caller/Caller.java @@ -0,0 +1,36 @@ +package caller; + +import api.ApiWithRestrictedMethodAndField; +import api.RestrictedApi; +import org.kohsuke.accmod.restrictions.suppressions.SuppressRestrictedWarnings; + +public class Caller extends ApiWithRestrictedMethodAndField { // This is fine, ApiWithRestrictedMethodAndField itself is not restricted + + private RestrictedApi restrictedApi; + + @SuppressRestrictedWarnings(ApiWithRestrictedMethodAndField.class) + public Caller() { + ApiWithRestrictedMethodAndField.notReallyPublic(); // illegal but check disabled at the method level + } + + @SuppressRestrictedWarnings({RestrictedApi.class, ApiWithRestrictedMethodAndField.class}) + private void invalidFieldUse() { + restrictedApi.field = null; + super.field = null; + } + + @SuppressRestrictedWarnings(ApiWithRestrictedMethodAndField.class) + public void callerMethod() { + ApiWithRestrictedMethodAndField.notReallyPublic(); // illegal but check disabled at the method level + } + + @SuppressRestrictedWarnings(RestrictedApi.class) + public void methodWithRestrictedParameter(RestrictedApi api) { + api.doNotUse(); // illegal but check disabled at the method level + } + + @SuppressRestrictedWarnings(RestrictedApi.class) + public RestrictedApi getRestrictedApi() { + return new RestrictedApi(); // illegal but check disabled at the method level + } +} diff --git a/access-modifier-checker/src/it/disable-restrictions/caller/src/main/java/caller/CallerDisabledAtClassLevel.java b/access-modifier-checker/src/it/disable-restrictions/caller/src/main/java/caller/CallerDisabledAtClassLevel.java new file mode 100644 index 0000000..1314fbc --- /dev/null +++ b/access-modifier-checker/src/it/disable-restrictions/caller/src/main/java/caller/CallerDisabledAtClassLevel.java @@ -0,0 +1,32 @@ +package caller; + +import api.ApiWithRestrictedMethodAndField; +import api.RestrictedApi; +import org.kohsuke.accmod.restrictions.suppressions.SuppressRestrictedWarnings; + +@SuppressRestrictedWarnings( {ApiWithRestrictedMethodAndField.class, RestrictedApi.class}) +public class CallerDisabledAtClassLevel extends RestrictedApi { + + private RestrictedApi restrictedApi; + + public CallerDisabledAtClassLevel() { + ApiWithRestrictedMethodAndField.notReallyPublic(); // illegal but check disabled at the class level + } + + private void invalidFieldUse() { + restrictedApi.field = null; + super.field = null; + } + + public void callerMethod() { + ApiWithRestrictedMethodAndField.notReallyPublic(); // illegal but check disabled at the class level + } + + public void methodWithRestrictedParameter(RestrictedApi api) { + api.doNotUse(); // illegal but check disabled at the class level + } + + public RestrictedApi getRestrictedApi() { + return new RestrictedApi(); // illegal but check disabled at the class level + } +} \ No newline at end of file diff --git a/access-modifier-checker/src/it/disable-restrictions/caller/src/main/java/caller/RestrictedApiSubclass.java b/access-modifier-checker/src/it/disable-restrictions/caller/src/main/java/caller/RestrictedApiSubclass.java new file mode 100644 index 0000000..f03ff47 --- /dev/null +++ b/access-modifier-checker/src/it/disable-restrictions/caller/src/main/java/caller/RestrictedApiSubclass.java @@ -0,0 +1,8 @@ +package caller; + +import api.RestrictedApi; +import org.kohsuke.accmod.restrictions.suppressions.SuppressRestrictedWarnings; + +@SuppressRestrictedWarnings(RestrictedApi.class) +public class RestrictedApiSubclass extends RestrictedApi { +} diff --git a/access-modifier-checker/src/it/disable-restrictions/caller/src/main/java/caller/RestrictedInterfaceImplementation.java b/access-modifier-checker/src/it/disable-restrictions/caller/src/main/java/caller/RestrictedInterfaceImplementation.java new file mode 100644 index 0000000..d09afcc --- /dev/null +++ b/access-modifier-checker/src/it/disable-restrictions/caller/src/main/java/caller/RestrictedInterfaceImplementation.java @@ -0,0 +1,8 @@ +package caller; + +import api.RestrictedInterface; +import org.kohsuke.accmod.restrictions.suppressions.SuppressRestrictedWarnings; + +@SuppressRestrictedWarnings(RestrictedInterface.class) +public class RestrictedInterfaceImplementation implements RestrictedInterface { +} diff --git a/access-modifier-checker/src/it/disable-restrictions/invoker.properties b/access-modifier-checker/src/it/disable-restrictions/invoker.properties new file mode 100644 index 0000000..5c363f7 --- /dev/null +++ b/access-modifier-checker/src/it/disable-restrictions/invoker.properties @@ -0,0 +1,2 @@ +invoker.goals=clean package +invoker.buildResult = success diff --git a/access-modifier-checker/src/it/disable-restrictions/pom.xml b/access-modifier-checker/src/it/disable-restrictions/pom.xml new file mode 100644 index 0000000..c03dd1e --- /dev/null +++ b/access-modifier-checker/src/it/disable-restrictions/pom.xml @@ -0,0 +1,17 @@ + + + 4.0.0 + test + disable-restrictions + 1.0-SNAPSHOT + pom + + UTF-8 + 1.7 + 1.7 + + + api + caller + + \ No newline at end of file diff --git a/access-modifier-checker/src/main/java/org/kohsuke/accmod/impl/Checker.java b/access-modifier-checker/src/main/java/org/kohsuke/accmod/impl/Checker.java index bb504d1..42160c8 100644 --- a/access-modifier-checker/src/main/java/org/kohsuke/accmod/impl/Checker.java +++ b/access-modifier-checker/src/main/java/org/kohsuke/accmod/impl/Checker.java @@ -23,9 +23,13 @@ */ package org.kohsuke.accmod.impl; +import java.util.HashSet; +import java.util.Set; +import org.apache.maven.plugin.logging.Log; import org.kohsuke.accmod.AccessRestriction; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.impl.Restrictions.Parser; +import org.kohsuke.accmod.restrictions.suppressions.SuppressRestrictedWarnings; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; @@ -85,12 +89,17 @@ public class Checker { private final AccessRestrictionFactory factory; + private final Log log; - Checker(ClassLoader dependencies, ErrorListener errorListener, Properties properties) throws IOException { + private int line; + + Checker(ClassLoader dependencies, ErrorListener errorListener, Properties properties, + Log log) throws IOException { this.dependencies = dependencies; this.errorListener = errorListener; this.properties = properties; this.factory = new AccessRestrictionFactory(dependencies); + this.log = log; // load access restrictions loadAccessRestrictions(); @@ -228,132 +237,17 @@ public void checkClass(File clazz) throws IOException { FileInputStream in = new FileInputStream(clazz); try { ClassReader cr = new ClassReader(in); - cr.accept(new ClassVisitor(Opcodes.ASM5) { - private String className; - private String methodName,methodDesc; - private int line; - - @Override - public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - this.className = name; - - if (isSynthetic(access)) { - return; - } - - if (superName != null) { - for (Restrictions r : getRestrictions(superName)) { - r.usedAsSuperType(currentLocation, errorListener); - } - } - - if (interfaces != null) { - for (String intf : interfaces) { - for (Restrictions r : getRestrictions(intf)) { - r.usedAsInterface(currentLocation, errorListener); - } - } - } - } - - @Override - public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - this.methodName = name; - this.methodDesc = desc; - - if (isSynthetic(access)) { - return null; - } - - return new MethodVisitor(Opcodes.ASM5) { - @Override - public void visitLineNumber(int _line, Label start) { - line = _line; - } - - public void visitTypeInsn(int opcode, String type) { - switch (opcode) { - case Opcodes.NEW: - for (Restrictions r : getRestrictions(type)) { - r.instantiated(currentLocation, errorListener); - } - } - } - - @Override - public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { - for (Restrictions r : getRestrictions(owner + '.' + name + desc)) { - r.invoked(currentLocation, errorListener); - } - } - - @Override - public void visitFieldInsn(int opcode, String owner, String name, String desc) { - Iterable rs = getRestrictions(owner + '.' + name); - switch (opcode) { - case Opcodes.GETSTATIC: - case Opcodes.GETFIELD: - for (Restrictions r : rs) { - r.read(currentLocation, errorListener); - } - break; - case Opcodes.PUTSTATIC: - case Opcodes.PUTFIELD: - for (Restrictions r : rs) { - r.written(currentLocation, errorListener); - } - break; - } - super.visitFieldInsn(opcode, owner, name, desc); - } - }; - } - - - /** - * Constant that represents the current location. - */ - private final Location currentLocation = new Location() { - public String getClassName() { - return className.replace('/','.'); - } - - public String getMethodName() { - return methodName; - } - - public String getMethodDescriptor() { - return methodDesc; - } - - public int getLineNumber() { - return line; - } - - public String toString() { - return className+':'+line; - } - - public ClassLoader getDependencyClassLoader() { - return dependencies; - } - - @Override - public String getProperty(String key) { - return properties.getProperty(key); - } - }; - }, SKIP_FRAMES); + cr.accept(new RestrictedClassVisitor(), SKIP_FRAMES); } finally { in.close(); } } - private Iterable getRestrictions(String keyName) { + private Iterable getRestrictions(String keyName, Set skippedTypes) { List rs = new ArrayList<>(); Restrictions r = restrictions.get(keyName); - if (r != null) { - rs.add(r); + if (r != null && skippedTypes.isEmpty()) { + rs.add(r); // Don't get it from the cache if we have types that we want to skip } int idx = Integer.MAX_VALUE; while (true) { @@ -366,6 +260,10 @@ private Iterable getRestrictions(String keyName) { } idx = newIdx; keyName = keyName.substring(0, idx); + if(skippedTypes.contains(Type.getObjectType(keyName))) { + // We have hit a type that should be skipped - do not add it to the restrictions + break; + } r = restrictions.get(keyName); if (r != null) { Collection applicable = new ArrayList<>(); @@ -387,4 +285,201 @@ private Iterable getRestrictions(String keyName) { private static boolean isSynthetic(int access) { return (access & Opcodes.ACC_SYNTHETIC) != 0; } + + private class RestrictedClassVisitor extends ClassVisitor { + private String className; + private String methodName, methodDesc; + private String superName; + private String[] interfaces; + private RestrictedAnnotationVisitor annotationVisitor = new RestrictedAnnotationVisitor(); + + private Set getSkippedTypes() { + return annotationVisitor.getSkippedTypes(); + } + + public RestrictedClassVisitor() { + super(Opcodes.ASM5); + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + this.className = name; + + if (isSynthetic(access)) { + return; + } + + this.superName = superName; + this.interfaces = interfaces; + } + + @Override + public void visitEnd() { + // We need to do this in visitEnd so that we have parsed the annotations _before_ doing these checks + if (superName != null) { + for (Restrictions r : getRestrictions(superName, getSkippedTypes())) { + r.usedAsSuperType(currentLocation, errorListener); + } + } + if (interfaces != null) { + for (String intf : interfaces) { + for (Restrictions r : getRestrictions(intf, getSkippedTypes())) { + r.usedAsInterface(currentLocation, errorListener); + } + } + } + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + this.methodName = name; + this.methodDesc = desc; + + if (isSynthetic(access)) { + return null; + } + + return new RestrictedMethodVisitor(currentLocation, annotationVisitor.getSkippedTypes()); + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + return Type.getType(SuppressRestrictedWarnings.class).equals(Type.getType(desc)) + ? annotationVisitor + : super.visitAnnotation(desc, visible); } + + /** + * Constant that represents the current location. + */ + private final Location currentLocation = new Location() { + public String getClassName() { + return className.replace('/','.'); + } + + public String getMethodName() { + return methodName; + } + + public String getMethodDescriptor() { + return methodDesc; + } + + public int getLineNumber() { + return line; + } + + public String toString() { + return className+':'+line; + } + + public ClassLoader getDependencyClassLoader() { + return dependencies; + } + + @Override + public String getProperty(String key) { + return properties.getProperty(key); + } + }; + } + + private class RestrictedMethodVisitor extends MethodVisitor { + + private final Set skippedTypesFromParent; + private Location currentLocation; + private RestrictedAnnotationVisitor annotationVisitor = new RestrictedAnnotationVisitor(); + + private Set getSkippedTypes() { + Set allSkippedTypes = new HashSet<>(skippedTypesFromParent); + allSkippedTypes.addAll(annotationVisitor.getSkippedTypes()); + return allSkippedTypes; + } + + public RestrictedMethodVisitor(Location currentLocation, Set skippedTypes) { + super(Opcodes.ASM5); + log.debug(String.format("New method visitor at %s#%s", + currentLocation.getClassName(), currentLocation.getMethodName())); + this.currentLocation = currentLocation; + this.skippedTypesFromParent = skippedTypes; + } + + @Override + public void visitLineNumber(int _line, Label start) { + line = _line; + } + + public void visitTypeInsn(int opcode, String type) { + switch (opcode) { + case Opcodes.NEW: + for (Restrictions r : getRestrictions(type, getSkippedTypes())) { + r.instantiated(currentLocation, errorListener); + } + } + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + log.debug(String.format("Visiting method %s#%s", owner, name)); + for (Restrictions r : getRestrictions(owner + '.' + name + desc, getSkippedTypes())) { + r.invoked(currentLocation, errorListener); + } + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + log.debug(String.format("Visiting field '%s %s' in type %s", desc, name, owner)); + + Iterable rs = getRestrictions(owner + '.' + name, getSkippedTypes()); + switch (opcode) { + case Opcodes.GETSTATIC: + case Opcodes.GETFIELD: + for (Restrictions r : rs) { + r.read(currentLocation, errorListener); + } + break; + case Opcodes.PUTSTATIC: + case Opcodes.PUTFIELD: + for (Restrictions r : rs) { + r.written(currentLocation, errorListener); + } + break; + } + super.visitFieldInsn(opcode, owner, name, desc); + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + return Type.getType(SuppressRestrictedWarnings.class).equals(Type.getType(desc)) + ? annotationVisitor + : super.visitAnnotation(desc, visible); + } + } + + private class RestrictedAnnotationVisitor extends AnnotationVisitor { + + private Set skippedRestrictedClasses = new HashSet<>(); + + public RestrictedAnnotationVisitor() { + super(Opcodes.ASM5); + } + + public Set getSkippedTypes() { + return skippedRestrictedClasses; + } + + @Override + public AnnotationVisitor visitArray(String name) { + return new AnnotationVisitor(Opcodes.ASM5) { + + @Override + public void visit(String name, Object value) { + Type type = value instanceof Type ? (Type) value : Type.getType(value.getClass()); + log.debug(String.format("Skipping @%s class: %s", + Restricted.class.getSimpleName(), type.getClassName())); + skippedRestrictedClasses.add(type); + super.visit(name, value); + } + }; + } + } } diff --git a/access-modifier-checker/src/main/java/org/kohsuke/accmod/impl/EnforcerMojo.java b/access-modifier-checker/src/main/java/org/kohsuke/accmod/impl/EnforcerMojo.java index 55e5c83..ca769b4 100644 --- a/access-modifier-checker/src/main/java/org/kohsuke/accmod/impl/EnforcerMojo.java +++ b/access-modifier-checker/src/main/java/org/kohsuke/accmod/impl/EnforcerMojo.java @@ -84,7 +84,7 @@ public void onError(Throwable t, Location loc, String msg) { public void onWarning(Throwable t, Location loc, String msg) { getLog().warn(loc+" "+msg,t); } - }, properties != null ? properties : new Properties()); + }, properties != null ? properties : new Properties(), getLog()); {// if there's restriction list in the inspected module itself, load it as well InputStream self = null; diff --git a/access-modifier-suppressions/pom.xml b/access-modifier-suppressions/pom.xml new file mode 100644 index 0000000..aded61f --- /dev/null +++ b/access-modifier-suppressions/pom.xml @@ -0,0 +1,29 @@ + + + 4.0.0 + + access-modifier + org.kohsuke + 1.16-SNAPSHOT + + access-modifier-suppressions + + Suppression for Access Modifier annotations + This module allows you to enable suppressions for turning off warnings about Restricted APIs. + !!!WARNING!!! + Classes are marked as @Restricted for a reason and this module should not be used lightly! It implies that the + author does not intend for them to be used outside their defined scope and as such they may be + changed/modified/removed at any stage without warning. A simple upgrade of the dependency may break your module. Use + at your own risk. + You should try to not use @Restricted classes in the first place, but if you _must_ use them, this is a less-brutal + approach than just disabling the access-modifier-checker entirely + + + + + org.kohsuke + access-modifier-annotation + ${project.version} + + + diff --git a/access-modifier-suppressions/src/main/java/org/kohsuke/accmod/restrictions/suppressions/SuppressRestrictedWarnings.java b/access-modifier-suppressions/src/main/java/org/kohsuke/accmod/restrictions/suppressions/SuppressRestrictedWarnings.java new file mode 100644 index 0000000..b2f2224 --- /dev/null +++ b/access-modifier-suppressions/src/main/java/org/kohsuke/accmod/restrictions/suppressions/SuppressRestrictedWarnings.java @@ -0,0 +1,51 @@ +/* + * The MIT License + * + * Copyright (c) 2018, Steve Arch + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.kohsuke.accmod.restrictions.suppressions; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import org.kohsuke.accmod.Restricted; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + *

Indicates that certain classes annotated with {@link Restricted} annotations should be skipped during the + * access-modifier-check.

+ * + *

Warning! Classes are markes as {@link Restricted} for a reason! Do not use these suppressions lightly. + * Use at your own risk

+ * + * @author Steve Arch + */ +@Retention(RUNTIME) +@Documented +@Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE}) +public @interface SuppressRestrictedWarnings { + /** + * The classes that are marked as {@link Restricted} that should be skipped from the scan. + */ + Class[] value(); +} diff --git a/pom.xml b/pom.xml index a851974..2ca0fdc 100644 --- a/pom.xml +++ b/pom.xml @@ -18,6 +18,7 @@ access-modifier-annotation access-modifier-checker + access-modifier-suppressions