eu.cqse.check.framework.util.ObjectiveCLanguageFeatureParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of teamscale-check-api Show documentation
Show all versions of teamscale-check-api Show documentation
The Teamscale Custom Check API allows users to extend Teamscale by writing custom analyses that create findings.
/*
* Copyright (c) CQSE GmbH
*
* 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 eu.cqse.check.framework.util;
import static eu.cqse.check.framework.scanner.ETokenType.BOOL;
import static eu.cqse.check.framework.scanner.ETokenType.CHAR;
import static eu.cqse.check.framework.scanner.ETokenType.COLON;
import static eu.cqse.check.framework.scanner.ETokenType.DOT;
import static eu.cqse.check.framework.scanner.ETokenType.DOUBLE;
import static eu.cqse.check.framework.scanner.ETokenType.FLOAT;
import static eu.cqse.check.framework.scanner.ETokenType.IDENTIFIER;
import static eu.cqse.check.framework.scanner.ETokenType.INT;
import static eu.cqse.check.framework.scanner.ETokenType.LONG;
import static eu.cqse.check.framework.scanner.ETokenType.SHORT;
import static eu.cqse.check.framework.scanner.ETokenType.SIGNED;
import static eu.cqse.check.framework.scanner.ETokenType.UNSIGNED;
import static eu.cqse.check.framework.scanner.ETokenType.VOID;
import static eu.cqse.check.framework.shallowparser.SubTypeNames.SIMPLE_STATEMENT;
import static eu.cqse.check.framework.shallowparser.TokenStreamUtils.NOT_FOUND;
import static eu.cqse.check.framework.shallowparser.framework.EShallowEntityType.STATEMENT;
import java.util.EnumSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.conqat.lib.commons.collections.CollectionUtils;
import org.conqat.lib.commons.collections.Pair;
import eu.cqse.check.framework.scanner.ELanguage;
import eu.cqse.check.framework.scanner.ETokenType;
import eu.cqse.check.framework.scanner.IToken;
import eu.cqse.check.framework.shallowparser.SubTypeNames;
import eu.cqse.check.framework.shallowparser.TokenStreamUtils;
import eu.cqse.check.framework.shallowparser.framework.EShallowEntityType;
import eu.cqse.check.framework.shallowparser.framework.ShallowEntity;
import eu.cqse.check.framework.util.variable.CLikeVariableUseExtractor;
/** Language feature parser for Objective-C. */
public class ObjectiveCLanguageFeatureParser extends ObjectiveCLanguageFeatureParserBase {
/** All token types that can be used to specify a type. */
private static final EnumSet PRIMITIVE_TYPE_TOKENS = EnumSet.of(SIGNED, UNSIGNED, BOOL, CHAR, SHORT,
INT, LONG, FLOAT, DOUBLE, VOID);
/** Names of classes that count as generic exceptions. */
private static final Set GENERIC_EXCEPTION_NAMES = CollectionUtils.asHashSet("NSException", "id");
/** The names of unconditional exit statements for a switch. */
private static final Set UNCONDITIONAL_SWITCH_EXIT_STATEMENT_NAMES = CollectionUtils.asHashSet("break",
"return", "goto", "continue", "@throw", "abort", "exit", "_Exit");
/**
* The names of unconditional exit statements for a nested switch. These statements will exit the
* "outer" switch, even if executed in a "nested" switch. In the following example, although case 1
* does not contain an exit statement itself, still it terminates unconditionally since all control
* flows of the nested switch leads to a termination:
*
*
* switch (x) {
* case 1:
* // do something
* switch (y) {
* case 2:
* // do something
* return -1;
* case 3:
* // something
* exit(0);
* default:
* return 0;
* }
* default:
* break;
* }
*
*
*/
private static final Set UNCONDITIONAL_NESTED_SWITCH_EXIT_STATEMENT_NAMES = CollectionUtils
.asHashSet("return", "@throw", "abort", "exit", "_Exit");
ObjectiveCLanguageFeatureParser() {
super(ELanguage.OBJECTIVE_C, PRIMITIVE_TYPE_TOKENS,
new CLikeVariableUseExtractor(EnumSet.of(DOT), VALID_IDENTIFIERS));
}
@Override
public boolean isImport(ShallowEntity entity) {
return entity.getType() == EShallowEntityType.META && entity.getSubtype().equals(SubTypeNames.AT_IMPORT);
}
/**
* Determines whether the given class name represents a generic exception.
*/
public static boolean isGenericExceptionClass(String className) {
return GENERIC_EXCEPTION_NAMES.contains(className);
}
/**
* Returns a list of parameter tokens for the given tokensAfterMethodName. Example for
* -(void)foo1:(int) x1:(int) x2
. Returns [int, x1, :, int, x2]
.
*/
@Override
public List extractParameterTokens(List methodStartTokens) {
return extractObjectiveCParameterTokens(methodStartTokens);
}
/**
* Returns whether the given entity is an unconditional exit statement for a case statement. This
* checks {@link #isCaseOrDefaultExitStatement(ShallowEntity, boolean)}, but also recurses in case
* of blocks and conditionals. Even though there are several commonalities between Objective-C and
* C/C++, there is still a few language differences for deciding on whether switch cases terminate
* unconditionally, such as the use of instructions like "@throw" rather than "throw". Also, if
* {@code isNestedSwitch} is true, it considers only "exit statements" that will exit the entire
* switch hierarchy (e.g., "return", "@throw", "exit()").
*/
public static boolean isUnconditionalSwitchExit(ShallowEntity lastEntity, boolean isNestedSwitch) {
if (lastEntity.getSubtype().equals(SubTypeNames.ELSE)) {
return isUnconditionalSwitchExitGivenElseAsLastEntity(lastEntity, isNestedSwitch);
}
return isCaseOrDefaultExitStatement(lastEntity, isNestedSwitch);
}
/**
* Given a) that the last entity parameter is an else statement and b) whether we are in a nested
* switch block or not, this method determines whether (or not) the last entity in a)
* unconditionally exits the switch block.
*/
private static boolean isUnconditionalSwitchExitGivenElseAsLastEntity(ShallowEntity lastEntity,
boolean isNestedSwitch) {
// check the "else"
if (!isUnconditionalSwitchExit(lastEntity.getChildren(), isNestedSwitch)) {
return false;
}
List siblings = lastEntity.getParent().getChildren();
int index = siblings.indexOf(lastEntity);
index -= 1;
// check any else-if
while (index > 0 && SubTypeNames.ELSE_IF.equals(siblings.get(index).getSubtype())) {
if (!isUnconditionalSwitchExit(siblings.get(index).getChildren(), isNestedSwitch)) {
return false;
}
index -= 1;
}
// check the "if"
return isUnconditionalSwitchExit(siblings.get(index).getChildren(), isNestedSwitch);
}
private static boolean isUnconditionalSwitchExit(List entities, boolean isNestedSwitch) {
return !entities.isEmpty() && isUnconditionalSwitchExit(CollectionUtils.getLast(entities), isNestedSwitch);
}
/**
* Returns whether the given entity is a valid exit statement for a case statement
*/
public static boolean isCaseOrDefaultExitStatement(ShallowEntity entity, boolean isNestedSwitch) {
return (entity.getType() == STATEMENT && entity.getSubtype().equals(SIMPLE_STATEMENT)
&& getUnconditionalSwitchExitStatementNames(isNestedSwitch).contains(entity.getName()));
}
/**
* Returns the collection of exit statement names to check for whether in a nested switch block or
* not. In cases of nested switch statements, we consider only exit statements that guarantee that
* all the nested control-flow paths are terminated. In other words, in nested switches, we check if
* the outermost switch breaks out by having its nested switches terminating with certain keywords
* such as 'exit', 'throw, and 'return'.
*/
private static Set getUnconditionalSwitchExitStatementNames(boolean isNestedSwitch) {
if (isNestedSwitch) {
return UNCONDITIONAL_NESTED_SWITCH_EXIT_STATEMENT_NAMES;
}
return UNCONDITIONAL_SWITCH_EXIT_STATEMENT_NAMES;
}
/** Returns the parent class name of the entity. */
public static Optional getSuperClassName(ShallowEntity entity) {
int inheritanceSequenceIndex = TokenStreamUtils.firstTokenOfTypeSequence(entity.ownStartTokens(), 0, COLON,
IDENTIFIER);
if (inheritanceSequenceIndex == NOT_FOUND || entity.ownStartTokens().size() == inheritanceSequenceIndex + 1) {
return Optional.empty();
}
return Optional.of(entity.ownStartTokens().get(inheritanceSequenceIndex + 1).getText());
}
/** {@inheritDoc} */
@Override
public Pair, String> getReturnTypeAndModifiers(ShallowEntity method) {
return getObjectiveCReturnTypeAndModifiers(method);
}
}