001/*-------------------------------------------------------------------------+
002|                                                                          |
003| Copyright (c) 2005-2018 The ConQAT Project                               |
004|                                                                          |
005| Licensed under the Apache License, Version 2.0 (the "License");          |
006| you may not use this file except in compliance with the License.         |
007| You may obtain a copy of the License at                                  |
008|                                                                          |
009|    http://www.apache.org/licenses/LICENSE-2.0                            |
010|                                                                          |
011| Unless required by applicable law or agreed to in writing, software      |
012| distributed under the License is distributed on an "AS IS" BASIS,        |
013| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
014| See the License for the specific language governing permissions and      |
015| limitations under the License.                                           |
016|                                                                          |
017+-------------------------------------------------------------------------*/
018package eu.cqse.check.clike;
019
020import java.util.List;
021import java.util.Optional;
022
023import eu.cqse.check.base.EntityCheckBase;
024import eu.cqse.check.framework.core.CheckException;
025import eu.cqse.check.framework.scanner.ETokenType;
026import eu.cqse.check.framework.scanner.IToken;
027import eu.cqse.check.framework.shallowparser.SubTypeNames;
028import eu.cqse.check.framework.shallowparser.TokenStreamTextUtils;
029import eu.cqse.check.framework.shallowparser.TokenStreamUtils;
030import eu.cqse.check.framework.shallowparser.framework.ShallowEntity;
031
032/**
033 * Base class for custom checks which reveal when exceptions are catched.
034 */
035public abstract class CatchExceptionCheckBase extends EntityCheckBase {
036
037        @Override
038        protected String getXPathSelectionString() {
039                return "//METHOD//STATEMENT[subtype('" + SubTypeNames.CATCH + "') or subtype('" + SubTypeNames.AT_CATCH + "')]";
040        }
041
042        /**
043         * {@inheritDoc}
044         *
045         * Determines whether the catch block at the {@link ShallowEntity} contains a
046         * forbidden exception.
047         */
048        @Override
049        protected void processEntity(ShallowEntity entity) throws CheckException {
050                List<IToken> catchArgumentTokens = TokenStreamUtils.tokensBetween(entity.ownStartTokens(), ETokenType.LPAREN,
051                                ETokenType.RPAREN);
052
053                int currentIndex = 0;
054                int numberOfTokens = catchArgumentTokens.size();
055
056                if (numberOfTokens == 0) {
057                        if (createFindingForException(Optional.empty())) {
058                                createFinding(getFindingMessage(Optional.empty()), entity);
059                        }
060                        return;
061                }
062
063                do {
064                        int endOfClassName = findLastTokenIndexOfClassName(catchArgumentTokens, currentIndex);
065
066                        if (endOfClassName != TokenStreamUtils.NOT_FOUND) {
067                                List<IToken> typeTokens = catchArgumentTokens.subList(currentIndex, endOfClassName + 1);
068
069                                String className = TokenStreamTextUtils.concatTokenTexts(typeTokens);
070
071                                if (createFindingForException(Optional.of(className))) {
072                                        createFinding(getFindingMessage(Optional.of(className)), entity);
073                                }
074
075                                if (numberOfTokens > endOfClassName + 1
076                                                && catchArgumentTokens.get(endOfClassName + 1).getType() == ETokenType.OR) {
077                                        currentIndex = endOfClassName + 2;
078                                } else {
079                                        break;
080                                }
081                        } else {
082                                break;
083                        }
084                } while (currentIndex < numberOfTokens);
085        }
086
087        /**
088         * Finds the index of the last token belonging to a class name starting at the
089         * start index in the tokens list.
090         */
091        private static int findLastTokenIndexOfClassName(List<IToken> tokens, int startIndex) {
092                if (tokens.get(startIndex).getType() == ETokenType.ID) {
093                        // The any type in Objective-C is always a single "id" token.
094                        return startIndex;
095                }
096                if (tokens.get(startIndex).getType() != ETokenType.IDENTIFIER) {
097                        return TokenStreamUtils.NOT_FOUND;
098                }
099
100                int currentIndex = startIndex;
101                int numberOfTokens = tokens.size();
102
103                while (numberOfTokens > currentIndex + 2 && tokens.get(currentIndex + 1).getType() == ETokenType.DOT
104                                && tokens.get(currentIndex + 2).getType() == ETokenType.IDENTIFIER) {
105                        currentIndex = currentIndex + 2;
106                }
107
108                return currentIndex;
109        }
110
111        /**
112         * Returns true if this check should create a finding for the catched exception.
113         * Class name may be empty in anonymous catch blocks.
114         */
115        protected abstract boolean createFindingForException(Optional<String> className) throws CheckException;
116
117        /**
118         * Creates a message for a finding with the given exception class name. Class
119         * name may be empty in anonymous catch blocks.
120         */
121        protected abstract String getFindingMessage(Optional<String> className);
122}