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&lt;SomeType&lt;(1&gt;2)&gt;&gt;" 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&lt;SomeType&lt;(1&gt;2)&gt;&gt;" 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}