Skip to content
This repository was archived by the owner on Sep 17, 2020. It is now read-only.

Commit 472aa63

Browse files
authored
Merge pull request #20 from volkovs/custom-detectors-demo
Custom detectors support + demo
2 parents 7648117 + c07254a commit 472aa63

File tree

18 files changed

+628
-94
lines changed

18 files changed

+628
-94
lines changed

huntbugs/src/main/java/one/util/huntbugs/registry/DetectorRegistry.java

Lines changed: 67 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,6 @@
1515
*/
1616
package one.util.huntbugs.registry;
1717

18-
import java.io.PrintStream;
19-
import java.util.ArrayDeque;
20-
import java.util.ArrayList;
21-
import java.util.Arrays;
22-
import java.util.Collections;
23-
import java.util.Comparator;
24-
import java.util.Deque;
25-
import java.util.HashMap;
26-
import java.util.LinkedHashSet;
27-
import java.util.List;
28-
import java.util.Map;
29-
import java.util.Set;
30-
import java.util.function.Function;
31-
import java.util.stream.Collectors;
32-
import java.util.stream.Stream;
33-
3418
import com.strobel.assembler.ir.Instruction;
3519
import com.strobel.assembler.ir.OpCode;
3620
import com.strobel.assembler.metadata.MetadataSystem;
@@ -46,7 +30,6 @@
4630
import com.strobel.decompiler.ast.Block;
4731
import com.strobel.decompiler.ast.Lambda;
4832
import com.strobel.decompiler.ast.Node;
49-
5033
import one.util.huntbugs.analysis.Context;
5134
import one.util.huntbugs.analysis.ErrorMessage;
5235
import one.util.huntbugs.db.FieldStats;
@@ -57,12 +40,30 @@
5740
import one.util.huntbugs.registry.anno.WarningDefinition;
5841
import one.util.huntbugs.repo.Repository;
5942
import one.util.huntbugs.repo.RepositoryVisitor;
43+
import one.util.huntbugs.spi.HuntBugsPlugin;
6044
import one.util.huntbugs.util.NodeChain;
6145
import one.util.huntbugs.util.Nodes;
6246
import one.util.huntbugs.warning.Messages.Message;
6347
import one.util.huntbugs.warning.Role.NumberRole;
6448
import one.util.huntbugs.warning.WarningType;
6549

50+
import java.io.PrintStream;
51+
import java.util.ArrayDeque;
52+
import java.util.ArrayList;
53+
import java.util.Arrays;
54+
import java.util.Collections;
55+
import java.util.Comparator;
56+
import java.util.Deque;
57+
import java.util.HashMap;
58+
import java.util.LinkedHashSet;
59+
import java.util.List;
60+
import java.util.Map;
61+
import java.util.ServiceLoader;
62+
import java.util.Set;
63+
import java.util.function.Function;
64+
import java.util.stream.Collectors;
65+
import java.util.stream.Stream;
66+
6667
/**
6768
* @author Tagir Valeev
6869
*
@@ -102,9 +103,8 @@ public DetectorRegistry(Context ctx) {
102103
}
103104

104105
private Map<String, WarningType> createWarningMap(Stream<WarningType> stream) {
105-
Map<String, WarningType> systemWarnings = stream.map(ctx.getOptions().getRule()::adjust).collect(
106+
return stream.map(ctx.getOptions().getRule()::adjust).collect(
106107
Collectors.toMap(WarningType::getName, Function.identity()));
107-
return systemWarnings;
108108
}
109109

110110
private List<WarningDefinition> getDefinitions(Class<?> clazz) {
@@ -145,26 +145,19 @@ private Detector createDetector(Class<?> clazz, Map<String, WarningType> wts) th
145145
}
146146

147147
void init() {
148-
Repository repo = Repository.createSelfRepository();
148+
149+
// adding HuntBugs built-in detectors
150+
Repository selfRepo = Repository.createSelfRepository();
149151
String pkg = DETECTORS_PACKAGE.replace('.', '/');
150-
repo.visit(pkg, new RepositoryVisitor() {
151-
@Override
152-
public boolean visitPackage(String packageName) {
153-
return packageName.equals(pkg);
154-
}
152+
selfRepo.visit(pkg, new DetectorVisitor(pkg, false));
153+
154+
// adding HuntBugs 3-rd party detectors if any
155+
for (HuntBugsPlugin huntBugsPlugin : ServiceLoader.load(HuntBugsPlugin.class)) {
156+
Repository pluginRepository = Repository.createPluginRepository(huntBugsPlugin);
157+
String pluginDetectorPackage = huntBugsPlugin.detectorPackage().replace('.', '/');
158+
pluginRepository.visit(pluginDetectorPackage, new DetectorVisitor(pluginDetectorPackage, true));
159+
}
155160

156-
@Override
157-
public void visitClass(String className) {
158-
String name = className.replace('/', '.');
159-
try {
160-
ctx.incStat("Detectors.Total");
161-
if (addDetector(MetadataSystem.class.getClassLoader().loadClass(name)))
162-
ctx.incStat("Detectors");
163-
} catch (ClassNotFoundException e) {
164-
ctx.addError(new ErrorMessage(name, null, null, null, -1, e));
165-
}
166-
}
167-
});
168161
}
169162

170163
private void visitChildren(Node node, NodeChain parents, List<MethodContext> list, MethodData mdata) {
@@ -310,7 +303,7 @@ private void sortConstructors(List<MethodDefinition> ctors) {
310303
if(body != null) {
311304
for(Instruction instr : body.getInstructions()) {
312305
if(instr.getOpCode() == OpCode.INVOKESPECIAL) {
313-
MethodReference mr = (MethodReference)instr.getOperand(0);
306+
MethodReference mr = instr.getOperand(0);
314307
if(mr.getDeclaringType().isEquivalentTo(ctor.getDeclaringType()) && mr.isConstructor()) {
315308
deps.put(ctor, mr.resolve());
316309
}
@@ -357,9 +350,9 @@ public void reportWarningTypes(PrintStream out) {
357350
List<String> result = new ArrayList<>();
358351

359352
String arrow = " --> ";
360-
typeToDetector.forEach((wt, detector) -> {
361-
result.add(wt.getCategory() + arrow + wt.getName() + arrow + detector);
362-
});
353+
typeToDetector.forEach((wt, detector) ->
354+
result.add(wt.getCategory() + arrow + wt.getName() + arrow + detector)
355+
);
363356
printTree(out, result, arrow);
364357
out.println("Total types: " + typeToDetector.size());
365358
}
@@ -400,4 +393,37 @@ public WarningType getWarningType(String typeName) {
400393
public Stream<WarningType> warningTypes() {
401394
return typeToDetector.keySet().stream();
402395
}
396+
397+
private class DetectorVisitor implements RepositoryVisitor {
398+
399+
private String packageToVisit;
400+
401+
private boolean external;
402+
403+
DetectorVisitor(String packageToVisit, boolean external) {
404+
this.packageToVisit = packageToVisit;
405+
this.external = external;
406+
}
407+
408+
@Override
409+
public boolean visitPackage(String packageName) {
410+
return packageName.equals(packageToVisit);
411+
}
412+
413+
@Override
414+
public void visitClass(String className) {
415+
String name = className.replace('/', '.');
416+
try {
417+
ctx.incStat("Detectors.Total");
418+
if (addDetector(MetadataSystem.class.getClassLoader().loadClass(name))) {
419+
ctx.incStat("Detectors");
420+
if (external) {
421+
ctx.incStat("Detectors from HuntBugs plugins");
422+
}
423+
}
424+
} catch (ClassNotFoundException e) {
425+
ctx.addError(new ErrorMessage(name, null, null, null, -1, e));
426+
}
427+
}
428+
}
403429
}

huntbugs/src/main/java/one/util/huntbugs/repo/Repository.java

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
*/
1616
package one.util.huntbugs.repo;
1717

18+
import com.strobel.assembler.metadata.ITypeLoader;
19+
import one.util.huntbugs.spi.HuntBugsPlugin;
20+
1821
import java.io.IOException;
1922
import java.net.URISyntaxException;
2023
import java.net.URL;
@@ -30,7 +33,7 @@
3033
import java.util.Set;
3134
import java.util.jar.JarFile;
3235

33-
import com.strobel.assembler.metadata.ITypeLoader;
36+
import static java.lang.String.format;
3437

3538
/**
3639
* @author Tagir Valeev
@@ -41,7 +44,7 @@ public interface Repository {
4144

4245
void visit(String rootPackage, RepositoryVisitor visitor);
4346

44-
public static Repository createSelfRepository() {
47+
static Repository createSelfRepository() {
4548
List<Repository> repos = new ArrayList<>();
4649
Set<Path> paths = new HashSet<>();
4750
try {
@@ -59,33 +62,50 @@ public static Repository createSelfRepository() {
5962
} catch (IOException e) {
6063
throw new RuntimeException(e);
6164
}
62-
CodeSource codeSource = CompositeRepository.class.getProtectionDomain().getCodeSource();
63-
URL url = codeSource == null ? null : codeSource.getLocation();
64-
if(url != null) {
65-
try {
66-
Path path = Paths.get(url.toURI());
67-
if(paths.add(path)) {
68-
if(Files.isDirectory(path))
69-
repos.add(new DirRepository(path));
70-
else if(Files.isRegularFile(path))
71-
repos.add(new JarRepository(new JarFile(path.toFile())));
65+
66+
repos.add(createDetectorsRepo(CompositeRepository.class, "HuntBugs Detectors", paths));
67+
68+
return new CompositeRepository(repos);
69+
}
70+
71+
static Repository createPluginRepository(HuntBugsPlugin huntBugsPlugin) {
72+
Class<?> pluginClass = huntBugsPlugin.getClass();
73+
String pluginName = huntBugsPlugin.name();
74+
return createDetectorsRepo(pluginClass, pluginName, new HashSet<>());
75+
}
76+
77+
static Repository createDetectorsRepo(Class<?> clazz, String pluginName, Set<Path> paths) {
78+
CodeSource codeSource = clazz.getProtectionDomain().getCodeSource();
79+
if (codeSource == null) {
80+
throw new RuntimeException(format("Initializing plugin '%s' could not get code source for class %s", pluginName, clazz.getName()));
81+
}
82+
83+
URL url = codeSource.getLocation();
84+
try {
85+
Path path = Paths.get(url.toURI());
86+
if(paths.add(path)) {
87+
if(Files.isDirectory(path)) {
88+
return new DirRepository(path);
89+
} else {
90+
return new JarRepository(new JarFile(path.toFile()));
7291
}
73-
} catch (URISyntaxException | FileSystemNotFoundException | IllegalArgumentException
74-
| IOException | UnsupportedOperationException e) {
75-
// ignore
92+
} else {
93+
return createNullRepository();
7694
}
95+
} catch (URISyntaxException | FileSystemNotFoundException | IllegalArgumentException
96+
| IOException | UnsupportedOperationException e) {
97+
String errorMessage = format("Error creating detector repository for plugin '%s'", pluginName);
98+
throw new RuntimeException(errorMessage, e);
7799
}
78-
CompositeRepository repo = new CompositeRepository(repos);
79-
return repo;
80100
}
81-
82-
public static Repository createNullRepository() {
101+
102+
static Repository createNullRepository() {
83103
return new Repository() {
84104
@Override
85105
public void visit(String rootPackage, RepositoryVisitor visitor) {
86106
// nothing to do
87107
}
88-
108+
89109
@Override
90110
public ITypeLoader createTypeLoader() {
91111
return (internalName, buffer) -> false;
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2016 HuntBugs contributors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package one.util.huntbugs.spi;
17+
18+
import one.util.huntbugs.analysis.AnalysisOptions;
19+
import one.util.huntbugs.analysis.Context;
20+
import one.util.huntbugs.analysis.ErrorMessage;
21+
import one.util.huntbugs.analysis.HuntBugsResult;
22+
import one.util.huntbugs.input.XmlReportReader;
23+
import one.util.huntbugs.output.Reports;
24+
import one.util.huntbugs.repo.CompositeRepository;
25+
import one.util.huntbugs.repo.Repository;
26+
27+
import java.io.PrintStream;
28+
import java.nio.file.Files;
29+
import java.nio.file.Path;
30+
import java.nio.file.Paths;
31+
import java.util.ArrayList;
32+
import java.util.Arrays;
33+
import java.util.List;
34+
import java.util.ServiceLoader;
35+
import java.util.stream.Collectors;
36+
37+
import static java.lang.String.format;
38+
39+
/**
40+
* @author Tagir Valeev
41+
*
42+
*/
43+
public abstract class DataTests {
44+
45+
public static void test(String packageToAnalyze) throws Exception {
46+
47+
// creating built-in and plugins repositories
48+
List<Repository> repositories = new ArrayList<>();
49+
repositories.add(Repository.createSelfRepository());
50+
for (HuntBugsPlugin huntBugsPlugin : ServiceLoader.load(HuntBugsPlugin.class)) {
51+
repositories.add(Repository.createPluginRepository(huntBugsPlugin));
52+
}
53+
CompositeRepository repository = new CompositeRepository(repositories);
54+
55+
Context ctx = new Context(repository, new AnalysisOptions());
56+
ctx.analyzePackage(packageToAnalyze);
57+
ctx.reportStats(System.out);
58+
ctx.reportErrors(System.err);
59+
ctx.reportWarnings(new PrintStream("target/testWarnings.out"));
60+
Path xmlReport = Paths.get("target/testWarnings.xml");
61+
Reports.write(xmlReport, Paths.get("target/testWarnings.html"), ctx);
62+
System.out.println("Analyzed " + ctx.getClassesCount() + " classes");
63+
if (ctx.getErrorCount() > 0) {
64+
List<ErrorMessage> errorMessages = ctx.errors().collect(Collectors.toList());
65+
throw new AssertionError(format("Analysis finished with %s errors: %s", ctx.getErrorCount(), errorMessages));
66+
}
67+
HuntBugsResult result = XmlReportReader.read(ctx, xmlReport);
68+
Path rereadReport = Paths.get("target/testWarnings_reread.xml");
69+
Reports.write(rereadReport, null, result);
70+
byte[] expectedReport = Files.readAllBytes(xmlReport);
71+
byte[] actualReport = Files.readAllBytes(rereadReport);
72+
if (!Arrays.equals(expectedReport, actualReport)) {
73+
String errorMessage = format("Expected: \n%s\n\nActual: \n%s\n\n", new String(expectedReport), new String(actualReport));
74+
throw new AssertionError(errorMessage);
75+
}
76+
}
77+
78+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2016 HuntBugs contributors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package one.util.huntbugs.spi;
17+
18+
/**
19+
* This is extension point for 3-rd party detector providers.
20+
*
21+
* @author Mihails Volkovs
22+
*
23+
*/
24+
public interface HuntBugsPlugin {
25+
26+
String name();
27+
28+
String detectorPackage();
29+
30+
}

0 commit comments

Comments
 (0)