eu.cqse.check.framework.util.CsLanguageFeatureParser 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.BASE;
import static eu.cqse.check.framework.scanner.ETokenType.BOOL;
import static eu.cqse.check.framework.scanner.ETokenType.BYTE;
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.CONST;
import static eu.cqse.check.framework.scanner.ETokenType.DECIMAL;
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.EQ;
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.IN;
import static eu.cqse.check.framework.scanner.ETokenType.INT;
import static eu.cqse.check.framework.scanner.ETokenType.INTERNAL;
import static eu.cqse.check.framework.scanner.ETokenType.LONG;
import static eu.cqse.check.framework.scanner.ETokenType.LPAREN;
import static eu.cqse.check.framework.scanner.ETokenType.NEW;
import static eu.cqse.check.framework.scanner.ETokenType.OBJECT;
import static eu.cqse.check.framework.scanner.ETokenType.OUT;
import static eu.cqse.check.framework.scanner.ETokenType.PARAMS;
import static eu.cqse.check.framework.scanner.ETokenType.PRIVATE;
import static eu.cqse.check.framework.scanner.ETokenType.PROTECTED;
import static eu.cqse.check.framework.scanner.ETokenType.PUBLIC;
import static eu.cqse.check.framework.scanner.ETokenType.QUESTION;
import static eu.cqse.check.framework.scanner.ETokenType.READONLY;
import static eu.cqse.check.framework.scanner.ETokenType.RPAREN;
import static eu.cqse.check.framework.scanner.ETokenType.SBYTE;
import static eu.cqse.check.framework.scanner.ETokenType.SHORT;
import static eu.cqse.check.framework.scanner.ETokenType.STATIC;
import static eu.cqse.check.framework.scanner.ETokenType.STRING;
import static eu.cqse.check.framework.scanner.ETokenType.THIS;
import static eu.cqse.check.framework.scanner.ETokenType.UINT;
import static eu.cqse.check.framework.scanner.ETokenType.ULONG;
import static eu.cqse.check.framework.scanner.ETokenType.USHORT;
import static eu.cqse.check.framework.scanner.ETokenType.VAR;
import static eu.cqse.check.framework.scanner.ETokenType.VOID;
import static eu.cqse.check.framework.scanner.ETokenType.VOLATILE;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.conqat.lib.commons.assertion.CCSMAssert;
import org.conqat.lib.commons.collections.CollectionUtils;
import org.conqat.lib.commons.collections.UnmodifiableSet;
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.TokenStreamTextUtils;
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.shallowparser.languages.cs.CsShallowParser;
import eu.cqse.check.framework.util.tokens.TokenPattern;
import eu.cqse.check.framework.util.tokens.TokenPatternMatch;
import eu.cqse.check.framework.util.variable.CSVariableUseExtractor;
/**
* Language feature parser for Cs.
*/
public class CsLanguageFeatureParser extends CLikeLanguageFeatureParserBase {
/** IN and OUT keyword token types. */
private static final Set IN_OUT_KEYWORDS = CollectionUtils.asUnmodifiable(EnumSet.of(IN, OUT));
/**
* All token types that can be used to call a constructor from the current or a parent class
*/
private static final UnmodifiableSet ACCESS_KEYWORDS = CollectionUtils
.asUnmodifiable(EnumSet.of(BASE, THIS));
/** All token types that can be used to specify a type. */
private static final UnmodifiableSet ADDITIONAL_TYPE_TOKENS = CollectionUtils
.asUnmodifiable(EnumSet.of(IDENTIFIER, VAR, STRING, OBJECT));
/** All token types that can be used to specify a primitive type. */
public static final UnmodifiableSet PRIMITIVE_TYPE_TOKENS = CollectionUtils.asUnmodifiable(EnumSet.of(
BOOL, CHAR, BYTE, SBYTE, SHORT, INT, LONG, USHORT, UINT, ULONG, FLOAT, DOUBLE, DECIMAL, VOID, QUESTION));
/** All token types that are modifiers for variables or attributes. */
private static final UnmodifiableSet VARIABLE_DECLARATION_MODIFIERS = CollectionUtils
.asUnmodifiable(EnumSet.of(PUBLIC, PRIVATE, INTERNAL, PROTECTED, CONST, READONLY, STATIC, VOLATILE, NEW));
/** Suffix for CS-EventHandlers. */
private static final String EVENT_ARGS_SUFFIX = "EventArgs";
/**
* Group index used in the {@link #ACCESS_CONSTRUCTORS_IN_CONSTRUCTOR_PATTERN} to group the access
* keywords used in a constructor declaration
*/
private static final int ACCESS_KEYWORD_GROUP_INDEX = 0;
/**
* Pattern to match any constructor calls by using {@link #ACCESS_KEYWORDS} in a constructor
* declaration. Examples:
*
* SubClass (int x) : base(x)
* BaseClass () : this(0)
*
*
* @see this
* @see base
*/
private static final TokenPattern ACCESS_CONSTRUCTORS_IN_CONSTRUCTOR_PATTERN = new TokenPattern()
.sequence(IDENTIFIER, new TokenPattern().skipNested(LPAREN, RPAREN, false), COLON)
.sequence(new TokenPattern().alternative(ACCESS_KEYWORDS)).skipNested(LPAREN, RPAREN, false)
.group(ACCESS_KEYWORD_GROUP_INDEX);
/** Constructor. */
public CsLanguageFeatureParser() {
super(ELanguage.CS, CsShallowParser.VALID_IDENTIFIERS, PRIMITIVE_TYPE_TOKENS, ADDITIONAL_TYPE_TOKENS, DOT, ".",
new CSVariableUseExtractor(DOT, CsShallowParser.VALID_IDENTIFIERS));
}
/** Names of classes that count as generic exceptions. */
private static final Set GENERIC_EXCEPTION_NAMES = CollectionUtils.asHashSet("Exception",
"ApplicationException", "SystemException");
/** Name of the Null Pointer Exception class */
public static final UnmodifiableSet NULL_POINTER_EXCEPTION_NAMES = CollectionUtils
.asUnmodifiable(CollectionUtils.asHashSet("NullReferenceException"));
/**
* Names of exception classes that should not be caught, because it is considered bad practise.
*/
public static final UnmodifiableSet SHOULD_NOT_BE_CAUGHT_EXCEPTION_NAMES = CollectionUtils
.asUnmodifiable(CollectionUtils.asHashSet("NullReferenceException", "StackOverflowException",
"OutOfMemoryException", "ThreadAbortException", "ExecutionEngineException",
"IndexOutOfRangeException", "AccessViolationException", "DivideByZeroException",
"InvalidCastException", "ArithmeticException", "TypeInitializationException", "ArgumentException"));
/**
* Returns the imported namespace name from a given "using" entity. If the "using" clause is an
* aliasing clause, null is returned.
*/
@Override
public String getImportName(ShallowEntity entity) {
CCSMAssert.isTrue(isImport(entity), "entity.getType() must be equal to EShallowEntityType.META and "
+ "entity.getSubtype() must be equal to SubTypeNames.USING");
List tokens = entity.ownStartTokens();
if (TokenStreamUtils.containsAll(tokens, EQ)) {
// if this using directive is aliasing a namespace (thus it contains
// a "="), we don't extract the namespace
return null;
}
int lastIdentifierIndex = TokenStreamUtils.lastTokenOfType(tokens, CsShallowParser.VALID_IDENTIFIERS);
if (lastIdentifierIndex == TokenStreamUtils.NOT_FOUND) {
return null;
}
return TokenStreamTextUtils.concatTokenTexts(tokens.subList(1, lastIdentifierIndex + 1));
}
/** Returns whether the given entity is a using directive. */
@Override
public boolean isImport(ShallowEntity entity) {
return entity.getType().equals(EShallowEntityType.META) && entity.getSubtype().equals(SubTypeNames.USING);
}
/** Checks whether the given method is a CS-EventHandler. */
public boolean isEventHandler(ShallowEntity method) {
CCSMAssert.isTrue(method.getType() == EShallowEntityType.METHOD, "method.getType() must be \"METHOD\"");
if (!hasVoidReturnType(method)) {
return false;
}
List> parameterTokens = getSplitParameterTokens(method);
if (parameterTokens.size() != 2) {
return false;
}
List firstParameter = parameterTokens.get(0);
List secondParameter = parameterTokens.get(1);
if (!TokenStreamUtils.hasTypes(firstParameter, ETokenType.OBJECT, ETokenType.IDENTIFIER)) {
return false;
}
String secondType = getModifiersAndTypeFromTokens(secondParameter).getSecond();
return secondType.endsWith(EVENT_ARGS_SUFFIX);
}
/** Returns whether the given entity is partial. */
public boolean isPartial(ShallowEntity entity) {
return TokenStreamUtils.firstTokenOfType(entity.ownStartTokens(),
ETokenType.PARTIAL) != TokenStreamUtils.NOT_FOUND;
}
/**
* Returns all parameter tokens of constructor calls which come from a new constructor declaration.
* If there's no constructor call, this will return an empty list. In C# this is possible by using
* the standard constructor declaration followed by a colon and either
*
* base (...)
(to call it from the base-class) or
* this (...)
(to call it from the own class)
*
*
* Example: TestClass () : this (null) {}
*/
public List getConstructorCallParameterTokens(ShallowEntity constructor) {
TokenPatternMatch match = ACCESS_CONSTRUCTORS_IN_CONSTRUCTOR_PATTERN
.findFirstMatch(constructor.includedTokens());
if (match == null) {
return CollectionUtils.emptyList();
}
return TokenStreamUtils.tokensBetweenWithNesting(match.groupTokens(ACCESS_KEYWORD_GROUP_INDEX), LPAREN, RPAREN);
}
@Override
public boolean containsVariableLengthArgumentListIndicator(List tokens) {
return TokenStreamUtils.containsAny(tokens, PARAMS, THIS, EQ);
}
/**
* Filters IN and OUT tokens from generics.
*
* {@inheritDoc}
*/
@Override
protected List filterGenericTokens(List genericTokens) {
return CollectionUtils.filter(genericTokens, token -> !IN_OUT_KEYWORDS.contains(token.getType()));
}
/** Returns a list of all class names the given entity inherits from. */
public List getParentNames(ShallowEntity type) {
CCSMAssert.isTrue(type.getType() == EShallowEntityType.TYPE, "type.getType() must be \"TYPE\"");
List inheritanceTokens = TokenStreamUtils.tokensBetween(type.ownStartTokens(), ETokenType.COLON,
ETokenType.LBRACE);
if (inheritanceTokens.isEmpty()) {
return CollectionUtils.emptyList();
}
List> splitInheritanceTokens = TokenStreamUtils.splitWithNesting(inheritanceTokens,
ETokenType.COMMA, ETokenType.LT, ETokenType.GT);
return TokenStreamTextUtils.concatAllTokenTexts(splitInheritanceTokens);
}
/**
* Returns all variable names from a variable declaration within a for statement.
*/
public List getVariableNamesFromForLikeTokens(List forLikeTokens, ETokenType endToken) {
List variableDeclarationTokens = getVariableTokensFromForLikeTokens(forLikeTokens, endToken);
return getVariableDeclarationNamesFromTokens(variableDeclarationTokens);
}
/**
* Returns all variable names from the given variable declaration tokens. Names are only returned if
* they are newly introduced, that means, that e. g. the variable "t" from "using(t) {" will not be
* returned. This method does not support generic types at the moment.
*/
public List getVariableDeclarationNamesFromTokens(List variableDeclarationTokens) {
List variableNames = new ArrayList<>();
int offset = 0;
while (variableDeclarationTokens.size() > 0
&& VARIABLE_DECLARATION_MODIFIERS.contains(variableDeclarationTokens.get(offset).getType())) {
offset += 1;
}
if (variableDeclarationTokens.size() >= 2 + offset) {
IToken token1 = variableDeclarationTokens.get(offset);
IToken token2 = variableDeclarationTokens.get(1 + offset);
if (isVariableDeclaration(token1, token2)) {
variableNames.add(token2);
addAdditionalVariableNameTokens(variableDeclarationTokens, offset, variableNames);
}
}
return variableNames;
}
/** Adds additional variable name tokens. */
private static void addAdditionalVariableNameTokens(List variableDeclarationTokens, int offset,
List variableNames) {
int parenthesisNesting = 0;
for (int i = 2 + offset; i < variableDeclarationTokens.size(); ++i) {
IToken token = variableDeclarationTokens.get(i);
ETokenType tokenType = token.getType();
if (tokenType == ETokenType.LPAREN) {
parenthesisNesting += 1;
} else if (tokenType == ETokenType.RPAREN) {
parenthesisNesting -= 1;
} else if (tokenType == ETokenType.IDENTIFIER && parenthesisNesting == 0
&& variableDeclarationTokens.get(i - 1).getType() == ETokenType.COMMA) {
variableNames.add(token);
}
}
}
/** Returns whether the two given tokens indicate a variable declaration. */
private boolean isVariableDeclaration(IToken token1, IToken token2) {
return (typeTokens.contains(token1.getType()) && validIdentifiers.contains(token2.getType()));
}
/**
* Determines whether the given class name represents a generic exception.
*/
public boolean isGenericExceptionClass(String className) {
return GENERIC_EXCEPTION_NAMES.contains(className);
}
/**
* Extracts the namespace from a list of top level shallow entities. If empty is returned, no
* namespace is set for the given entities.
*/
public static Optional getNamespace(List shallowEntities) {
return shallowEntities.stream().filter(entity -> entity.getType() == EShallowEntityType.MODULE
&& entity.getSubtype().equals(SubTypeNames.NAMESPACE)).map(ShallowEntity::getName).findFirst();
}
}