Skip to content

Commit

Permalink
Unit Tests for UnexpectedAccessDetector
Browse files Browse the repository at this point in the history
  • Loading branch information
wojtus committed Dec 4, 2014
1 parent b2aa36c commit 0960f0f
Show file tree
Hide file tree
Showing 2 changed files with 184 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;

import com.google.common.annotations.VisibleForTesting;

import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.BytecodeScanningDetector;
Expand All @@ -24,87 +26,90 @@
* @see com.google.common.annotations.VisibleForTesting
*/
public class UnexpectedAccessDetector extends BytecodeScanningDetector {
@Nonnull
private final BugReporter bugReporter;

public UnexpectedAccessDetector(BugReporter bugReporter) {
this.bugReporter = checkNotNull(bugReporter);
}

@Override
public void sawOpcode(int opcode) {
if (! isInvoking(opcode)) {
return;
}

ClassDescriptor currentClass = getClassDescriptor();
ClassDescriptor invokedClass = getClassDescriptorOperand();
if (currentClass.equals(invokedClass)) {
// no need to check, because method is called by owner
} else if (! currentClass.getPackageName().equals(invokedClass.getPackageName())) {
// no need to check, because method is called by class in other package
} else {
MethodDescriptor invokedMethod = getMethodDescriptorOperand();

try {
verifyVisibility(invokedClass, invokedMethod);
} catch (ClassNotFoundException e) {
String message = String.format("Detector could not find %s, you should add this class into CLASSPATH", invokedClass.getDottedClassName());
bugReporter.logError(message, e);
}
}
}

/**
* <p>Report if specified method is package-private and annotated by {@code @VisibleForTesting}.</p>
*/
private void verifyVisibility(ClassDescriptor invokedClass, MethodDescriptor invokedMethod) throws ClassNotFoundException {
JavaClass bcelClass = Repository.getRepository().loadClass(invokedClass.getDottedClassName());
Method bcelMethod = findMethod(bcelClass, invokedMethod);

if (checkVisibility(bcelMethod) && checkAnnotated(bcelMethod)) {
BugInstance bug = new BugInstance(this, "GUAVA_UNEXPECTED_ACCESS_TO_VISIBLE_FOR_TESTING", HIGH_PRIORITY)
.addCalledMethod(this).addClassAndMethod(this).addSourceLine(this);
bugReporter.reportBug(bug);
}
}

private Method findMethod(JavaClass bcelClass, MethodDescriptor invokedMethod) {
for (Method bcelMethod : bcelClass.getMethods()) {
MethodDescriptor methodDescriptor = BCELUtil.getMethodDescriptor(bcelClass, bcelMethod);
if (methodDescriptor.equals(invokedMethod)) {
return bcelMethod;
}
}

throw new IllegalArgumentException("Method not found: " + invokedMethod);
}

/**
* @return true if visibility of specified method is package-private.
*/
private boolean checkVisibility(Method bcelMethod) {
return ! (bcelMethod.isPrivate() || bcelMethod.isProtected() || bcelMethod.isPublic());
}

/**
* @return true if specified method is annotated by {@code VisibleForTesting}.
*/
private boolean checkAnnotated(Method bcelMethod) {
for (AnnotationEntry annotation : bcelMethod.getAnnotationEntries()) {
String type = annotation.getAnnotationType();
if ("Lcom/google/common/annotations/VisibleForTesting;".equals(type)) {
return true;
}
}
return false;
}

private boolean isInvoking(int opcode) {
return opcode == INVOKESPECIAL ||
opcode == INVOKEINTERFACE ||
opcode == INVOKESTATIC ||
opcode == INVOKEVIRTUAL;
}
@Nonnull
private final BugReporter bugReporter;

/**
* @param bugReporter
*/
public UnexpectedAccessDetector(BugReporter bugReporter) {
this.bugReporter = checkNotNull(bugReporter);
}

@Override
public void sawOpcode(int opcode) {
if (!isInvoking(opcode)) {
return;
}

ClassDescriptor currentClass = getClassDescriptor();
ClassDescriptor invokedClass = getClassDescriptorOperand();
if (currentClass.equals(invokedClass)) {
// no need to check, because method is called by owner
} else if (!currentClass.getPackageName().equals(invokedClass.getPackageName())) {
// no need to check, because method is called by class in other package
} else {
MethodDescriptor invokedMethod = getMethodDescriptorOperand();

try {
verifyVisibility(invokedClass, invokedMethod);
} catch (ClassNotFoundException e) {
String message = String.format("Detector could not find %s, you should add this class into CLASSPATH", invokedClass.getDottedClassName());
bugReporter.logError(message, e);
}
}
}

/**
* <p>Report if specified method is package-private and annotated by {@code @VisibleForTesting}.</p>
*/
private void verifyVisibility(ClassDescriptor invokedClass, MethodDescriptor invokedMethod) throws ClassNotFoundException {
JavaClass bcelClass = Repository.getRepository().loadClass(invokedClass.getDottedClassName());
Method bcelMethod = findMethod(bcelClass, invokedMethod);

if (checkVisibility(bcelMethod) && checkAnnotated(bcelMethod)) {
BugInstance bug = new BugInstance(this, "GUAVA_UNEXPECTED_ACCESS_TO_VISIBLE_FOR_TESTING", HIGH_PRIORITY).addCalledMethod(this).addClassAndMethod(this).addSourceLine(this);
bugReporter.reportBug(bug);
}
}

@VisibleForTesting
Method findMethod(JavaClass bcelClass, MethodDescriptor invokedMethod) {
for (Method bcelMethod : bcelClass.getMethods()) {
MethodDescriptor methodDescriptor = BCELUtil.getMethodDescriptor(bcelClass, bcelMethod);
if (methodDescriptor.equals(invokedMethod)) {
return bcelMethod;
}
}

throw new IllegalArgumentException("Method not found: " + invokedMethod);
}

/**
* @return true if visibility of specified method is package-private.
*/
@VisibleForTesting
boolean checkVisibility(Method bcelMethod) {
return !(bcelMethod.isPrivate() || bcelMethod.isProtected() || bcelMethod.isPublic());
}

/**
* @return true if specified method is annotated by {@code VisibleForTesting}.
*/
@VisibleForTesting
boolean checkAnnotated(Method bcelMethod) {
for (AnnotationEntry annotation : bcelMethod.getAnnotationEntries()) {
String type = annotation.getAnnotationType();
if ("Lcom/google/common/annotations/VisibleForTesting;".equals(type)) {
return true;
}
}
return false;
}

@VisibleForTesting
boolean isInvoking(int opcode) {
return opcode == INVOKESPECIAL || opcode == INVOKEINTERFACE || opcode == INVOKESTATIC || opcode == INVOKEVIRTUAL;
}

}
Original file line number Diff line number Diff line change
@@ -1,21 +1,42 @@
package jp.co.worksap.oss.findbugs.guava;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import org.apache.bcel.Constants;
import org.apache.bcel.classfile.AnnotationEntry;
import org.apache.bcel.classfile.Attribute;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.objectweb.asm.Opcodes;

import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.classfile.MethodDescriptor;


@Ignore("test-driven-detectors4findbugs dependency is removed")
/**
* @author tolina GmbH
*
*/
public class UnexpectedAccessDetectorTest {

private UnexpectedAccessDetector detector;

@Mock
private BugReporter bugReporter;

/**
*
*/
@Before
public void setup() {
public void initMocks() {
MockitoAnnotations.initMocks(this);
// bugReporter = bugReporterForTesting();
// detector = new UnexpectedAccessDetector(bugReporter);
}

@Test
Expand All @@ -28,4 +49,75 @@ public void testCallingAnnotatedMethod() throws Exception {
// assertBugReported(ClassWhichCallsVisibleMethodForTesting.class, detector, bugReporter, ofType("GUAVA_UNEXPECTED_ACCESS_TO_VISIBLE_FOR_TESTING"));
}


@Test
public void testFindMethod() {
UnexpectedAccessDetector detector = new UnexpectedAccessDetector(bugReporter);

JavaClass bcelClass = null;
MethodDescriptor invokedMethod = null;

Method method = detector.findMethod(bcelClass, invokedMethod);

}

/**
* Checks, if detecting of 'default'-Visibility works
*/
@Test
public void testCheckVisibility() {
UnexpectedAccessDetector detector = new UnexpectedAccessDetector(bugReporter);
Method privateMethod = new Method();
privateMethod.setModifiers(Opcodes.ACC_PRIVATE);
assertFalse(detector.checkVisibility(privateMethod));

Method protectedMethod = new Method();
protectedMethod.setModifiers(Opcodes.ACC_PROTECTED);
assertFalse(detector.checkVisibility(protectedMethod));

Method publicMethod = new Method();
publicMethod.setModifiers(Opcodes.ACC_PUBLIC);
assertFalse(detector.checkVisibility(publicMethod));

Method defaultVisibilityMethod = new Method();
assertTrue(detector.checkVisibility(defaultVisibilityMethod));
}

/**
* Checks, if detecting of annotated Methods works
*/
@Test
public void testCheckAnnotated() {
UnexpectedAccessDetector detector = new UnexpectedAccessDetector(bugReporter);
Method method = new Method();
method.setAttributes(new Attribute[] {});

assertFalse(detector.checkAnnotated(method));

AnnotationEntry someAnnotationEntry = mock(AnnotationEntry.class);
when(someAnnotationEntry.getAnnotationType()).thenReturn("Lorg/junit/Test;");
method.addAnnotationEntry(someAnnotationEntry);

assertFalse(detector.checkAnnotated(method));

AnnotationEntry visibleForTestingAnnotationEntry = mock(AnnotationEntry.class);
when(visibleForTestingAnnotationEntry.getAnnotationType()).thenReturn("Lcom/google/common/annotations/VisibleForTesting;");
method.addAnnotationEntry(visibleForTestingAnnotationEntry);

assertTrue(detector.checkAnnotated(method));
}

/**
* Checks, if 'invoking' OP-Codes are detected
*/
@Test
public void testIsInvoking() {
UnexpectedAccessDetector detector = new UnexpectedAccessDetector(bugReporter);
assertTrue(detector.isInvoking(Constants.INVOKESPECIAL));
assertTrue(detector.isInvoking(Constants.INVOKEINTERFACE));
assertTrue(detector.isInvoking(Constants.INVOKESTATIC));
assertTrue(detector.isInvoking(Constants.INVOKEVIRTUAL));

}

}

0 comments on commit 0960f0f

Please sign in to comment.