All Downloads are FREE. Search and download functionalities are using the official Maven repository.

eu.cqse.check.framework.util.clike.ConditionalBlockGroupExtractor Maven / Gradle / Ivy

Go to download

The Teamscale Custom Check API allows users to extend Teamscale by writing custom analyses that create findings.

There is a newer version: 2024.7.2
Show newest version
/*
 * 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;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy