001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.coding;
021
022import java.util.Arrays;
023import java.util.LinkedList;
024import java.util.List;
025import java.util.Set;
026import java.util.stream.Collectors;
027
028import com.puppycrawl.tools.checkstyle.StatelessCheck;
029import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
030import com.puppycrawl.tools.checkstyle.api.DetailAST;
031import com.puppycrawl.tools.checkstyle.api.FullIdent;
032import com.puppycrawl.tools.checkstyle.api.TokenTypes;
033import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
034
035/**
036 * <p>
037 * Checks that certain exception types do not appear in a {@code catch} statement.
038 * </p>
039 * <p>
040 * Rationale: catching {@code java.lang.Exception}, {@code java.lang.Error} or
041 * {@code java.lang.RuntimeException} is almost never acceptable.
042 * Novice developers often simply catch Exception in an attempt to handle
043 * multiple exception classes. This unfortunately leads to code that inadvertently
044 * catches {@code NullPointerException}, {@code OutOfMemoryError}, etc.
045 * </p>
046 * <ul>
047 * <li>
048 * Property {@code illegalClassNames} - Specify exception class names to reject.
049 * Type is {@code java.lang.String[]}.
050 * Default value is {@code Error, Exception, RuntimeException, Throwable, java.lang.Error,
051 * java.lang.Exception, java.lang.RuntimeException, java.lang.Throwable}.
052 * </li>
053 * </ul>
054 * <p>
055 * To configure the check:
056 * </p>
057 * <pre>
058 * &lt;module name=&quot;IllegalCatch&quot;/&gt;
059 * </pre>
060 * <p>Example:</p>
061 * <pre>
062 * try {
063 *     // some code here
064 * } catch (Exception e) { // violation
065 *
066 * }
067 *
068 * try {
069 *     // some code here
070 * } catch (ArithmeticException e) { // OK
071 *
072 * } catch (Exception e) { // violation, catching Exception is illegal
073 *                           and order of catch blocks doesn't matter
074 * }
075 *
076 * try {
077 *     // some code here
078 * } catch (ArithmeticException | Exception e) { // violation, catching Exception is illegal
079 *
080 * }
081 *
082 * try {
083 *     // some code here
084 * } catch (ArithmeticException e) { // OK
085 *
086 * }
087 *
088 * </pre>
089 * <p>
090 * To configure the check to override the default list
091 * with ArithmeticException and OutOfMemoryError:
092 * </p>
093 * <pre>
094 * &lt;module name=&quot;IllegalCatch&quot;&gt;
095 *   &lt;property name=&quot;illegalClassNames&quot; value=&quot;ArithmeticException,
096 *               OutOfMemoryError&quot;/&gt;
097 * &lt;/module&gt;
098 * </pre>
099 * <p>Example:</p>
100 * <pre>
101 * try {
102 *     // some code here
103 * } catch (OutOfMemoryError e) { // violation
104 *
105 * }
106 *
107 * try {
108 *     // some code here
109 * } catch (ArithmeticException e) { // violation
110 *
111 * }
112 *
113 * try {
114 *     // some code here
115 * } catch (NullPointerException e) { // OK
116 *
117 * } catch (OutOfMemoryError e) { // violation
118 *
119 * }
120 *
121 * try {
122 *     // some code here
123 * } catch (ArithmeticException | Exception e) {  // violation
124 *
125 * }
126 *
127 * try {
128 *     // some code here
129 * } catch (Exception e) { // OK
130 *
131 * }
132 * </pre>
133 * <p>
134 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
135 * </p>
136 * <p>
137 * Violation Message Keys:
138 * </p>
139 * <ul>
140 * <li>
141 * {@code illegal.catch}
142 * </li>
143 * </ul>
144 *
145 * @since 3.2
146 */
147@StatelessCheck
148public final class IllegalCatchCheck extends AbstractCheck {
149
150    /**
151     * A key is pointing to the warning message text in "messages.properties"
152     * file.
153     */
154    public static final String MSG_KEY = "illegal.catch";
155
156    /** Specify exception class names to reject. */
157    private final Set<String> illegalClassNames = Arrays.stream(new String[] {"Exception", "Error",
158        "RuntimeException", "Throwable", "java.lang.Error", "java.lang.Exception",
159        "java.lang.RuntimeException", "java.lang.Throwable", }).collect(Collectors.toSet());
160
161    /**
162     * Setter to specify exception class names to reject.
163     *
164     * @param classNames
165     *            array of illegal exception classes
166     */
167    public void setIllegalClassNames(final String... classNames) {
168        illegalClassNames.clear();
169        illegalClassNames.addAll(
170                CheckUtil.parseClassNames(classNames));
171    }
172
173    @Override
174    public int[] getDefaultTokens() {
175        return getRequiredTokens();
176    }
177
178    @Override
179    public int[] getRequiredTokens() {
180        return new int[] {TokenTypes.LITERAL_CATCH};
181    }
182
183    @Override
184    public int[] getAcceptableTokens() {
185        return getRequiredTokens();
186    }
187
188    @Override
189    public void visitToken(DetailAST detailAST) {
190        final DetailAST parameterDef =
191            detailAST.findFirstToken(TokenTypes.PARAMETER_DEF);
192        final DetailAST excTypeParent =
193                parameterDef.findFirstToken(TokenTypes.TYPE);
194        final List<DetailAST> excTypes = getAllExceptionTypes(excTypeParent);
195
196        for (DetailAST excType : excTypes) {
197            final FullIdent ident = FullIdent.createFullIdent(excType);
198
199            if (illegalClassNames.contains(ident.getText())) {
200                log(detailAST, MSG_KEY, ident.getText());
201            }
202        }
203    }
204
205    /**
206     * Finds all exception types in current catch.
207     * We need it till we can have few different exception types into one catch.
208     *
209     * @param parentToken - parent node for types (TYPE or BOR)
210     * @return list, that contains all exception types in current catch
211     */
212    private static List<DetailAST> getAllExceptionTypes(DetailAST parentToken) {
213        DetailAST currentNode = parentToken.getFirstChild();
214        final List<DetailAST> exceptionTypes = new LinkedList<>();
215        if (currentNode.getType() == TokenTypes.BOR) {
216            exceptionTypes.addAll(getAllExceptionTypes(currentNode));
217            currentNode = currentNode.getNextSibling();
218            exceptionTypes.add(currentNode);
219        }
220        else {
221            do {
222                exceptionTypes.add(currentNode);
223                currentNode = currentNode.getNextSibling();
224            } while (currentNode != null);
225        }
226        return exceptionTypes;
227    }
228
229}