eu.cqse.check.framework.util.clike.ConditionalBlockGroupExtractor 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.clike;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.conqat.lib.commons.collections.CollectionUtils;
import eu.cqse.check.framework.core.CheckException;
import eu.cqse.check.framework.preprocessor.PreprocessorUtils;
import eu.cqse.check.framework.shallowparser.TokenStreamUtils;
import eu.cqse.check.framework.shallowparser.framework.EShallowEntityType;
import eu.cqse.check.framework.shallowparser.framework.ShallowEntity;
/**
* This class provides various helper methods to extract conditional block groups out of the
* abstract syntax tree or Lists of ShallowEntities.
*/
public class ConditionalBlockGroupExtractor {
/**
* Recursively checks the entity and its children for corresponding statements in conditional
* expressions, except for statements in macro definitions (as we do not want to create findings
* within macro definitions). Use this for example on the Document Root coming from the AST to find
* all {@link ConditionalBlockGroupBase} in the Document.
*
* @param entity
* the entity whose children are checked for if/else/else if or switch/case statements
* @throws CheckException
*/
public static List findCorrespondingBlockGroups(ShallowEntity entity)
throws CheckException {
// ignore conditional blocks in expanded code from preprocessing (macros, see TS-39533)
if (PreprocessorUtils.isEntityMacroExpanded(entity)) {
return CollectionUtils.emptyList();
}
List groupedBlocks = new ArrayList<>();
EConditionalType blockType = ConditionalBlockUtils.getBlockTypeFromEntity(entity);
if (blockType != null) {
switch (blockType) {
case IF:
groupedBlocks.add(createIfBlockGroup(entity));
break;
case SWITCH:
groupedBlocks.add(createSwitchBlockGroup(entity));
break;
default:
break;
}
}
groupedBlocks.addAll(findCorrespondingBlockGroups(entity.getChildren()));
return groupedBlocks;
}
/**
* Recursively checks the given entities and their children for corresponding statements in
* conditional expressions.
*
* @param entities
* the entities whose children are checked for if/else/else if or switch/case statements
* @throws CheckException
*/
public static List findCorrespondingBlockGroups(List entities)
throws CheckException {
List groupedBlocks = new ArrayList<>();
if (entities.isEmpty()) {
return groupedBlocks;
}
ShallowEntity parent = entities.get(0).getParent();
for (ShallowEntity entity : entities) {
ShallowEntity actualParent = entity.getParent();
if (actualParent != parent) {
String message = "The ShallowEntity: [" + entity.getSubtype().toUpperCase() + "] spanning lines ";
message += entity.getStartLine() + " through " + entity.getEndLine();
message += " has a different parent from its siblings";
message += " .Expected parent: [" + parent.getSubtype() + "] spanning lines " + parent.getStartLine()
+ " through " + parent.getEndLine();
message += " .Actual parent: [" + actualParent.getSubtype() + "] spanning lines "
+ actualParent.getStartLine() + " through " + actualParent.getEndLine();
message += " File: " + TokenStreamUtils.determineMostSpecificOrigin(entity.getAllTokensOfFile());
throw new IllegalArgumentException(message);
}
List childrenBlockGroups = findCorrespondingBlockGroups(entity);
if (!childrenBlockGroups.isEmpty()) {
groupedBlocks.addAll(childrenBlockGroups);
}
}
return groupedBlocks;
}
/**
* Finds corresponding else/ else if blocks for the given if statement, except for blocks in macro
* definitions (as we do not want to create findings within macro definitions).
*
* @param ifEntity
* the if statement for which the else/ else if blocks are searched
*/
private static IfBlockGroup createIfBlockGroup(ShallowEntity ifEntity) {
List neighboringEntities = Objects.requireNonNull(ifEntity.getParent()).getChildren();
int ifIndex = neighboringEntities.indexOf(ifEntity);
IfBlockGroup ifblockGroup = new IfBlockGroup();
ConditionalBlock ifBlock = new ConditionalBlock(ConditionalBlockUtils.getBlockTypeFromEntity(ifEntity),
ConditionalBlockUtils.extractCondition(ifEntity), ifEntity.getChildren());
ifblockGroup.getCorrespondingBlocks().add(ifBlock);
for (int nextIndex = ifIndex + 1; nextIndex < neighboringEntities.size(); nextIndex++) {
ShallowEntity nextEntity = neighboringEntities.get(nextIndex);
// ignore blocks in expanded code from preprocessing (macros)
if (PreprocessorUtils.isEntityMacroExpanded(nextEntity)) {
continue;
}
EConditionalType blockType = ConditionalBlockUtils.getBlockTypeFromEntity(nextEntity);
if (ConditionalBlockUtils.BLOCKS_CORRESPONDING_TO_IF.contains(blockType)) {
if (blockType == EConditionalType.ELSE_IF) {
ifblockGroup.getCorrespondingBlocks().add(new ConditionalBlock(blockType,
ConditionalBlockUtils.extractCondition(nextEntity), nextEntity.getChildren()));
} else if (blockType == EConditionalType.ELSE) {
ifblockGroup.getCorrespondingBlocks()
.add(new ConditionalBlock(blockType, nextEntity.getChildren()));
break;
}
} else {
break;
}
}
return ifblockGroup;
}
/**
* Finds corresponding case blocks for the given switch statement, except for blocks in macro
* definitions (as we do not want to create findings within macro definitions).
*
* @param switchEntity
* the switch statement for which the cases are searched
*/
private static SwitchBlockGroup createSwitchBlockGroup(ShallowEntity switchEntity) {
List metas = findMetaStatements(switchEntity);
List innerStatements = switchEntity.getChildren();
SwitchBlockGroup switchBlockGroup = new SwitchBlockGroup();
switchBlockGroup.setSurroundingEntity(switchEntity);
for (int i = 0; i < metas.size(); i++) {
ShallowEntity currentMeta = metas.get(i);
// ignore blocks in expanded code from preprocessing (macros, see TS-40184)
if (PreprocessorUtils.isEntityMacroExpanded(currentMeta)) {
continue;
}
int currentMetaParentIndex = innerStatements.indexOf(currentMeta);
int nextMetaParentIndex;
if (i + 1 < metas.size()) {
nextMetaParentIndex = innerStatements.indexOf(metas.get(i + 1));
} else {
nextMetaParentIndex = innerStatements.size();
}
List metaStatements = innerStatements.subList(currentMetaParentIndex + 1,
nextMetaParentIndex);
if (metaStatements.isEmpty()) {
// empty statements are not relevant for this check (e.g. fallthrough case
// statement)
continue;
}
EConditionalType blockType = ConditionalBlockUtils.getBlockTypeFromEntity(currentMeta);
// If we are unable to determine the block type, we ignore the block. For
// example, this happens on using keywords (such as `property`) as identifiers
// in case blocks (see TS-36475).
if (blockType == null) {
continue;
}
if (blockType == EConditionalType.CASE) {
switchBlockGroup.getCorrespondingBlocks().add(new ConditionalBlock(blockType,
ConditionalBlockUtils.extractCondition(currentMeta), metaStatements));
} else {
switchBlockGroup.getCorrespondingBlocks().add(new ConditionalBlock(blockType, metaStatements));
}
}
return switchBlockGroup;
}
/**
* Searches for the meta statements inside the switch case construct. Meta statements are case and
* default statements.
*/
private static List findMetaStatements(ShallowEntity entity) {
List metas = new ArrayList<>();
for (ShallowEntity innerStatement : entity.getChildren()) {
if (innerStatement.getType() == EShallowEntityType.META) {
metas.add(innerStatement);
}
}
return metas;
}
}