001package eu.cqse.check.framework.core; 002 003import java.util.ArrayList; 004import java.util.Arrays; 005import java.util.List; 006 007import org.conqat.lib.commons.assertion.CCSMAssert; 008import org.conqat.lib.commons.collections.CollectionUtils; 009import org.conqat.lib.commons.collections.PairList; 010import org.conqat.lib.commons.region.OffsetBasedRegion; 011import org.jaxen.BaseXPath; 012import org.jaxen.JaxenException; 013 014import eu.cqse.check.framework.core.phase.ECodeViewOption; 015import eu.cqse.check.framework.core.phase.ECodeViewOption.ETextViewOption; 016import eu.cqse.check.framework.core.xpath.CheckXPathUtils; 017import eu.cqse.check.framework.scanner.IToken; 018import eu.cqse.check.framework.shallowparser.framework.EShallowEntityType; 019import eu.cqse.check.framework.shallowparser.framework.ShallowEntity; 020import eu.cqse.check.framework.util.CppLanguageFeatureParser; 021 022/** 023 * Abstract base class for custom checks. A check is instantiated 024 * ({@Link CheckInstance}) and initialized with a check context when its 025 * executing processor is set up (CheckProcessor.initializeChecks()) based on 026 * {@link CheckInfo} Objects. It is destroyed, when the executing processor has 027 * finished execution. 028 */ 029public abstract class CheckImplementationBase { 030 031 /** 032 * The check context that is used to access custom check specific functionality. 033 */ 034 protected ICheckContext context = null; 035 036 /** 037 * Sets the context. Must not be called if the context has already been set. 038 */ 039 public void setContext(ICheckContext context) { 040 CCSMAssert.isTrue(this.context == null, "The context has already been set."); 041 this.context = context; 042 } 043 044 /** 045 * Initializes the custom check. This is called after the parameters have been 046 * set, but before the check is executed and before the context is set. 047 * 048 * This method is called only once for a batch of files that need to be 049 * analyzed. Since the context is file-specific it can't be accessed here. This 050 * method is intended for preprocessing or parsing of the parameters. 051 * 052 * @throws CheckException 053 * can be thrown by subclasses 054 */ 055 public void initialize() throws CheckException { 056 // empty dummy implementation 057 } 058 059 /** Executes the custom check with the given context. */ 060 public abstract void execute() throws CheckException; 061 062 /** 063 * Can be used to specifiy the code representation. See {@link ECodeViewOption} 064 * for details, the default is {@link ECodeViewOption#FILTERED}. See the 065 * {@linkplain <a href= 066 * "https://docs.google.com/presentation/d/1xNfWJSG057R9VCwEN3RGiV7C5u2uduLDlbQHy-ma7IY"> 067 * Teamscale Code Processing Pipeline</a>} 068 */ 069 protected ECodeViewOption getCodeViewOption() { 070 return ECodeViewOption.FILTERED; 071 } 072 073 /** 074 * Selects all shallow entities from the abstract syntax tree, that match the 075 * given xpath-expression. Set CodeViewOption via overwriting 076 * {@link #getCodeViewOption()}. This method can be used, if the executing check 077 * requests the {@link ECheckParameter#ABSTRACT_SYNTAX_TREE} parameter. 078 */ 079 protected List<ShallowEntity> select(String xPathExpression) throws CheckException { 080 return select(context.getRootEntity(getCodeViewOption()), xPathExpression); 081 } 082 083 /** 084 * Selects all shallow entities from the abstract syntax tree, that match the 085 * given xpath-expression. Code representation is 086 * {@link ECodeViewOption#UNFILTERED_PREPROCESSED}. 087 */ 088 protected List<ShallowEntity> selectUnfilteredPreProcessed(String xPathExpression) throws CheckException { 089 return select(context.getRootEntity(ECodeViewOption.UNFILTERED_PREPROCESSED), xPathExpression); 090 } 091 092 /** 093 * Creates a xpath-expression to select entities with the given type and the 094 * optional subtype alternatives eg. "//METHOD[subtype('function') | 095 * subtype('function declaration')]". 096 */ 097 protected static String createSelectionPattern(EShallowEntityType entityType, String... subtypeAlternatives) { 098 String typeSelector = "//" + entityType.name(); 099 if (subtypeAlternatives.length == 0) { 100 return typeSelector; 101 } 102 return typeSelector + "[" + Arrays.stream(subtypeAlternatives).map(subtype -> "subtype('" + subtype + "')") 103 .reduce((first, second) -> first + " | " + second).get() + "]"; 104 } 105 106 /** 107 * Selects all shallow entities starting from the given entity, that match the 108 * given xpath-expression. This method is still selecting entities from the 109 * whole AST as long as the xPath expression does not limit the selection to a 110 * special axis. For more information have a look at 111 * http://www.w3schools.com/xsl/xpath_syntax.asp 112 */ 113 @SuppressWarnings("unchecked") 114 public static List<ShallowEntity> select(ShallowEntity entity, String xPathExpression) throws CheckException { 115 BaseXPath xPath = CheckXPathUtils.getXPath(xPathExpression); 116 try { 117 Object result = xPath.evaluate(entity); 118 if (result instanceof List<?>) { 119 return (List<ShallowEntity>) result; 120 } 121 throw new CheckException( 122 "Evaluating the xPath expression '" + xPathExpression + "' returned unexpected result: " + result); 123 } catch (JaxenException e) { 124 throw new CheckException(e); 125 } 126 } 127 128 /** 129 * Creates a finding with the given message for the whole analyzed token 130 * element. 131 */ 132 protected void createFinding(String message) throws CheckException { 133 context.createFindingForElement(message); 134 } 135 136 /** 137 * Creates a finding with the given message at the given token. 138 * 139 * Use this method, if the token is determined from 140 * {@link ICheckContext#getTextContent()} or another method that is based on 141 * <b>filtered</b> content. 142 */ 143 protected void createFinding(String message, IToken token) throws CheckException { 144 createFinding(message, token, token); 145 } 146 147 /** 148 * Creates a finding with the given message beginning from startToken to 149 * endToken. 150 * 151 * Use this method, if the tokens are determined from 152 * {@link ICheckContext#getTextContent()} or another method that is based on 153 * <b>filtered</b> content. 154 */ 155 protected void createFinding(String message, IToken startToken, IToken endToken) throws CheckException { 156 context.createFindingForOffsets(message, startToken.getOffset(), endToken.getEndOffset(), 157 ETextViewOption.FILTERED_CONTENT); 158 } 159 160 /** 161 * Creates a finding with the given message beginning from first token to last 162 * token. 163 * 164 * Use this method, if the tokens are determined from 165 * {@link ICheckContext#getTextContent()} or another method that is based on 166 * <b>filtered</b> content. 167 */ 168 protected void createFinding(String message, List<IToken> tokens) throws CheckException { 169 createFinding(message, tokens.get(0), CollectionUtils.getLast(tokens)); 170 } 171 172 /** 173 * Creates a finding with the given message beginning from startToken to 174 * endToken. 175 * 176 * Use this method, if the tokens are determined from 177 * {@link ICheckContext#getUnfilteredTextContent()} or another method that is 178 * based on <b>unfiltered</b> content. 179 */ 180 protected void createFindingOnUnfilteredCode(String message, IToken startToken, IToken endToken) 181 throws CheckException { 182 context.createFindingForOffsets(message, startToken.getOffset(), endToken.getEndOffset(), 183 ETextViewOption.UNFILTERED_CONTENT); 184 } 185 186 /** 187 * Creates a finding with the given message for the given entity. 188 * 189 * Use this method, if the shallow entity is determined from 190 * {@link ICheckContext#getTextContent()} or another method that is based on 191 * <b>filtered</b> content. 192 */ 193 protected void createFinding(String message, ShallowEntity entity) throws CheckException { 194 context.createFindingForOffsets(message, entity.getStartOffset(), entity.getEndOffset(), 195 ETextViewOption.FILTERED_CONTENT); 196 } 197 198 /** 199 * Creates a finding with the given message on the first line of the given 200 * entity. 201 * 202 * Use this method, if the shallow entity is determined from 203 * {@link ICheckContext#getTextContent()} or another method that is based on 204 * <b>filtered</b> content. 205 */ 206 protected void createFindingOnFirstLine(String message, ShallowEntity entity) throws CheckException { 207 createFinding(message, entity.getStartLine()); 208 } 209 210 /** 211 * Creates a finding with the given message at the given token. 212 * 213 * Use this method, if the token is determined from 214 * {@link ICheckContext#getUnfilteredTextContent()} or another method that is 215 * based on <b>unfiltered</b> content. 216 */ 217 protected void createFindingOnUnfilteredCode(String message, IToken token) throws CheckException { 218 createFindingOnUnfilteredCode(message, token, token); 219 } 220 221 /** 222 * Creates a finding with the given message for the given entity. 223 * 224 * Use this method, if the shallow entity is determined from 225 * {@link ICheckContext#getUnfilteredTextContent()} or another method that is 226 * based on <b>unfiltered</b> content. 227 */ 228 protected void createFindingOnUnfilteredCode(String message, ShallowEntity entity) throws CheckException { 229 context.createFindingForOffsets(message, entity.getStartOffset(), entity.getEndOffset(), 230 ETextViewOption.UNFILTERED_CONTENT); 231 } 232 233 /** 234 * Creates a finding with the given message at the given line. Use this method, 235 * if the line is determined from {@link ICheckContext#getTextContent()} or 236 * another method that is based on <b>filtered</b> content. 237 */ 238 protected void createFinding(String message, int line) throws CheckException { 239 context.createFindingForLines(message, line, line, ETextViewOption.FILTERED_CONTENT); 240 } 241 242 /** 243 * Creates findings with the given message for the given pair lists of start- 244 * and end tokens. 245 * 246 * @param message 247 * the message describing the findings 248 * @param startAndEndTokens 249 * A list of corresponding pairs of start and end tokens. 250 * @throws CheckException 251 */ 252 protected void createFindingForSiblings(String message, PairList<IToken, IToken> startAndEndTokens) 253 throws CheckException { 254 List<OffsetBasedRegion> regions = new ArrayList<>(); 255 startAndEndTokens.forEach((startToken, endToken) -> { 256 OffsetBasedRegion region = new OffsetBasedRegion(startToken.getOffset(), endToken.getOffset()); 257 regions.add(region); 258 }); 259 260 context.createFindingForSiblingsInCurrentFile(message, regions); 261 } 262 263 /** 264 * Creates findings for entities which are associated in some way. The entities 265 * must stem from the same file. 266 */ 267 protected void createFindingForSiblingEntities(String message, List<ShallowEntity> associatedEntities) 268 throws CheckException { 269 List<OffsetBasedRegion> regions = new ArrayList<>(); 270 associatedEntities.forEach(ent -> { 271 OffsetBasedRegion region = new OffsetBasedRegion(ent.getStartOffset(), ent.getEndOffset()); 272 regions.add(region); 273 }); 274 context.createFindingForSiblingsInCurrentFile(message, regions); 275 } 276 277 /** 278 * Creates a finding with the given message from the given start line 279 * (inclusive) to the given end line (inclusive). Use this method, if the lines 280 * are determined from {@link ICheckContext#getTextContent()} or another method 281 * that is based on <b>filtered</b> content. 282 */ 283 protected void createFinding(String message, int startLine, int endLine) throws CheckException { 284 context.createFindingForLines(message, startLine, endLine, ETextViewOption.FILTERED_CONTENT); 285 } 286 287 /** Creates an instance of the given check class. */ 288 public static CheckImplementationBase createInstance(Class<?> checkClass) throws CheckException { 289 CheckImplementationBase implementation = null; 290 try { 291 implementation = (CheckImplementationBase) checkClass.newInstance(); 292 } catch (InstantiationException e) { 293 throw new CheckException("Check class " + checkClass.getName() + " does not provide a default constructor.", 294 e); 295 } catch (IllegalAccessException e) { 296 throw new CheckException("Check class " + checkClass.getName() + "'s default constructor is not visible.", 297 e); 298 } catch (ClassCastException e) { 299 throw new CheckException( 300 "Check class " + checkClass.getName() + " does not implement CheckImplementationBase.", e); 301 } 302 return implementation; 303 } 304 305 /** 306 * Determines if the the check is currently processing source code that is 307 * written in C++ based on the file extension and the C++ specific tokens. 308 * Especially distinguishes C++ from C source code. 309 * 310 * @return true if the context is C++, false else 311 */ 312 public boolean isCpp() throws CheckException { 313 return CppLanguageFeatureParser.isCpp(context.getUniformPath(), 314 context.getTokens(ECodeViewOption.FILTERED_PREPROCESSED)); 315 } 316}