/*
* Copyright 2016 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.formatstring;
import static com.google.errorprone.BugPattern.Category.JDK;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.staticMethod;
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Locale;
import java.util.regex.Pattern;
/** @author cushon@google.com (Liam Miller-Cushon) */
@BugPattern(
name = "FormatString",
summary = "Invalid printf-style format string",
category = JDK,
severity = ERROR
)
public class FormatString extends BugChecker implements MethodInvocationTreeMatcher {
// TODO(cushon): add support for additional printf methods, maybe with an annotation
private static final Matcher<ExpressionTree> FORMAT_METHOD =
anyOf(
instanceMethod()
.onDescendantOf("java.io.PrintStream")
.withNameMatching(Pattern.compile("format|printf")),
instanceMethod()
.onDescendantOf("java.io.PrintWriter")
.withNameMatching(Pattern.compile("format|printf")),
instanceMethod().onDescendantOf("java.util.Formatter").named("format"),
staticMethod().onClass("java.lang.String").named("format"),
staticMethod()
.onClass("java.io.Console")
.withNameMatching(Pattern.compile("format|printf|readline|readPassword")));
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, final VisitorState state) {
if (!FORMAT_METHOD.matches(tree, state)) {
return Description.NO_MATCH;
}
Deque<ExpressionTree> args = new ArrayDeque<ExpressionTree>(tree.getArguments());
// skip the first argument of printf(Locale,String,Object...)
if (ASTHelpers.isSameType(
ASTHelpers.getType(args.peekFirst()),
state.getTypeFromString(Locale.class.getName()),
state)) {
args.removeFirst();
}
FormatStringValidation.ValidationResult result = FormatStringValidation.validate(args, state);
if (result == null) {
return Description.NO_MATCH;
}
return buildDescription(tree).setMessage(result.message()).build();
}
}