/** * Find Security Bugs * Copyright (c) Philippe Arteau, All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3.0 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. */ package com.h3xstream.findbugs.test.matcher; import edu.umd.cs.findbugs.*; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; public class BugInstanceMatcher extends BaseMatcher<BugInstance> { private static final Logger log = LoggerFactory.getLogger(BugInstanceMatcherBuilder.class); private static final Pattern ANON_FUNCTION_SCALA_PATTERN = Pattern.compile("\\$\\$anonfun\\$([^\\$]+)\\$"); private String bugType; private String className; private String methodName; private String fieldName; private Integer lineNumber; private Integer lineNumberApprox; private String priority; private String jspFile; private List<Integer> multipleChoicesLine; /** * All the parameters are optional. Only the non-null parameters are used. * * @param bugType Expected bug type * @param className Class name * @param methodName Method name * @param fieldName Field name * @param lineNumber Line number * @param lineNumberApprox Approximate line for test samples that are unstable (Historically the JSP samples) * @param priority Priority * @param jspFile JSP file name * @param multipleChoicesLine At least of the line (JSP samples specific) */ public BugInstanceMatcher(String bugType, String className, String methodName, String fieldName, Integer lineNumber, Integer lineNumberApprox, String priority, String jspFile, List<Integer> multipleChoicesLine) { this.bugType = bugType; this.className = className; this.methodName = methodName; this.fieldName = fieldName; this.lineNumber = lineNumber; this.lineNumberApprox = lineNumberApprox; this.priority = priority; this.jspFile = jspFile; this.multipleChoicesLine = multipleChoicesLine; } @Override public boolean matches(Object obj) { if (obj instanceof BugInstance) { BugInstance bugInstance = (BugInstance) obj; boolean criteriaMatches = true; if (bugType != null) { criteriaMatches &= bugInstance.getType().equals(bugType); } if (priority != null) { criteriaMatches &= bugInstance.getPriorityString().equals(priority); } if (className != null) { ClassAnnotation classAnn = extractBugAnnotation(bugInstance, ClassAnnotation.class); if (classAnn == null) return false; String fullName = classAnn.getClassName(); int startDot = fullName.lastIndexOf(".") + 1; int endDollar = fullName.indexOf('$'); String simpleName = fullName.substring(startDot != -1 ? startDot : 0, endDollar != -1 ? endDollar : fullName.length()); String simpleNameInner = fullName.substring(startDot != -1 ? startDot : 0, fullName.length()); criteriaMatches &= fullName.equals(className) || simpleName.equals(className) || simpleNameInner.equals(className); } if (methodName != null) { MethodAnnotation methodAnn = extractBugAnnotation(bugInstance, MethodAnnotation.class); ClassAnnotation classAnn = extractBugAnnotation(bugInstance, ClassAnnotation.class); String fullClassName = classAnn.getClassName(); if (methodAnn == null) return false; if(methodAnn.getMethodName().startsWith("apply") && fullClassName != null) { Matcher m = ANON_FUNCTION_SCALA_PATTERN.matcher(fullClassName); if(m.find()) { //Scala function enclose in criteriaMatches &= methodAnn.getMethodName().equals(methodName) || methodName.equals(m.group(1)); } } else { // criteriaMatches &= methodAnn.getMethodName().equals(methodName); } } if (fieldName != null) { FieldAnnotation fieldAnn = extractBugAnnotation(bugInstance, FieldAnnotation.class); if (fieldAnn == null) return false; criteriaMatches &= fieldAnn.getFieldName().equals(fieldName); } if (lineNumber != null) { SourceLineAnnotation srcAnn = extractBugAnnotation(bugInstance, SourceLineAnnotation.class); if (srcAnn == null) return false; criteriaMatches &= srcAnn.getStartLine() <= lineNumber && lineNumber <= srcAnn.getEndLine(); } if (lineNumberApprox != null) { SourceLineAnnotation srcAnn = extractBugAnnotation(bugInstance, SourceLineAnnotation.class); if (srcAnn == null) return false; criteriaMatches &= srcAnn.getStartLine()-1 <= lineNumberApprox && lineNumberApprox <= srcAnn.getEndLine()+1; } if(jspFile != null) { ClassAnnotation classAnn = extractBugAnnotation(bugInstance, ClassAnnotation.class); String fullName = classAnn.getClassName().replaceAll("\\.","/").replaceAll("_005f","_").replaceAll("_jsp", ".jsp"); //String simpleName = fullName.substring(fullName.lastIndexOf("/") + 1); criteriaMatches &= fullName.endsWith(jspFile); } if (multipleChoicesLine != null) { SourceLineAnnotation srcAnn = extractBugAnnotation(bugInstance, SourceLineAnnotation.class); if (srcAnn == null) return false; boolean found = false; for(Integer potentialMatch : multipleChoicesLine) { if(srcAnn.getStartLine()-1 <= potentialMatch && potentialMatch <= srcAnn.getEndLine()+1) { found = true; } } //if(!found) { //log.info("The bug was between lines "+srcAnn.getStartLine()+" and "+srcAnn.getEndLine()); //} criteriaMatches &= found; } return criteriaMatches; } else { return false; } } private <T> T extractBugAnnotation(BugInstance bugInstance, Class<T> annotationType) { for (BugAnnotation annotation : bugInstance.getAnnotations()) { if (annotation.getClass().equals(annotationType)) { return (T) annotation; } } return null; } @Override public void describeTo(Description description) { description.appendText("BugInstance with:\n"); if (bugType != null) { description.appendText("bugType=").appendValue(bugType).appendText(","); } if (className != null) { description.appendText("className=").appendValue(className).appendText(","); } if (methodName != null) { description.appendText("methodName=").appendValue(methodName).appendText(","); } if (fieldName != null) { description.appendText("fieldName=").appendValue(fieldName).appendText(","); } if (lineNumber != null) { description.appendText("lineNumber=").appendValue(lineNumber); } } }