1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.coding;
21  
22  import java.util.Arrays;
23  import java.util.Collections;
24  import java.util.Set;
25  import java.util.stream.Collectors;
26  
27  import com.puppycrawl.tools.checkstyle.StatelessCheck;
28  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
29  import com.puppycrawl.tools.checkstyle.api.DetailAST;
30  import com.puppycrawl.tools.checkstyle.api.FullIdent;
31  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
32  import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil;
33  import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
34  
35  /**
36   * <p>
37   * This check can be used to ensure that types are not declared to be thrown.
38   * Declaring that a method throws {@code java.lang.Error} or
39   * {@code java.lang.RuntimeException} is almost never acceptable.
40   * </p>
41   * <ul>
42   * <li>
43   * Property {@code illegalClassNames} - Specify throw class names to reject.
44   * Default value is {@code java.lang.Throwable, RuntimeException, Error, Throwable,
45   * java.lang.Error, java.lang.RuntimeException}.
46   * </li>
47   * <li>
48   * Property {@code ignoredMethodNames} - Specify names of methods to ignore.
49   * Default value is {@code finalize}.
50   * </li>
51   * <li>
52   * Property {@code ignoreOverriddenMethods} - allow to ignore checking overridden methods
53   * (marked with {@code Override} or {@code java.lang.Override} annotation).
54   * Default value is {@code true}.
55   * </li>
56   * </ul>
57   * <p>
58   * To configure the check:
59   * </p>
60   * <pre>
61   * &lt;module name="IllegalThrows"/&gt;
62   * </pre>
63   * <p>
64   * To configure the check rejecting throws NullPointerException from methods:
65   * </p>
66   * <pre>
67   * &lt;module name="IllegalThrows"&gt;
68   *   &lt;property name="illegalClassNames" value="NullPointerException"/&gt;
69   * &lt;/module&gt;
70   * </pre>
71   * <p>
72   * To configure the check ignoring method named "foo()":
73   * </p>
74   * <pre>
75   * &lt;module name="IllegalThrows"&gt;
76   *   &lt;property name="ignoredMethodNames" value="foo"/&gt;
77   * &lt;/module&gt;
78   * </pre>
79   * <p>
80   * To configure the check to warn on overridden methods:
81   * </p>
82   * <pre>
83   * &lt;module name=&quot;IllegalThrows&quot;&gt;
84   *   &lt;property name=&quot;ignoreOverriddenMethods&quot; value=&quot;false&quot;/&gt;
85   * &lt;/module&gt;
86   * </pre>
87   *
88   * @since 4.0
89   */
90  @StatelessCheck
91  public final class IllegalThrowsCheck extends AbstractCheck {
92  
93      /**
94       * A key is pointing to the warning message text in "messages.properties"
95       * file.
96       */
97      public static final String MSG_KEY = "illegal.throw";
98  
99      /** Specify names of methods to ignore. */
100     private final Set<String> ignoredMethodNames =
101         Arrays.stream(new String[] {"finalize", }).collect(Collectors.toSet());
102 
103     /** Specify throw class names to reject. */
104     private final Set<String> illegalClassNames = Arrays.stream(
105         new String[] {"Error", "RuntimeException", "Throwable", "java.lang.Error",
106                       "java.lang.RuntimeException", "java.lang.Throwable", })
107         .collect(Collectors.toSet());
108 
109     /**
110      * Allow to ignore checking overridden methods (marked with {@code Override}
111      * or {@code java.lang.Override} annotation).
112      */
113     private boolean ignoreOverriddenMethods = true;
114 
115     /**
116      * Setter to specify throw class names to reject.
117      *
118      * @param classNames
119      *            array of illegal exception classes
120      */
121     public void setIllegalClassNames(final String... classNames) {
122         illegalClassNames.clear();
123         illegalClassNames.addAll(
124                 CheckUtil.parseClassNames(classNames));
125     }
126 
127     @Override
128     public int[] getDefaultTokens() {
129         return getRequiredTokens();
130     }
131 
132     @Override
133     public int[] getRequiredTokens() {
134         return new int[] {TokenTypes.LITERAL_THROWS};
135     }
136 
137     @Override
138     public int[] getAcceptableTokens() {
139         return getRequiredTokens();
140     }
141 
142     @Override
143     public void visitToken(DetailAST detailAST) {
144         final DetailAST methodDef = detailAST.getParent();
145         // Check if the method with the given name should be ignored.
146         if (!isIgnorableMethod(methodDef)) {
147             DetailAST token = detailAST.getFirstChild();
148             while (token != null) {
149                 if (token.getType() != TokenTypes.COMMA) {
150                     final FullIdent ident = FullIdent.createFullIdent(token);
151                     if (illegalClassNames.contains(ident.getText())) {
152                         log(token, MSG_KEY, ident.getText());
153                     }
154                 }
155                 token = token.getNextSibling();
156             }
157         }
158     }
159 
160     /**
161      * Checks if current method is ignorable due to Check's properties.
162      * @param methodDef {@link TokenTypes#METHOD_DEF METHOD_DEF}
163      * @return true if method is ignorable.
164      */
165     private boolean isIgnorableMethod(DetailAST methodDef) {
166         return shouldIgnoreMethod(methodDef.findFirstToken(TokenTypes.IDENT).getText())
167             || ignoreOverriddenMethods
168                && (AnnotationUtil.containsAnnotation(methodDef, "Override")
169                   || AnnotationUtil.containsAnnotation(methodDef, "java.lang.Override"));
170     }
171 
172     /**
173      * Check if the method is specified in the ignore method list.
174      * @param name the name to check
175      * @return whether the method with the passed name should be ignored
176      */
177     private boolean shouldIgnoreMethod(String name) {
178         return ignoredMethodNames.contains(name);
179     }
180 
181     /**
182      * Setter to specify names of methods to ignore.
183      * @param methodNames array of ignored method names
184      */
185     public void setIgnoredMethodNames(String... methodNames) {
186         ignoredMethodNames.clear();
187         Collections.addAll(ignoredMethodNames, methodNames);
188     }
189 
190     /**
191      * Setter to allow to ignore checking overridden methods
192      * (marked with {@code Override} or {@code java.lang.Override} annotation).
193      * @param ignoreOverriddenMethods Check's property.
194      */
195     public void setIgnoreOverriddenMethods(boolean ignoreOverriddenMethods) {
196         this.ignoreOverriddenMethods = ignoreOverriddenMethods;
197     }
198 
199 }