001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2022 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.Set;
024import java.util.stream.Collectors;
025
026import com.puppycrawl.tools.checkstyle.StatelessCheck;
027import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
028import com.puppycrawl.tools.checkstyle.api.DetailAST;
029import com.puppycrawl.tools.checkstyle.api.FullIdent;
030import com.puppycrawl.tools.checkstyle.api.TokenTypes;
031import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
032
033/**
034 * <p>
035 * Checks that certain exception types do not appear in a {@code catch} statement.
036 * </p>
037 * <p>
038 * Rationale: catching {@code java.lang.Exception}, {@code java.lang.Error} or
039 * {@code java.lang.RuntimeException} is almost never acceptable.
040 * Novice developers often simply catch Exception in an attempt to handle
041 * multiple exception classes. This unfortunately leads to code that inadvertently
042 * catches {@code NullPointerException}, {@code OutOfMemoryError}, etc.
043 * </p>
044 * <ul>
045 * <li>
046 * Property {@code illegalClassNames} - Specify exception class names to reject.
047 * Type is {@code java.lang.String[]}.
048 * Default value is {@code Error, Exception, RuntimeException, Throwable, java.lang.Error,
049 * java.lang.Exception, java.lang.RuntimeException, java.lang.Throwable}.
050 * </li>
051 * </ul>
052 * <p>
053 * To configure the check:
054 * </p>
055 * <pre>
056 * &lt;module name=&quot;IllegalCatch&quot;/&gt;
057 * </pre>
058 * <p>Example:</p>
059 * <pre>
060 * try {
061 *     // some code here
062 * } catch (Exception e) { // violation
063 *
064 * }
065 *
066 * try {
067 *     // some code here
068 * } catch (ArithmeticException e) { // OK
069 *
070 * } catch (Exception e) { // violation, catching Exception is illegal
071 *                           and order of catch blocks doesn't matter
072 * }
073 *
074 * try {
075 *     // some code here
076 * } catch (ArithmeticException | Exception e) { // violation, catching Exception is illegal
077 *
078 * }
079 *
080 * try {
081 *     // some code here
082 * } catch (ArithmeticException e) { // OK
083 *
084 * }
085 *
086 * </pre>
087 * <p>
088 * To configure the check to override the default list
089 * with ArithmeticException and OutOfMemoryError:
090 * </p>
091 * <pre>
092 * &lt;module name=&quot;IllegalCatch&quot;&gt;
093 *   &lt;property name=&quot;illegalClassNames&quot; value=&quot;ArithmeticException,
094 *               OutOfMemoryError&quot;/&gt;
095 * &lt;/module&gt;
096 * </pre>
097 * <p>Example:</p>
098 * <pre>
099 * try {
100 *     // some code here
101 * } catch (OutOfMemoryError e) { // violation
102 *
103 * }
104 *
105 * try {
106 *     // some code here
107 * } catch (ArithmeticException e) { // violation
108 *
109 * }
110 *
111 * try {
112 *     // some code here
113 * } catch (NullPointerException e) { // OK
114 *
115 * } catch (OutOfMemoryError e) { // violation
116 *
117 * }
118 *
119 * try {
120 *     // some code here
121 * } catch (ArithmeticException | Exception e) {  // violation
122 *
123 * }
124 *
125 * try {
126 *     // some code here
127 * } catch (Exception e) { // OK
128 *
129 * }
130 * </pre>
131 * <p>
132 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
133 * </p>
134 * <p>
135 * Violation Message Keys:
136 * </p>
137 * <ul>
138 * <li>
139 * {@code illegal.catch}
140 * </li>
141 * </ul>
142 *
143 * @since 3.2
144 */
145@StatelessCheck
146public final class IllegalCatchCheck extends AbstractCheck {
147
148    /**
149     * A key is pointing to the warning message text in "messages.properties"
150     * file.
151     */
152    public static final String MSG_KEY = "illegal.catch";
153
154    /** Specify exception class names to reject. */
155    private final Set<String> illegalClassNames = Arrays.stream(new String[] {"Exception", "Error",
156        "RuntimeException", "Throwable", "java.lang.Error", "java.lang.Exception",
157        "java.lang.RuntimeException", "java.lang.Throwable", }).collect(Collectors.toSet());
158
159    /**
160     * Setter to specify exception class names to reject.
161     *
162     * @param classNames
163     *            array of illegal exception classes
164     */
165    public void setIllegalClassNames(final String... classNames) {
166        illegalClassNames.clear();
167        illegalClassNames.addAll(
168                CheckUtil.parseClassNames(classNames));
169    }
170
171    @Override
172    public int[] getDefaultTokens() {
173        return getRequiredTokens();
174    }
175
176    @Override
177    public int[] getRequiredTokens() {
178        return new int[] {TokenTypes.LITERAL_CATCH};
179    }
180
181    @Override
182    public int[] getAcceptableTokens() {
183        return getRequiredTokens();
184    }
185
186    @Override
187    public void visitToken(DetailAST detailAST) {
188        final DetailAST parameterDef =
189            detailAST.findFirstToken(TokenTypes.PARAMETER_DEF);
190        final DetailAST excTypeParent =
191                parameterDef.findFirstToken(TokenTypes.TYPE);
192
193        DetailAST currentNode = excTypeParent.getFirstChild();
194        while (currentNode != null) {
195            final FullIdent ident = FullIdent.createFullIdent(currentNode);
196            if (illegalClassNames.contains(ident.getText())) {
197                log(detailAST, MSG_KEY, ident.getText());
198            }
199            currentNode = currentNode.getNextSibling();
200        }
201    }
202}