/* * Copyright 2012 Google Inc. All Rights Reserved. * * 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 com.google.errorprone.bugpatterns; import static com.google.errorprone.BugPattern.Category.JDK; import static com.google.errorprone.BugPattern.SeverityLevel.ERROR; import static com.google.errorprone.matchers.Matchers.allOf; import static com.google.errorprone.matchers.Matchers.anyOf; import static com.google.errorprone.matchers.Matchers.argument; import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod; import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod; import com.google.errorprone.BugPattern; import com.google.errorprone.VisitorState; import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher; import com.google.errorprone.fixes.SuggestedFix; import com.google.errorprone.matchers.Description; import com.google.errorprone.matchers.Matcher; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.tools.javac.tree.JCTree.JCExpression; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; /** @author mdempsky@google.com (Matthew Dempsky) */ @BugPattern( name = "InvalidPatternSyntax", summary = "Invalid syntax used for a regular expression", explanation = "This error is triggered by calls to regex-accepting methods with invalid string " + "literals. These calls would cause a PatternSyntaxException at runtime.\n\n" + "We deliberately do not check java.util.regex.Pattern#compile as many of its users " + "are deliberately testing the regex compiler or using a vacuously true regex.", category = JDK, severity = ERROR ) public class InvalidPatternSyntax extends BugChecker implements MethodInvocationTreeMatcher { private static final String MESSAGE_BASE = "Invalid syntax used for a regular expression: "; /* Match string literals that are not valid syntax for regular expressions. */ private static final Matcher<ExpressionTree> BAD_REGEX_LITERAL = new Matcher<ExpressionTree>() { @Override public boolean matches(ExpressionTree tree, VisitorState state) { Object value = ((JCExpression) tree).type.constValue(); return value instanceof String && !isValidSyntax((String) value); } private boolean isValidSyntax(String regex) { // Actually valid, but useless. if (".".equals(regex)) { return false; } try { Pattern.compile(regex); return true; } catch (PatternSyntaxException e) { return false; } } }; /* * Match invocations to regex-accepting methods with bad string literals. * * <p>We deliberately omit Pattern.compile itself, as most of its users appear to be either * passing e.g. LITERAL flags, deliberately testing the regex compiler, or deliberately * using "." as the "vacuously true regex." */ private static final Matcher<MethodInvocationTree> BAD_REGEX_USAGE = allOf( anyOf( instanceMethod().onDescendantOf("java.lang.String") .withSignature("matches(java.lang.String)"), instanceMethod().onDescendantOf("java.lang.String") .withSignature("replaceAll(java.lang.String,java.lang.String)"), instanceMethod().onDescendantOf("java.lang.String") .withSignature("replaceFirst(java.lang.String,java.lang.String)"), instanceMethod().onDescendantOf("java.lang.String") .withSignature("split(java.lang.String)"), instanceMethod().onDescendantOf("java.lang.String") .withSignature("split(java.lang.String,int)"), staticMethod().onClass("java.util.regex.Pattern").named("matches"), staticMethod().onClass("com.google.common.base.Splitter").named("onPattern")), argument(0, BAD_REGEX_LITERAL)); @Override public Description matchMethodInvocation( MethodInvocationTree methodInvocationTree, VisitorState state) { if (!BAD_REGEX_USAGE.matches(methodInvocationTree, state)) { return Description.NO_MATCH; } // TODO: Suggest fixes for more situations. Description.Builder descriptionBuilder = buildDescription(methodInvocationTree); ExpressionTree arg = methodInvocationTree.getArguments().get(0); String value = (String) ((JCExpression) arg).type.constValue(); String reasonInvalid = ""; if (".".equals(value)) { descriptionBuilder.addFix(SuggestedFix.replace(arg, "\"\\\\.\"")); reasonInvalid = "\".\" is a valid but useless regex"; } else { try { Pattern.compile(value); } catch (PatternSyntaxException e) { reasonInvalid = e.getMessage(); } } descriptionBuilder.setMessage(MESSAGE_BASE + reasonInvalid); return descriptionBuilder.build(); } }