From c07254a1014963b448d43cc25cb9dccb53fc630e Mon Sep 17 00:00:00 2001 From: Mihails Volkovs Date: Tue, 23 Aug 2016 18:01:14 +0300 Subject: [PATCH] Supporting 3-rd party detectors (rules) - multiple messages.xml are read and merged - Java SPI mechanism for discovering 3-rd party detectors - default detector package is still needs to be used - huntbugs maven plugin is not affected (to configure it standard tag 'dependencies' inside tag 'plugin' could be used) - removed redundant public modifiers at interface nested classes (public by default) - plugin currently supports single package for detectors - introducing helper class to test detectors (to be reused) - custom detectors can be located in custom packages - minor refactoring - test coverage improved - added license agreement headers - demonstrating development of custom detector - sample project for custom detectors - detectors are automatically tested with sample code in test scope - sample project with HuntBugs maven plugin with custom detectors - production code in sample project is not annotated with @AssertWarning - sample project might be used for demonstration of project configuration without custom detectors as well --- .../huntbugs/registry/DetectorRegistry.java | 108 +++++++++++------- .../one/util/huntbugs/repo/Repository.java | 60 ++++++---- .../java/one/util/huntbugs/spi/DataTests.java | 78 +++++++++++++ .../one/util/huntbugs/spi/HuntBugsPlugin.java | 30 +++++ .../one/util/huntbugs/warning/Messages.java | 32 +++++- .../test/java/one/util/huntbugs/DataTest.java | 31 +---- .../registry/DetectorRegistryTest.java | 68 +++++++++++ pom.xml | 2 + sample-huntbugs-custom-detector/pom.xml | 31 +++++ .../huntbugs/sample/SampleHuntBugsPlugin.java | 35 ++++++ .../sample/detect/SampleCustomDetector.java | 39 +++++++ .../one.util.huntbugs.spi.HuntBugsPlugin | 1 + .../src/main/resources/huntbugs/messages.xml | 13 +++ .../one/util/huntbugs/sample/DataTest.java | 31 +++++ .../testdata/TestSampleCustomDetector.java | 43 +++++++ sample-project/pom.xml | 50 ++++++++ .../huntbugs/sample/MyProductionCode.java | 30 +++++ .../huntbugs/sample/MyProductionCodeTest.java | 40 +++++++ 18 files changed, 628 insertions(+), 94 deletions(-) create mode 100644 huntbugs/src/main/java/one/util/huntbugs/spi/DataTests.java create mode 100644 huntbugs/src/main/java/one/util/huntbugs/spi/HuntBugsPlugin.java create mode 100644 huntbugs/src/test/java/one/util/huntbugs/registry/DetectorRegistryTest.java create mode 100644 sample-huntbugs-custom-detector/pom.xml create mode 100644 sample-huntbugs-custom-detector/src/main/java/one/util/huntbugs/sample/SampleHuntBugsPlugin.java create mode 100644 sample-huntbugs-custom-detector/src/main/java/one/util/huntbugs/sample/detect/SampleCustomDetector.java create mode 100644 sample-huntbugs-custom-detector/src/main/resources/META-INF/services/one.util.huntbugs.spi.HuntBugsPlugin create mode 100644 sample-huntbugs-custom-detector/src/main/resources/huntbugs/messages.xml create mode 100644 sample-huntbugs-custom-detector/src/test/java/one/util/huntbugs/sample/DataTest.java create mode 100644 sample-huntbugs-custom-detector/src/test/java/one/util/huntbugs/sample/testdata/TestSampleCustomDetector.java create mode 100644 sample-project/pom.xml create mode 100644 sample-project/src/main/java/one/util/huntbugs/sample/MyProductionCode.java create mode 100644 sample-project/src/test/java/one/util/huntbugs/sample/MyProductionCodeTest.java diff --git a/huntbugs/src/main/java/one/util/huntbugs/registry/DetectorRegistry.java b/huntbugs/src/main/java/one/util/huntbugs/registry/DetectorRegistry.java index 82720a03..0116fd93 100644 --- a/huntbugs/src/main/java/one/util/huntbugs/registry/DetectorRegistry.java +++ b/huntbugs/src/main/java/one/util/huntbugs/registry/DetectorRegistry.java @@ -15,22 +15,6 @@ */ package one.util.huntbugs.registry; -import java.io.PrintStream; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.Deque; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; - import com.strobel.assembler.ir.Instruction; import com.strobel.assembler.ir.OpCode; import com.strobel.assembler.metadata.MetadataSystem; @@ -46,7 +30,6 @@ import com.strobel.decompiler.ast.Block; import com.strobel.decompiler.ast.Lambda; import com.strobel.decompiler.ast.Node; - import one.util.huntbugs.analysis.Context; import one.util.huntbugs.analysis.ErrorMessage; import one.util.huntbugs.db.FieldStats; @@ -57,12 +40,30 @@ import one.util.huntbugs.registry.anno.WarningDefinition; import one.util.huntbugs.repo.Repository; import one.util.huntbugs.repo.RepositoryVisitor; +import one.util.huntbugs.spi.HuntBugsPlugin; import one.util.huntbugs.util.NodeChain; import one.util.huntbugs.util.Nodes; import one.util.huntbugs.warning.Messages.Message; import one.util.huntbugs.warning.Role.NumberRole; import one.util.huntbugs.warning.WarningType; +import java.io.PrintStream; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Deque; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + /** * @author Tagir Valeev * @@ -102,9 +103,8 @@ public DetectorRegistry(Context ctx) { } private Map createWarningMap(Stream stream) { - Map systemWarnings = stream.map(ctx.getOptions().getRule()::adjust).collect( + return stream.map(ctx.getOptions().getRule()::adjust).collect( Collectors.toMap(WarningType::getName, Function.identity())); - return systemWarnings; } private List getDefinitions(Class clazz) { @@ -145,26 +145,19 @@ private Detector createDetector(Class clazz, Map wts) th } void init() { - Repository repo = Repository.createSelfRepository(); + + // adding HuntBugs built-in detectors + Repository selfRepo = Repository.createSelfRepository(); String pkg = DETECTORS_PACKAGE.replace('.', '/'); - repo.visit(pkg, new RepositoryVisitor() { - @Override - public boolean visitPackage(String packageName) { - return packageName.equals(pkg); - } + selfRepo.visit(pkg, new DetectorVisitor(pkg, false)); + + // adding HuntBugs 3-rd party detectors if any + for (HuntBugsPlugin huntBugsPlugin : ServiceLoader.load(HuntBugsPlugin.class)) { + Repository pluginRepository = Repository.createPluginRepository(huntBugsPlugin); + String pluginDetectorPackage = huntBugsPlugin.detectorPackage().replace('.', '/'); + pluginRepository.visit(pluginDetectorPackage, new DetectorVisitor(pluginDetectorPackage, true)); + } - @Override - public void visitClass(String className) { - String name = className.replace('/', '.'); - try { - ctx.incStat("Detectors.Total"); - if (addDetector(MetadataSystem.class.getClassLoader().loadClass(name))) - ctx.incStat("Detectors"); - } catch (ClassNotFoundException e) { - ctx.addError(new ErrorMessage(name, null, null, null, -1, e)); - } - } - }); } private void visitChildren(Node node, NodeChain parents, List list, MethodData mdata) { @@ -310,7 +303,7 @@ private void sortConstructors(List ctors) { if(body != null) { for(Instruction instr : body.getInstructions()) { if(instr.getOpCode() == OpCode.INVOKESPECIAL) { - MethodReference mr = (MethodReference)instr.getOperand(0); + MethodReference mr = instr.getOperand(0); if(mr.getDeclaringType().isEquivalentTo(ctor.getDeclaringType()) && mr.isConstructor()) { deps.put(ctor, mr.resolve()); } @@ -357,9 +350,9 @@ public void reportWarningTypes(PrintStream out) { List result = new ArrayList<>(); String arrow = " --> "; - typeToDetector.forEach((wt, detector) -> { - result.add(wt.getCategory() + arrow + wt.getName() + arrow + detector); - }); + typeToDetector.forEach((wt, detector) -> + result.add(wt.getCategory() + arrow + wt.getName() + arrow + detector) + ); printTree(out, result, arrow); out.println("Total types: " + typeToDetector.size()); } @@ -400,4 +393,37 @@ public WarningType getWarningType(String typeName) { public Stream warningTypes() { return typeToDetector.keySet().stream(); } + + private class DetectorVisitor implements RepositoryVisitor { + + private String packageToVisit; + + private boolean external; + + DetectorVisitor(String packageToVisit, boolean external) { + this.packageToVisit = packageToVisit; + this.external = external; + } + + @Override + public boolean visitPackage(String packageName) { + return packageName.equals(packageToVisit); + } + + @Override + public void visitClass(String className) { + String name = className.replace('/', '.'); + try { + ctx.incStat("Detectors.Total"); + if (addDetector(MetadataSystem.class.getClassLoader().loadClass(name))) { + ctx.incStat("Detectors"); + if (external) { + ctx.incStat("Detectors from HuntBugs plugins"); + } + } + } catch (ClassNotFoundException e) { + ctx.addError(new ErrorMessage(name, null, null, null, -1, e)); + } + } + } } diff --git a/huntbugs/src/main/java/one/util/huntbugs/repo/Repository.java b/huntbugs/src/main/java/one/util/huntbugs/repo/Repository.java index 257614ab..87d539f7 100644 --- a/huntbugs/src/main/java/one/util/huntbugs/repo/Repository.java +++ b/huntbugs/src/main/java/one/util/huntbugs/repo/Repository.java @@ -15,6 +15,9 @@ */ package one.util.huntbugs.repo; +import com.strobel.assembler.metadata.ITypeLoader; +import one.util.huntbugs.spi.HuntBugsPlugin; + import java.io.IOException; import java.net.URISyntaxException; import java.net.URL; @@ -30,7 +33,7 @@ import java.util.Set; import java.util.jar.JarFile; -import com.strobel.assembler.metadata.ITypeLoader; +import static java.lang.String.format; /** * @author Tagir Valeev @@ -41,7 +44,7 @@ public interface Repository { void visit(String rootPackage, RepositoryVisitor visitor); - public static Repository createSelfRepository() { + static Repository createSelfRepository() { List repos = new ArrayList<>(); Set paths = new HashSet<>(); try { @@ -59,33 +62,50 @@ public static Repository createSelfRepository() { } catch (IOException e) { throw new RuntimeException(e); } - CodeSource codeSource = CompositeRepository.class.getProtectionDomain().getCodeSource(); - URL url = codeSource == null ? null : codeSource.getLocation(); - if(url != null) { - try { - Path path = Paths.get(url.toURI()); - if(paths.add(path)) { - if(Files.isDirectory(path)) - repos.add(new DirRepository(path)); - else if(Files.isRegularFile(path)) - repos.add(new JarRepository(new JarFile(path.toFile()))); + + repos.add(createDetectorsRepo(CompositeRepository.class, "HuntBugs Detectors", paths)); + + return new CompositeRepository(repos); + } + + static Repository createPluginRepository(HuntBugsPlugin huntBugsPlugin) { + Class pluginClass = huntBugsPlugin.getClass(); + String pluginName = huntBugsPlugin.name(); + return createDetectorsRepo(pluginClass, pluginName, new HashSet<>()); + } + + static Repository createDetectorsRepo(Class clazz, String pluginName, Set paths) { + CodeSource codeSource = clazz.getProtectionDomain().getCodeSource(); + if (codeSource == null) { + throw new RuntimeException(format("Initializing plugin '%s' could not get code source for class %s", pluginName, clazz.getName())); + } + + URL url = codeSource.getLocation(); + try { + Path path = Paths.get(url.toURI()); + if(paths.add(path)) { + if(Files.isDirectory(path)) { + return new DirRepository(path); + } else { + return new JarRepository(new JarFile(path.toFile())); } - } catch (URISyntaxException | FileSystemNotFoundException | IllegalArgumentException - | IOException | UnsupportedOperationException e) { - // ignore + } else { + return createNullRepository(); } + } catch (URISyntaxException | FileSystemNotFoundException | IllegalArgumentException + | IOException | UnsupportedOperationException e) { + String errorMessage = format("Error creating detector repository for plugin '%s'", pluginName); + throw new RuntimeException(errorMessage, e); } - CompositeRepository repo = new CompositeRepository(repos); - return repo; } - - public static Repository createNullRepository() { + + static Repository createNullRepository() { return new Repository() { @Override public void visit(String rootPackage, RepositoryVisitor visitor) { // nothing to do } - + @Override public ITypeLoader createTypeLoader() { return (internalName, buffer) -> false; diff --git a/huntbugs/src/main/java/one/util/huntbugs/spi/DataTests.java b/huntbugs/src/main/java/one/util/huntbugs/spi/DataTests.java new file mode 100644 index 00000000..81abe208 --- /dev/null +++ b/huntbugs/src/main/java/one/util/huntbugs/spi/DataTests.java @@ -0,0 +1,78 @@ +/* + * Copyright 2016 HuntBugs contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package one.util.huntbugs.spi; + +import one.util.huntbugs.analysis.AnalysisOptions; +import one.util.huntbugs.analysis.Context; +import one.util.huntbugs.analysis.ErrorMessage; +import one.util.huntbugs.analysis.HuntBugsResult; +import one.util.huntbugs.input.XmlReportReader; +import one.util.huntbugs.output.Reports; +import one.util.huntbugs.repo.CompositeRepository; +import one.util.huntbugs.repo.Repository; + +import java.io.PrintStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.ServiceLoader; +import java.util.stream.Collectors; + +import static java.lang.String.format; + +/** + * @author Tagir Valeev + * + */ +public abstract class DataTests { + + public static void test(String packageToAnalyze) throws Exception { + + // creating built-in and plugins repositories + List repositories = new ArrayList<>(); + repositories.add(Repository.createSelfRepository()); + for (HuntBugsPlugin huntBugsPlugin : ServiceLoader.load(HuntBugsPlugin.class)) { + repositories.add(Repository.createPluginRepository(huntBugsPlugin)); + } + CompositeRepository repository = new CompositeRepository(repositories); + + Context ctx = new Context(repository, new AnalysisOptions()); + ctx.analyzePackage(packageToAnalyze); + ctx.reportStats(System.out); + ctx.reportErrors(System.err); + ctx.reportWarnings(new PrintStream("target/testWarnings.out")); + Path xmlReport = Paths.get("target/testWarnings.xml"); + Reports.write(xmlReport, Paths.get("target/testWarnings.html"), ctx); + System.out.println("Analyzed " + ctx.getClassesCount() + " classes"); + if (ctx.getErrorCount() > 0) { + List errorMessages = ctx.errors().collect(Collectors.toList()); + throw new AssertionError(format("Analysis finished with %s errors: %s", ctx.getErrorCount(), errorMessages)); + } + HuntBugsResult result = XmlReportReader.read(ctx, xmlReport); + Path rereadReport = Paths.get("target/testWarnings_reread.xml"); + Reports.write(rereadReport, null, result); + byte[] expectedReport = Files.readAllBytes(xmlReport); + byte[] actualReport = Files.readAllBytes(rereadReport); + if (!Arrays.equals(expectedReport, actualReport)) { + String errorMessage = format("Expected: \n%s\n\nActual: \n%s\n\n", new String(expectedReport), new String(actualReport)); + throw new AssertionError(errorMessage); + } + } + +} diff --git a/huntbugs/src/main/java/one/util/huntbugs/spi/HuntBugsPlugin.java b/huntbugs/src/main/java/one/util/huntbugs/spi/HuntBugsPlugin.java new file mode 100644 index 00000000..2fd2fb73 --- /dev/null +++ b/huntbugs/src/main/java/one/util/huntbugs/spi/HuntBugsPlugin.java @@ -0,0 +1,30 @@ +/* + * Copyright 2016 HuntBugs contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package one.util.huntbugs.spi; + +/** + * This is extension point for 3-rd party detector providers. + * + * @author Mihails Volkovs + * + */ +public interface HuntBugsPlugin { + + String name(); + + String detectorPackage(); + +} diff --git a/huntbugs/src/main/java/one/util/huntbugs/warning/Messages.java b/huntbugs/src/main/java/one/util/huntbugs/warning/Messages.java index 0b1e7d2f..abd2f0e5 100644 --- a/huntbugs/src/main/java/one/util/huntbugs/warning/Messages.java +++ b/huntbugs/src/main/java/one/util/huntbugs/warning/Messages.java @@ -18,6 +18,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; +import java.net.URL; +import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -81,17 +83,35 @@ public Message getMessagesForType(String warningType) { } public static Messages load() { - Document dom; - try(InputStream is = Messages.class.getClassLoader().getResourceAsStream(MESSAGES_XML)) { + Map allMessages = new HashMap<>(); + + try { + // 3-rd party detectors could provide their own messages + Enumeration messageUrls = Messages.class.getClassLoader().getResources(MESSAGES_XML); + while (messageUrls.hasMoreElements()) { + URL messageUrl = messageUrls.nextElement(); + allMessages.putAll(toMap(readMessages(messageUrl))); + } + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + + return new Messages(allMessages); + } + + private static Document readMessages(URL messageUrl) { + try (InputStream is = messageUrl.openStream()) { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); - dom = builder.parse(is); - } - catch(IOException ex) { + return builder.parse(is); + } catch (IOException ex) { throw new UncheckedIOException(ex); } catch (ParserConfigurationException | SAXException e) { throw new RuntimeException(e); } + } + + private static Map toMap(Document dom) { Map map = new HashMap<>(); Element element = dom.getDocumentElement(); Element warnings = Xml.getChild(element, "WarningList"); @@ -112,6 +132,6 @@ public static Messages load() { node = node.getNextSibling(); } } - return new Messages(map); + return map; } } diff --git a/huntbugs/src/test/java/one/util/huntbugs/DataTest.java b/huntbugs/src/test/java/one/util/huntbugs/DataTest.java index 3a97b014..5db37d9c 100644 --- a/huntbugs/src/test/java/one/util/huntbugs/DataTest.java +++ b/huntbugs/src/test/java/one/util/huntbugs/DataTest.java @@ -15,40 +15,17 @@ */ package one.util.huntbugs; -import java.io.PrintStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import one.util.huntbugs.analysis.AnalysisOptions; -import one.util.huntbugs.analysis.Context; -import one.util.huntbugs.analysis.HuntBugsResult; -import one.util.huntbugs.input.XmlReportReader; -import one.util.huntbugs.output.Reports; -import one.util.huntbugs.repo.Repository; -import static org.junit.Assert.*; - +import one.util.huntbugs.spi.DataTests; import org.junit.Test; /** * @author Tagir Valeev - * */ public class DataTest { + @Test public void test() throws Exception { - Context ctx = new Context(Repository.createSelfRepository(), new AnalysisOptions()); - ctx.analyzePackage("one/util/huntbugs/testdata"); - ctx.reportStats(System.out); - ctx.reportErrors(System.err); - ctx.reportWarnings(new PrintStream("target/testWarnings.out")); - Path xmlReport = Paths.get("target/testWarnings.xml"); - Reports.write(xmlReport, Paths.get("target/testWarnings.html"), ctx); - System.out.println("Analyzed "+ctx.getClassesCount()+" classes"); - if(ctx.getErrorCount() > 0) - fail("Analysis finished with "+ctx.getErrorCount()+" errors"); - HuntBugsResult result = XmlReportReader.read(ctx, xmlReport); - Path rereadReport = Paths.get("target/testWarnings_reread.xml"); - Reports.write(rereadReport, null, result); - assertArrayEquals(Files.readAllBytes(xmlReport), Files.readAllBytes(rereadReport)); + DataTests.test("one/util/huntbugs/testdata"); } + } diff --git a/huntbugs/src/test/java/one/util/huntbugs/registry/DetectorRegistryTest.java b/huntbugs/src/test/java/one/util/huntbugs/registry/DetectorRegistryTest.java new file mode 100644 index 00000000..9c8d0126 --- /dev/null +++ b/huntbugs/src/test/java/one/util/huntbugs/registry/DetectorRegistryTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 2016 HuntBugs contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package one.util.huntbugs.registry; + +import one.util.huntbugs.analysis.AnalysisOptions; +import one.util.huntbugs.analysis.Context; +import one.util.huntbugs.registry.anno.WarningDefinition; +import one.util.huntbugs.repo.Repository; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Mihails Volkovs + */ +public class DetectorRegistryTest { + + private Context context; + + private DetectorRegistry detectorRegistry; + + @Before + public void setUp() { + context = new Context(Repository.createNullRepository(), new AnalysisOptions()); + detectorRegistry = new DetectorRegistry(context); + } + + @Test + public void addDetector() { + final long WARNINGS = getWarnings(); + assertTrue(detectorRegistry.addDetector(TestDetector.class)); + assertEquals(WARNINGS + 2, getWarnings()); + } + + @Test + public void addFakeDetector() { + final long WARNINGS = getWarnings(); + assertFalse(detectorRegistry.addDetector(DetectorRegistryTest.class)); + assertEquals(WARNINGS, getWarnings()); + } + + private long getWarnings() { + return context.getStat("WarningTypes.Total"); + } + + @WarningDefinition(category="DetectorRegistryTest", name="DetectorRegistryTest", maxScore=80) + @WarningDefinition(category="DetectorRegistryTest", name="DetectorRegistryTest", maxScore=80) + private static class TestDetector { + + } + +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 63f4ddc2..368db529 100644 --- a/pom.xml +++ b/pom.xml @@ -22,6 +22,8 @@ huntbugs huntbugs-ant-plugin huntbugs-maven-plugin + sample-huntbugs-custom-detector + sample-project diff --git a/sample-huntbugs-custom-detector/pom.xml b/sample-huntbugs-custom-detector/pom.xml new file mode 100644 index 00000000..06ec29c8 --- /dev/null +++ b/sample-huntbugs-custom-detector/pom.xml @@ -0,0 +1,31 @@ + + 4.0.0 + + + one.util + huntbugs-all + 0.0.11-SNAPSHOT + + sample-huntbugs-custom-detector + jar + + sample-huntbugs-custom-detector + Demonstration of how HuntBugs custom detectors could be implemented + + + + one.util + huntbugs + ${project.version} + provided + + + junit + junit + 4.12 + test + + + + diff --git a/sample-huntbugs-custom-detector/src/main/java/one/util/huntbugs/sample/SampleHuntBugsPlugin.java b/sample-huntbugs-custom-detector/src/main/java/one/util/huntbugs/sample/SampleHuntBugsPlugin.java new file mode 100644 index 00000000..e8aab5bf --- /dev/null +++ b/sample-huntbugs-custom-detector/src/main/java/one/util/huntbugs/sample/SampleHuntBugsPlugin.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016 HuntBugs contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package one.util.huntbugs.sample; + +import one.util.huntbugs.spi.HuntBugsPlugin; + +/** + * @author Mihails Volkovs + */ +public class SampleHuntBugsPlugin implements HuntBugsPlugin { + + @Override + public String name() { + return "Sample Detectors"; + } + + @Override + public String detectorPackage() { + return "one.util.huntbugs.sample.detect"; + } + +} diff --git a/sample-huntbugs-custom-detector/src/main/java/one/util/huntbugs/sample/detect/SampleCustomDetector.java b/sample-huntbugs-custom-detector/src/main/java/one/util/huntbugs/sample/detect/SampleCustomDetector.java new file mode 100644 index 00000000..ee00d246 --- /dev/null +++ b/sample-huntbugs-custom-detector/src/main/java/one/util/huntbugs/sample/detect/SampleCustomDetector.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 HuntBugs contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package one.util.huntbugs.sample.detect; + +import com.strobel.assembler.metadata.MethodDefinition; +import com.strobel.assembler.metadata.TypeDefinition; +import com.strobel.assembler.metadata.TypeReference; +import one.util.huntbugs.registry.MethodContext; +import one.util.huntbugs.registry.anno.MethodVisitor; +import one.util.huntbugs.registry.anno.WarningDefinition; +import one.util.huntbugs.util.Types; + +/** + * @author Mihails Volkovs + */ +@WarningDefinition(category="Demo", name="SampleCustomDetector", maxScore=80) +public class SampleCustomDetector { + + @MethodVisitor + public void visit(MethodContext mc, MethodDefinition md, TypeDefinition td) { + TypeReference returnType = md.getReturnType(); + if (Types.isCollection(returnType)) { + mc.report("SampleCustomDetector", 5); + } + } +} diff --git a/sample-huntbugs-custom-detector/src/main/resources/META-INF/services/one.util.huntbugs.spi.HuntBugsPlugin b/sample-huntbugs-custom-detector/src/main/resources/META-INF/services/one.util.huntbugs.spi.HuntBugsPlugin new file mode 100644 index 00000000..f096d15f --- /dev/null +++ b/sample-huntbugs-custom-detector/src/main/resources/META-INF/services/one.util.huntbugs.spi.HuntBugsPlugin @@ -0,0 +1 @@ +one.util.huntbugs.sample.SampleHuntBugsPlugin diff --git a/sample-huntbugs-custom-detector/src/main/resources/huntbugs/messages.xml b/sample-huntbugs-custom-detector/src/main/resources/huntbugs/messages.xml new file mode 100644 index 00000000..6f701c68 --- /dev/null +++ b/sample-huntbugs-custom-detector/src/main/resources/huntbugs/messages.xml @@ -0,0 +1,13 @@ + + + + + For some reason you can't return instance of java.util.Collection + For some reason you can't return instance of java.util.Collection + + + + diff --git a/sample-huntbugs-custom-detector/src/test/java/one/util/huntbugs/sample/DataTest.java b/sample-huntbugs-custom-detector/src/test/java/one/util/huntbugs/sample/DataTest.java new file mode 100644 index 00000000..7c19fc72 --- /dev/null +++ b/sample-huntbugs-custom-detector/src/test/java/one/util/huntbugs/sample/DataTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016 HuntBugs contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package one.util.huntbugs.sample; + +import one.util.huntbugs.spi.DataTests; +import org.junit.Test; + +/** + * @author Mihails Volkovs + */ +public class DataTest { + + @Test + public void test() throws Exception { + DataTests.test("one/util/huntbugs/sample/testdata"); + } + +} diff --git a/sample-huntbugs-custom-detector/src/test/java/one/util/huntbugs/sample/testdata/TestSampleCustomDetector.java b/sample-huntbugs-custom-detector/src/test/java/one/util/huntbugs/sample/testdata/TestSampleCustomDetector.java new file mode 100644 index 00000000..dab288ca --- /dev/null +++ b/sample-huntbugs-custom-detector/src/test/java/one/util/huntbugs/sample/testdata/TestSampleCustomDetector.java @@ -0,0 +1,43 @@ +/* + * Copyright 2016 HuntBugs contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package one.util.huntbugs.sample.testdata; + +import one.util.huntbugs.registry.anno.AssertNoWarning; +import one.util.huntbugs.registry.anno.AssertWarning; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Mihails Volkovs + */ +public class TestSampleCustomDetector { + + private static final String DEMO_CUSTOM_DETECTOR = "SampleCustomDetector"; + + @AssertWarning(DEMO_CUSTOM_DETECTOR) + public Collection getCollection() { + return new ArrayList(); + } + + @AssertNoWarning(DEMO_CUSTOM_DETECTOR) + public Map getMap() { + return new HashMap(); + } + +} diff --git a/sample-project/pom.xml b/sample-project/pom.xml new file mode 100644 index 00000000..600fdb59 --- /dev/null +++ b/sample-project/pom.xml @@ -0,0 +1,50 @@ + + 4.0.0 + + + one.util + huntbugs-all + 0.0.11-SNAPSHOT + + sample-project + jar + + sample-project + Sample project to demonstrate HuntBugs configuration and features + + + + junit + junit + 4.12 + test + + + + + + + one.util + huntbugs-maven-plugin + ${project.version} + + + one.util + sample-huntbugs-custom-detector + ${project.version} + + + + + huntbugs-check + prepare-package + + huntbugs + + + + + + + diff --git a/sample-project/src/main/java/one/util/huntbugs/sample/MyProductionCode.java b/sample-project/src/main/java/one/util/huntbugs/sample/MyProductionCode.java new file mode 100644 index 00000000..2b6b3338 --- /dev/null +++ b/sample-project/src/main/java/one/util/huntbugs/sample/MyProductionCode.java @@ -0,0 +1,30 @@ +/* + * Copyright 2016 HuntBugs contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package one.util.huntbugs.sample; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Mihails Volkovs + */ +public class MyProductionCode { + + public List bugsAlive() { + return new ArrayList<>(); + } + +} diff --git a/sample-project/src/test/java/one/util/huntbugs/sample/MyProductionCodeTest.java b/sample-project/src/test/java/one/util/huntbugs/sample/MyProductionCodeTest.java new file mode 100644 index 00000000..02e6deca --- /dev/null +++ b/sample-project/src/test/java/one/util/huntbugs/sample/MyProductionCodeTest.java @@ -0,0 +1,40 @@ +/* + * Copyright 2016 HuntBugs contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package one.util.huntbugs.sample; + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +/** + * @author Mihails Volkovs + */ +public class MyProductionCodeTest { + + private MyProductionCode productionCode; + + @Before + public void setUp() { + productionCode = new MyProductionCode(); + } + + @Test + public void bugsAlive() { + assertTrue(productionCode.bugsAlive().isEmpty()); + } + +} \ No newline at end of file