001/*-------------------------------------------------------------------------+ 002| | 003| Copyright 2005-2011 the ConQAT Project | 004| | 005| Licensed under the Apache License, Version 2.0 (the "License"); | 006| you may not use this file except in compliance with the License. | 007| You may obtain a copy of the License at | 008| | 009| http://www.apache.org/licenses/LICENSE-2.0 | 010| | 011| Unless required by applicable law or agreed to in writing, software | 012| distributed under the License is distributed on an "AS IS" BASIS, | 013| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 014| See the License for the specific language governing permissions and | 015| limitations under the License. | 016+-------------------------------------------------------------------------*/ 017package eu.cqse.check.framework.util; 018 019import static eu.cqse.check.framework.scanner.ETokenType.AUTO; 020import static eu.cqse.check.framework.scanner.ETokenType.BOOL; 021import static eu.cqse.check.framework.scanner.ETokenType.CHAR; 022import static eu.cqse.check.framework.scanner.ETokenType.CLASS; 023import static eu.cqse.check.framework.scanner.ETokenType.DELETE; 024import static eu.cqse.check.framework.scanner.ETokenType.DOT; 025import static eu.cqse.check.framework.scanner.ETokenType.DOUBLE; 026import static eu.cqse.check.framework.scanner.ETokenType.EQ; 027import static eu.cqse.check.framework.scanner.ETokenType.FLOAT; 028import static eu.cqse.check.framework.scanner.ETokenType.GT; 029import static eu.cqse.check.framework.scanner.ETokenType.IDENTIFIER; 030import static eu.cqse.check.framework.scanner.ETokenType.INT; 031import static eu.cqse.check.framework.scanner.ETokenType.LONG; 032import static eu.cqse.check.framework.scanner.ETokenType.NAMESPACE; 033import static eu.cqse.check.framework.scanner.ETokenType.NORETURN; 034import static eu.cqse.check.framework.scanner.ETokenType.POINTERTO; 035import static eu.cqse.check.framework.scanner.ETokenType.SCOPE; 036import static eu.cqse.check.framework.scanner.ETokenType.SEMICOLON; 037import static eu.cqse.check.framework.scanner.ETokenType.SHORT; 038import static eu.cqse.check.framework.scanner.ETokenType.SIGNED; 039import static eu.cqse.check.framework.scanner.ETokenType.UNSIGNED; 040import static eu.cqse.check.framework.scanner.ETokenType.VIRTUAL; 041import static eu.cqse.check.framework.scanner.ETokenType.VOID; 042import static eu.cqse.check.framework.shallowparser.SubTypeNames.CONSTRUCTOR_DECLARATION; 043import static eu.cqse.check.framework.shallowparser.SubTypeNames.FUNCTION_DECLARATION; 044import static eu.cqse.check.framework.shallowparser.SubTypeNames.OPERATOR_DECLARATION; 045import static eu.cqse.check.framework.shallowparser.SubTypeNames.SIMPLE_STATEMENT; 046import static eu.cqse.check.framework.shallowparser.framework.EShallowEntityType.STATEMENT; 047 048import java.util.ArrayList; 049import java.util.Arrays; 050import java.util.EnumSet; 051import java.util.HashMap; 052import java.util.HashSet; 053import java.util.List; 054import java.util.Map; 055import java.util.Set; 056import java.util.regex.Pattern; 057 058import org.apache.logging.log4j.LogManager; 059import org.apache.logging.log4j.Logger; 060import org.conqat.lib.commons.assertion.CCSMAssert; 061import org.conqat.lib.commons.collections.CollectionUtils; 062 063import eu.cqse.check.framework.scanner.ELanguage; 064import eu.cqse.check.framework.scanner.ETokenType; 065import eu.cqse.check.framework.scanner.IToken; 066import eu.cqse.check.framework.shallowparser.SubTypeNames; 067import eu.cqse.check.framework.shallowparser.TokenStreamTextUtils; 068import eu.cqse.check.framework.shallowparser.TokenStreamUtils; 069import eu.cqse.check.framework.shallowparser.framework.EShallowEntityType; 070import eu.cqse.check.framework.shallowparser.framework.ShallowEntity; 071import eu.cqse.check.framework.util.tokens.TokenPattern; 072import eu.cqse.check.framework.util.variable.CLikeVariableUseExtractor; 073 074/** 075 * Language feature parser for C/C++. 076 */ 077public class CppLanguageFeatureParser extends CLikeLanguageFeatureParserBase { 078 079 /** Logger instance. */ 080 private static final Logger LOGGER = LogManager.getLogger(); 081 082 /** All token types that can be used to specify a type. */ 083 public static final EnumSet<ETokenType> ADDITIONAL_TYPE_TOKENS = EnumSet.of(IDENTIFIER, AUTO); 084 085 /** All token types that can be used to specify a type. */ 086 public static final EnumSet<ETokenType> PRIMITIVE_TYPE_TOKENS = EnumSet.of(SIGNED, UNSIGNED, BOOL, CHAR, SHORT, INT, 087 LONG, FLOAT, DOUBLE, VOID); 088 089 /** All token types that can be used as identifier. */ 090 private static final EnumSet<ETokenType> VALID_IDENTIFIERS = EnumSet.of(IDENTIFIER, ETokenType.OPERATOR); 091 092 /** 093 * Matches trailing return type syntaxes introduced in C++11. Some examples: 094 * <ul> 095 * <li><code>auto f1() -> int {}</code></li> 096 * <li><code>inline auto f2(int arg) -> int noexcept;</code></li> 097 * <li><code>virtual auto f3() const && -> float = delete;</code></li> 098 * <li><code>auto MyClass::operator=(const MyClass& other) -> MyClass& {}</code></li> 099 * <li><code>auto SomeClass::someMethod() -> void {}</code></li> 100 * </ul> 101 */ 102 private static final TokenPattern TRAILING_RETURN_TYPE_TOKENS_PATTERN = new TokenPattern().beginningOfStream() 103 .optional(ETokenType.INLINE, ETokenType.VIRTUAL).sequence(ETokenType.AUTO, IDENTIFIER) 104 .skipTo(ETokenType.LPAREN).skipNested(ETokenType.LPAREN, ETokenType.RPAREN, true).skipTo(ETokenType.RPAREN) 105 .sequence(ETokenType.POINTERTO) 106 .optional(ETokenType.NOEXCEPT, new TokenPattern().sequence(EQ, DELETE, SEMICOLON)); 107 108 /** 109 * Matches pointer declaration variables. Some examples: 110 * <ul> 111 * <li>int *foo</li> 112 * <li>int *(foo)</li> 113 * </ul> 114 */ 115 private static final TokenPattern POINTER_DECLARATION_PATTERN = new TokenPattern().sequence(PRIMITIVE_TYPE_TOKENS) 116 .skipTo(ETokenType.MULT).optional(ETokenType.LPAREN).skipTo(IDENTIFIER); 117 118 /** 119 * Regular expression describing a valid C/C++ identifier. It can contain 120 * alphabets, digits and underscores, but cannot start with a digit. 121 */ 122 public static final Pattern VALID_IDENTIFIER_PATTERN = Pattern.compile("[\\p{Alpha}_][\\w]*"); 123 124 /** 125 * All tokens that should prevent a LT to be interpreted as template (if seen 126 * before). 127 */ 128 private static final Set<ETokenType> NON_GENERIC_PREFIX_TOKENS = EnumSet.of(ETokenType.LT, ETokenType.OPERATOR); 129 130 /** The names of unconditional exit statements for a switch. */ 131 private static final Set<String> UNCONDITIONAL_SWITCH_EXIT_STATEMENT_NAMES = CollectionUtils.asHashSet("break", 132 "return", "goto", "continue", "throw", 133 // In theory, exit() can refer to a function (and not terminate the program). 134 // In practice, exit() seems to be a standard termination function. 135 "exit"); 136 137 /** The names of unconditional exit statements for a nested switch. */ 138 private static final Set<String> UNCONDITIONAL_NESTED_SWITCH_EXIT_STATEMENT_NAMES = CollectionUtils 139 .asHashSet("return", "throw"); 140 141 /** 142 * Maximal number of tokens that we allow an alias to grow to, to protect 143 * against too large replacements. 144 */ 145 private static final int ALIAS_EXPANSION_SIZE_LIMIT = 1000; 146 147 /** 148 * Names of functions in the C++ standard lib that cause program termination. 149 * The functions are called via fully qualified name (e.g., std::abort(...)). 150 */ 151 public static final HashSet<String> STD_LIB_PROGRAM_TERMINATION_FUNCTIONS = CollectionUtils.asHashSet("abort", 152 "terminate", "exit", "quick_exit", "_Exit"); 153 154 /** Constructor. */ 155 public CppLanguageFeatureParser() { 156 super(ELanguage.CPP, VALID_IDENTIFIERS, PRIMITIVE_TYPE_TOKENS, ADDITIONAL_TYPE_TOKENS, SCOPE, "::", 157 new CLikeVariableUseExtractor(EnumSet.of(DOT, POINTERTO, SCOPE), VALID_IDENTIFIERS)); 158 } 159 160 /** 161 * Returns whether the given {@link ShallowEntity} is a function, operator, or 162 * constructor declaration that has the C++11 163 * {@link #TRAILING_RETURN_TYPE_TOKENS_PATTERN}. 164 * 165 * For example <code>foobar(const foobar&) = delete;</code> declares a deleted 166 * constructor that can't be called (typically it must be declared because of 167 * inheritance). 168 */ 169 public static boolean isCpp11DeletedFunctionOrConstructor(ShallowEntity entity) { 170 return CollectionUtils.asHashSet(CONSTRUCTOR_DECLARATION, FUNCTION_DECLARATION, OPERATOR_DECLARATION).contains( 171 entity.getSubtype()) && TokenStreamUtils.endsWith(entity.ownStartTokens(), EQ, DELETE, SEMICOLON); 172 } 173 174 /** {@inheritDoc} */ 175 @Override 176 public boolean isImport(ShallowEntity entity) { 177 if (!(entity.getType().equals(EShallowEntityType.META) && entity.getSubtype().equals(SubTypeNames.USING))) { 178 return false; 179 } 180 List<IToken> tokens = entity.ownStartTokens(); 181 return tokens.size() >= 4 && tokens.get(1).getType().equals(NAMESPACE); 182 } 183 184 /** {@inheritDoc} */ 185 @Override 186 public String getImportName(ShallowEntity entity) { 187 CCSMAssert.isTrue(isImport(entity), "entity.getType() must be equal to EShallowEntityType.META and " 188 + "entity.getSubtype() must be equal to SubTypeNames.USING"); 189 List<IToken> tokens = entity.ownStartTokens(); 190 return TokenStreamTextUtils.concatTokenTexts(tokens.subList(2, tokens.size() - 1)); 191 } 192 193 /** {@inheritDoc} */ 194 @Override 195 public boolean hasVoidReturnType(ShallowEntity method) { 196 if (ETokenType.VOID.name().equalsIgnoreCase(super.getReturnType(method))) { 197 return true; 198 } 199 if (hasMethodTrailingReturnTypeSyntax(method) 200 && ETokenType.VOID.name().equalsIgnoreCase(getTrailingReturnTypeOfMethod(method))) { 201 return true; 202 } 203 204 return false; 205 } 206 207 /** 208 * Returns the return type name of the given method that has a 209 * {@link #TRAILING_RETURN_TYPE_TOKENS_PATTERN}. 210 */ 211 private static String getTrailingReturnTypeOfMethod(ShallowEntity method) { 212 assertMethod(method); 213 List<IToken> methodTokens = method.ownStartTokens(); 214 int pointerToTokenIndex = TokenStreamUtils.firstTokenOfType(methodTokens, ETokenType.POINTERTO); 215 int lastTokenIndex = methodTokens.size() - 1; 216 IToken lastToken = methodTokens.get(lastTokenIndex); 217 if (lastToken.getType() == ETokenType.RBRACE) { 218 // implies we have an empty method 219 lastTokenIndex -= 1; 220 } 221 List<IToken> subList = methodTokens.subList(pointerToTokenIndex + 1, lastTokenIndex); 222 return TokenStreamTextUtils.concatTokenTexts(subList); 223 } 224 225 /** Asserts that the given shallow entity is a C++ method. */ 226 private static void assertMethod(ShallowEntity entity) { 227 CCSMAssert.isTrue(entity.getType() == EShallowEntityType.METHOD, "Entity is no method"); 228 } 229 230 /** 231 * Returns <code>true</code> if the given shallow entity has a 232 * {@link #TRAILING_RETURN_TYPE_TOKENS_PATTERN}. 233 */ 234 private static boolean hasMethodTrailingReturnTypeSyntax(ShallowEntity entity) { 235 assertMethod(entity); 236 return TRAILING_RETURN_TYPE_TOKENS_PATTERN.findFirstMatch(entity.ownStartTokens()) != null; 237 } 238 239 /** Returns whether the given shallow entity is virtual. */ 240 public static boolean isVirtual(ShallowEntity entity) { 241 return TokenStreamUtils.contains(entity.ownStartTokens(), VIRTUAL); 242 } 243 244 /** Returns whether the given shallow entity is marked as "_Noreturn". */ 245 public static boolean isNoreturn(ShallowEntity entity) { 246 return TokenStreamUtils.contains(entity.ownStartTokens(), NORETURN); 247 } 248 249 /** Returns whether the given entity is a constant */ 250 public static boolean isConstant(ShallowEntity entity) { 251 List<IToken> tokens = entity.ownStartTokens(); 252 253 // C++: const keyword, but no '*', as this indicates (variable) pointer 254 return TokenStreamUtils.containsAny(tokens, ETokenType.CONST, ETokenType.CONSTEXPR) 255 && !isPointerDeclaration(entity); 256 } 257 258 /** Returns true if a variable is declared as a pointer. */ 259 public static boolean isPointerDeclaration(ShallowEntity entity) { 260 List<IToken> tokens = entity.ownStartTokens(); 261 int assignmentOperatorIndex = TokenStreamUtils.firstTokenOfType(tokens, ETokenType.EQ); 262 263 if (assignmentOperatorIndex != TokenStreamUtils.NOT_FOUND) { 264 tokens = tokens.subList(0, assignmentOperatorIndex); 265 } 266 267 return POINTER_DECLARATION_PATTERN.findFirstMatch(tokens) != null; 268 } 269 270 /** 271 * A C++ method outside of a class/struct must be scoped with ::. This method 272 * can be used to distinguish between a C and a C++ method. 273 */ 274 public static boolean isCppMethod(ShallowEntity entity) { 275 assertMethod(entity); 276 if (TokenStreamUtils.contains(entity.ownStartTokens(), ETokenType.SCOPE)) { 277 return true; 278 } 279 return isInClassOrStruct(entity); 280 } 281 282 /** 283 * Returns <code>true</code> if the given uniform path has C++ related file 284 * extension or the given list of tokens contains either 285 * {@link ETokenType#SCOPE} or {@link ETokenType#CLASS}. This is a heuristic 286 * method to distinguish C from C++. 287 */ 288 public static boolean isCpp(String uniformPath, List<IToken> tokens) { 289 if (uniformPath != null && (uniformPath.endsWith(".cpp") || uniformPath.endsWith(".hpp"))) { 290 return true; 291 } 292 return TokenStreamUtils.containsAny(tokens, SCOPE, CLASS); 293 } 294 295 /** 296 * Checks if the parent of the entity is a class or a struct. 297 */ 298 private static boolean isInClassOrStruct(ShallowEntity entity) { 299 ShallowEntity parent = entity.getParent(); 300 if (parent == null) { 301 return false; 302 } 303 return parent.getSubtype().equals(SubTypeNames.CLASS) || parent.getSubtype().equals(SubTypeNames.STRUCT); 304 } 305 306 /** Returns whether the given entity is a top-level class. */ 307 public static boolean isTopLevelClass(ShallowEntity entity) { 308 ShallowEntity parent = entity.getParent(); 309 boolean isTopLevel = parent == null || parent.getType().equals(EShallowEntityType.MODULE); 310 boolean isClass = entity.getType() == EShallowEntityType.TYPE 311 && (SubTypeNames.CLASS.equals(entity.getSubtype())); 312 return isTopLevel && isClass; 313 } 314 315 @Override 316 protected int reverseSkipToType(List<IToken> tokens) { 317 int endIndex = super.reverseSkipToType(tokens); 318 if (endIndex < 0) { 319 return endIndex; 320 } 321 322 return reverseSkipScope(tokens, endIndex); 323 } 324 325 /** 326 * Skips a scope specification in front of an attribute/method. E. g. the scope 327 * specification in "int Foo::test;" is "Foo::". Returns the index pointing to 328 * the first token of the scope specification. This method is aware of template 329 * specifications within the scope specification. 330 */ 331 private int reverseSkipScope(List<IToken> tokens, int endIndex) { 332 while (endIndex > 1 && tokens.get(endIndex - 1).getType() == SCOPE) { 333 if (tokens.get(endIndex - 2).getType() == GT) { 334 endIndex = skipGenericReverse(tokens, endIndex - 2); 335 } else { 336 endIndex -= 1; 337 } 338 339 if (endIndex <= 0 || !validIdentifiers.contains(tokens.get(endIndex - 1).getType())) { 340 return -1; 341 } 342 endIndex = endIndex - 1; 343 } 344 return endIndex; 345 } 346 347 /** 348 * {@inheritDoc} 349 * 350 * In C++ constructs like "std::vector<SomeType<(1>2)>>" are 351 * allowed, thus we cannot simply skip to the matching closing token. 352 */ 353 @Override 354 public int skipGeneric(List<IToken> tokens, int startIndex) { 355 int angleDepth = 1; 356 int parenDepth = 0; 357 358 for (int i = startIndex + 1; i < tokens.size(); i++) { 359 switch (tokens.get(i).getType()) { 360 case LPAREN: 361 parenDepth++; 362 break; 363 case RPAREN: 364 parenDepth--; 365 break; 366 case LT: 367 if (parenDepth <= 0) { 368 angleDepth++; 369 } 370 break; 371 case GT: 372 if (parenDepth <= 0) { 373 angleDepth--; 374 if (angleDepth == 0) { 375 return i; 376 } 377 } 378 break; 379 default: 380 break; 381 } 382 } 383 384 return -1; 385 } 386 387 /** 388 * {@inheritDoc} 389 * 390 * In C++ constructs like "std::vector<SomeType<(1>2)>>" are 391 * allowed, thus we cannot simply skip to the matching closing token. 392 */ 393 @Override 394 public int skipGenericReverse(List<IToken> tokens, int startIndex) { 395 int angleDepth = 1; 396 int parenDepth = 0; 397 398 for (int i = startIndex - 1; i >= 0; i--) { 399 switch (tokens.get(i).getType()) { 400 case LPAREN: 401 parenDepth--; 402 break; 403 case RPAREN: 404 parenDepth++; 405 break; 406 case LT: 407 if (parenDepth <= 0) { 408 angleDepth--; 409 if (angleDepth == 0) { 410 return i; 411 } 412 } 413 break; 414 case GT: 415 if (parenDepth <= 0) { 416 angleDepth++; 417 } 418 break; 419 default: 420 break; 421 } 422 } 423 return -1; 424 } 425 426 @Override 427 protected int getMethodOpeningParenthesisIndex(List<IToken> methodTokens) { 428 for (int i = 0; i < methodTokens.size(); i++) { 429 switch (methodTokens.get(i).getType()) { 430 case LT: 431 if (i == 0 || !NON_GENERIC_PREFIX_TOKENS.contains(methodTokens.get(i - 1).getType())) { 432 i = skipGeneric(methodTokens, i); 433 if (i < 0) { 434 return -1; 435 } 436 } 437 break; 438 case LPAREN: 439 return i; 440 default: 441 break; 442 } 443 } 444 return -1; 445 } 446 447 /** Returns whether the given entity is a function declaration */ 448 public static boolean isFunctionDeclaration(ShallowEntity entity) { 449 return entity.getType() == EShallowEntityType.METHOD 450 && SubTypeNames.FUNCTION_DECLARATION.equals(entity.getSubtype()); 451 } 452 453 /** 454 * Returns whether the given entity is a valid exit statement for a case 455 * statement also considering if we are in a nested switch statement or not 456 */ 457 public static boolean isCaseOrDefaultExitStatement(ShallowEntity entity, Set<String> additionalExitNames, 458 boolean isNestedSwitch) { 459 if (entity.getType().equals(STATEMENT) && entity.getSubtype().equals(SIMPLE_STATEMENT)) { 460 if (getUnconditionalSwitchExitStatementNames(isNestedSwitch).contains(entity.getName()) 461 || additionalExitNames.contains(entity.getName())) { 462 return true; 463 } 464 465 return TokenStreamUtils.hasTokenTypeSequence(entity.ownStartTokens(), 0, IDENTIFIER, SCOPE, IDENTIFIER) 466 && "std".equals(entity.ownStartTokens().get(0).getText()) 467 && STD_LIB_PROGRAM_TERMINATION_FUNCTIONS.contains(entity.ownStartTokens().get(2).getText()); 468 469 } 470 471 return false; 472 } 473 474 /** 475 * Returns the collection of exit statement names to check for whether in a 476 * nested switch block or not. In cases of nested switch statements, we consider 477 * only exit statements that guarantee that all control-flow paths are 478 * terminated 479 */ 480 private static Set<String> getUnconditionalSwitchExitStatementNames(boolean isNestedSwitch) { 481 if (isNestedSwitch) { 482 return UNCONDITIONAL_NESTED_SWITCH_EXIT_STATEMENT_NAMES; 483 } 484 485 return UNCONDITIONAL_SWITCH_EXIT_STATEMENT_NAMES; 486 } 487 488 /** 489 * Returns whether the given entity is an unconditional exit statement for a 490 * case statement. This checks 491 * {@link #isCaseOrDefaultExitStatement(ShallowEntity, Set, boolean)}, but also 492 * recurses in case of blocks and conditionals. 493 * 494 * @param additionalExitNames 495 * names of functions that we know are unconditional exits. 496 */ 497 public static boolean isUnconditionalSwitchExit(ShallowEntity lastEntity, Set<String> additionalExitNames, 498 boolean isNestedSwitch) { 499 if (lastEntity.getSubtype().equals(SubTypeNames.ELSE)) { 500 return isUnconditionalSwitchExitGivenElseAsLastEntity(lastEntity, additionalExitNames, isNestedSwitch); 501 } 502 503 return isCaseOrDefaultExitStatement(lastEntity, additionalExitNames, isNestedSwitch); 504 } 505 506 /** 507 * Given a) that the last entity parameter is an else statement and b) whether 508 * we are in a nested switch block or not, this method determines whether (or 509 * not) the last entity in a) unconditionally exits the switch block. 510 */ 511 private static boolean isUnconditionalSwitchExitGivenElseAsLastEntity(ShallowEntity lastEntity, 512 Set<String> additionalExitNames, boolean isNestedSwitch) { 513 List<ShallowEntity> siblings = lastEntity.getParent().getChildren(); 514 int index = siblings.indexOf(lastEntity); 515 516 // check the "else" 517 if (!isUnconditionalSwitchExit(lastEntity.getChildren(), additionalExitNames, isNestedSwitch)) { 518 return false; 519 } 520 index -= 1; 521 522 // check any else-if 523 while (index > 0 && SubTypeNames.ELSE_IF.equals(siblings.get(index).getSubtype())) { 524 if (!isUnconditionalSwitchExit(siblings.get(index).getChildren(), additionalExitNames, isNestedSwitch)) { 525 return false; 526 } 527 index -= 1; 528 } 529 530 // check the "if" 531 return isUnconditionalSwitchExit(siblings.get(index).getChildren(), additionalExitNames, isNestedSwitch); 532 } 533 534 private static boolean isUnconditionalSwitchExit(List<ShallowEntity> entities, Set<String> additionalExitNames, 535 boolean isNestedSwitch) { 536 return !entities.isEmpty() 537 && isUnconditionalSwitchExit(CollectionUtils.getLast(entities), additionalExitNames, isNestedSwitch); 538 } 539 540 /** 541 * Extracts a mapping of types regarding the typedef instructions found in the 542 * given tokens. The returned aliases will already be fully expanded, i.e. not 543 * recursive expansion is needed anymore. 544 * 545 * @param tokens 546 * the tokens that may contain typedefs 547 * @return a mapping between the aliases (identifiers) and the tokens that get 548 * replaced by the aliases 549 */ 550 public static Map<String, List<IToken>> getAliasMapping(List<IToken> tokens) { 551 Map<String, List<IToken>> aliases = new HashMap<>(); 552 553 for (int i = 0; i < tokens.size(); i++) { 554 IToken current = tokens.get(i); 555 ETokenType currentType = current.getType(); 556 if (currentType == ETokenType.TYPEDEF || currentType == ETokenType.USING) { 557 int semicolonIndex = TokenStreamUtils.findFirstTopLevel(tokens, i + 1, EnumSet.of(ETokenType.SEMICOLON), 558 Arrays.asList(ETokenType.LBRACE), Arrays.asList(ETokenType.RBRACE)); 559 if (semicolonIndex == TokenStreamUtils.NOT_FOUND) { 560 continue; 561 } 562 563 List<IToken> tokenBuffer = tokens.subList(i + 1, semicolonIndex); 564 i = semicolonIndex; 565 566 if (currentType == ETokenType.TYPEDEF && isTypedefUsedForAliasResolution(tokenBuffer)) { 567 IToken lastToken = tokenBuffer.get(tokenBuffer.size() - 1); 568 insertAlias(lastToken.getText(), tokenBuffer.subList(0, tokenBuffer.size() - 1), aliases); 569 } else if (currentType == ETokenType.USING 570 && TokenStreamUtils.startsWith(tokenBuffer, ETokenType.IDENTIFIER, ETokenType.EQ)) { 571 insertAlias(tokenBuffer.get(0).getText(), tokenBuffer.subList(2, tokenBuffer.size()), aliases); 572 } 573 } 574 } 575 return aliases; 576 } 577 578 /** 579 * Returns whether the given tokens from a typeef should be used for alias 580 * resolution. We explicitly skip typedefs with function types (as there is no 581 * clear replacement) and containing inline union/struct types. This is 582 * identified by checking for braces and parentheses. 583 */ 584 private static boolean isTypedefUsedForAliasResolution(List<IToken> tokens) { 585 return !TokenStreamUtils.containsAny(tokens, ETokenType.LBRACE, ETokenType.LPAREN); 586 } 587 588 /** 589 * Inserts a new alias, first resolving the tokens of the alias. This respects 590 * the expansion limit set in {@link #ALIAS_EXPANSION_SIZE_LIMIT}. 591 */ 592 private static void insertAlias(String name, List<IToken> tokens, Map<String, List<IToken>> aliases) { 593 List<IToken> resolved = resolveAliases(tokens, aliases); 594 if (resolved.size() > ALIAS_EXPANSION_SIZE_LIMIT) { 595 LOGGER.warn("Skipping alias for " + name + " as resolved replacement would exceed " 596 + ALIAS_EXPANSION_SIZE_LIMIT + " tokens in file " + CollectionUtils.getAny(tokens).getOriginId()); 597 } else { 598 aliases.put(name, resolved); 599 } 600 } 601 602 /** 603 * Replaces the aliases introduced by typedefs and usings with their respective 604 * original type. 605 * 606 * @param tokens 607 * The tokens in which the aliases are resolved 608 * 609 * @param aliasMapping 610 * The resolved alias returned by @{@link #getAliasMapping(List)}. 611 */ 612 public static List<IToken> resolveAliases(List<IToken> tokens, Map<String, List<IToken>> aliasMapping) { 613 List<IToken> resolved = new ArrayList<>(); 614 for (IToken token : tokens) { 615 if (token.getType() == IDENTIFIER && aliasMapping.containsKey(token.getText())) { 616 resolved.addAll(aliasMapping.get(token.getText())); 617 } else { 618 resolved.add(token); 619 } 620 } 621 return resolved; 622 } 623 624}