All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.errorprone.bugpatterns.time.DateChecker Maven / Gradle / Ivy

There is a newer version: 2.27.1
Show newest version
/*
 * Copyright 2020 The Error Prone Authors.
 *
 * 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.time;

import static com.google.common.base.Verify.verify;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.constructor;
import static com.google.errorprone.matchers.Matchers.instanceMethod;

import com.google.common.base.Joiner;
import com.google.common.collect.Range;
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.bugpatterns.BugChecker.NewClassTreeMatcher;
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.LiteralTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.NewClassTree;
import java.time.DateTimeException;
import java.time.Month;
import java.util.ArrayList;
import java.util.List;

/**
 * Warns against suspect looking calls to {@link java.util.Date} APIs. Noteably, {@code Date} uses:
 *
 * 
    *
  • 1900-based years (negative values permitted) *
  • 0-based months (with rollover and negative values permitted) *
  • 1-based days (with rollover and negative values permitted) *
  • 0-based hours (with rollover and negative values permitted) *
  • 0-based minutes (with rollover and negative values permitted) *
  • 0-based seconds (with rollover and negative values permitted) *
* * @author [email protected] (Kurt Alfred Kluever) */ @BugPattern( summary = "Warns against suspect looking calls to java.util.Date APIs", explanation = "java.util.Date uses 1900-based years, 0-based months, 1-based days, and 0-based" + " hours/minutes/seconds. Additionally, it allows for negative values or very large" + " values (which rollover).", severity = WARNING) public final class DateChecker extends BugChecker implements MethodInvocationTreeMatcher, NewClassTreeMatcher { private static final String DATE = "java.util.Date"; private static final Matcher CONSTRUCTORS = anyOf( constructor().forClass(DATE).withParameters("int", "int", "int"), constructor().forClass(DATE).withParameters("int", "int", "int", "int", "int"), constructor().forClass(DATE).withParameters("int", "int", "int", "int", "int", "int")); private static final Matcher SET_YEAR = instanceMethod().onExactClass(DATE).named("setYear"); private static final Matcher SET_MONTH = instanceMethod().onExactClass(DATE).named("setMonth"); private static final Matcher SET_DAY = instanceMethod().onExactClass(DATE).named("setDate"); private static final Matcher SET_HOUR = instanceMethod().onExactClass(DATE).named("setHours"); private static final Matcher SET_MIN = instanceMethod().onExactClass(DATE).named("setMinutes"); private static final Matcher SET_SEC = instanceMethod().onExactClass(DATE).named("setSeconds"); // permits years [1901, 2050] which seems ~reasonable private static final Range YEAR_RANGE = Range.closed(1, 150); private static final Range MONTH_RANGE = Range.closed(0, 11); private static final Range DAY_RANGE = Range.closed(1, 31); private static final Range HOUR_RANGE = Range.closed(0, 23); private static final Range SEC_MIN_RANGE = Range.closed(0, 59); @Override public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) { List errors = new ArrayList<>(); if (tree.getArguments().size() == 1) { ExpressionTree arg0 = tree.getArguments().get(0); if (SET_YEAR.matches(tree, state)) { checkYear(arg0, errors); } else if (SET_MONTH.matches(tree, state)) { checkMonth(arg0, errors); } else if (SET_DAY.matches(tree, state)) { checkDay(arg0, errors); } else if (SET_HOUR.matches(tree, state)) { checkHours(arg0, errors); } else if (SET_MIN.matches(tree, state)) { checkMinutes(arg0, errors); } else if (SET_SEC.matches(tree, state)) { checkSeconds(arg0, errors); } } return buildDescription(tree, errors); } @Override public Description matchNewClass(NewClassTree tree, VisitorState state) { List errors = new ArrayList<>(); if (CONSTRUCTORS.matches(tree, state)) { List args = tree.getArguments(); int numArgs = args.size(); verify( numArgs >= 3 && numArgs <= 6, "Expected the constructor to have at least 3 and at most 6 arguments, but it had %s", numArgs); checkYear(args.get(0), errors); checkMonth(args.get(1), errors); checkDay(args.get(2), errors); if (numArgs > 4) { checkHours(args.get(3), errors); checkMinutes(args.get(4), errors); } if (numArgs > 5) { checkSeconds(args.get(5), errors); } } return buildDescription(tree, errors); } private Description buildDescription(ExpressionTree tree, List errors) { return errors.isEmpty() ? Description.NO_MATCH : buildDescription(tree) .setMessage( "This Date usage looks suspect for the following reason(s): " + Joiner.on(" ").join(errors)) .build(); } private static void checkYear(ExpressionTree tree, List errors) { checkBounds(tree, "1900-based year", YEAR_RANGE, errors); } private static void checkMonth(ExpressionTree tree, List errors) { checkBounds(tree, "0-based month", MONTH_RANGE, errors); if (tree instanceof LiteralTree) { int monthValue = (int) ((LiteralTree) tree).getValue(); try { errors.add( String.format( "Use Calendar.%s instead of %s to represent the month.", Month.of(monthValue + 1), monthValue)); } catch (DateTimeException badMonth) { // this is an out of bounds month, and thus already caught by the checkBounds() call above! } } } private static void checkDay(ExpressionTree tree, List errors) { // TODO(kak): we should also consider checking if the given day is valid for the given // month/year. E.g., Feb 30th is never valid, Feb 29th is sometimes valid, and Feb 28th is // always valid. checkBounds(tree, "day", DAY_RANGE, errors); } private static void checkHours(ExpressionTree tree, List errors) { checkBounds(tree, "hours", HOUR_RANGE, errors); } private static void checkMinutes(ExpressionTree tree, List errors) { checkBounds(tree, "minutes", SEC_MIN_RANGE, errors); } private static void checkSeconds(ExpressionTree tree, List errors) { checkBounds(tree, "seconds", SEC_MIN_RANGE, errors); } private static void checkBounds( ExpressionTree tree, String type, Range range, List errors) { Integer value = ASTHelpers.constValue(tree, Integer.class); if (value != null && !range.contains(value)) { errors.add(String.format("The %s value (%s) is out of bounds %s.", type, value, range)); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy