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.preprocessor.c;
018
019import java.util.ArrayList;
020import java.util.Collections;
021import java.util.Comparator;
022import java.util.EnumSet;
023import java.util.HashMap;
024import java.util.HashSet;
025import java.util.List;
026import java.util.Map;
027import java.util.Map.Entry;
028import java.util.Set;
029import java.util.function.Predicate;
030import java.util.regex.Pattern;
031
032import javax.annotation.Nonnull;
033
034import org.apache.logging.log4j.LogManager;
035import org.conqat.lib.commons.assertion.CCSMAssert;
036import org.conqat.lib.commons.cache4j.ICache;
037import org.conqat.lib.commons.cache4j.SynchronizedCache;
038import org.conqat.lib.commons.cache4j.backend.ECachingStrategy;
039import org.conqat.lib.commons.collections.CollectionUtils;
040import org.conqat.lib.commons.collections.UnmodifiableList;
041import org.conqat.lib.commons.error.NeverThrownRuntimeException;
042import org.conqat.lib.commons.factory.ForwardingFactory;
043import org.conqat.lib.commons.region.RegionSet;
044import org.conqat.lib.commons.string.StringUtils;
045
046import eu.cqse.check.framework.preprocessor.IPreprocessor;
047import eu.cqse.check.framework.scanner.ETokenType;
048import eu.cqse.check.framework.scanner.IToken;
049import eu.cqse.check.framework.shallowparser.TokenStreamUtils;
050
051/**
052 * Base class of the C preprocessor that deals with macro handling and the
053 * overall parsing.
054 *
055 * The design of the preprocessor uses inheritance to separate the aspects macro
056 * handling, include handling, and conditionals to separate classes. This is
057 * meant to keep the classes small and easier to understand, while still
058 * providing a final common class, that can be easily extended by subclassing
059 * and overriding certain methods, which would be hard when using
060 * delegation/composition.
061 */
062public abstract class MacroHandlingCPreprocessorBase implements IPreprocessor {
063
064        /** The cache used for storing macro tokens. */
065        @SuppressWarnings("unchecked")
066        private static final ICache<ObtainMacroTokensKey, UnmodifiableList<IToken>, NeverThrownRuntimeException> MACRO_TOKEN_CACHE = new SynchronizedCache<>(
067                        "MACRO-TOKEN-CACHE", ForwardingFactory.INSTANCE,
068                        ECachingStrategy.LRU.<ObtainMacroTokensKey, UnmodifiableList<IToken>>getBackend(500));
069
070        /** Default name for the varargs parameter. */
071        private static final String DEFAULT_VAR_ARGS_NAME = "__VA_ARGS__";
072
073        /** Patterns used for finding if directives (and ifdef, ifndef, ...). */
074        private static final Pattern IF_DIRECTIVE_START_PATTERN = Pattern.compile("^#\\s*if");
075
076        /**
077         * Patterns used for finding pragma directives. #pragma is the old C pragma
078         * directive. _pragma (...) is a newer C99 pattern which is replaced with
079         * #pragma by the compiler.
080         */
081        private static final Pattern PRAGMA_DIRECTIVE_START_PATTERN = Pattern.compile("^(#|__|_)pragma",
082                        Pattern.CASE_INSENSITIVE);
083
084        /**
085         * Maximal number of nested expansions, used to defend against infinite
086         * recursion.
087         */
088        private static final int EXPANSION_LIMIT = 30;
089
090        /**
091         * Maximal number of nested inclusions, used to defend against infinite
092         * recursion.
093         */
094        private static final int INCLUSION_LIMIT = 20;
095
096        /** The origin name used for macro tokens. */
097        public static final String MACRO_ORIGIN = "##macro##";
098
099        /** Returns whether the given token is inserted by macro expansion. */
100        public static final Predicate<IToken> IS_MACRO_TOKEN = token -> MACRO_ORIGIN.equals(token.getOriginId());
101
102        /** The macro provider used. */
103        protected final OverlayMacroProvider macroProvider;
104
105        /**
106         * The currently processed uniform path (for error reporting).
107         */
108        private String uniformPath = "";
109
110        /**
111         * Tokens that must not be expanded any further. The C++ Spec Section 16.3.4
112         * [cpp.rescan] paragraph 2 specifies that tokens that are ignored once are no
113         * longer available for further macro replacements.
114         */
115        private HashSet<IToken> ignoredTokens = new HashSet<>();
116
117        /** Constructor. */
118        protected MacroHandlingCPreprocessorBase(IMacroProvider macroProvider) {
119                this.macroProvider = new OverlayMacroProvider(macroProvider);
120        }
121
122        /** {@inheritDoc} */
123        @Override
124        public List<IToken> preprocess(String uniformPath, List<IToken> tokens) {
125                return preprocess(uniformPath, new ArrayList<>(tokens), INCLUSION_LIMIT, new HashSet<>());
126        }
127
128        /**
129         * Sets the uniform path which is used in warning messages.
130         */
131        public void setUniformPath(String uniformPath) {
132                this.uniformPath = uniformPath;
133        }
134
135        /**
136         * Performs the actual preprocessing of the given tokens. The given list of
137         * tokens might be changed.
138         *
139         * @param uniformPath
140         *            may be null if this information is not available.
141         */
142        protected List<IToken> preprocess(String uniformPath, List<IToken> tokens, int inclusionLimit,
143                        Set<String> seenPaths) {
144                ignoredTokens.clear();
145                List<IToken> result = new ArrayList<>();
146                if (uniformPath != null && seenPaths.contains(uniformPath)) {
147                        return result;
148                } else if (uniformPath != null) {
149                        seenPaths.add(uniformPath);
150                }
151                RegionSet ignoredRegions = new RegionSet();
152
153                for (int i = 0; i < tokens.size(); ++i) {
154                        if (ignoredRegions.contains(i)) {
155                                continue;
156                        }
157
158                        IToken token = tokens.get(i);
159                        if (isPragmaDirective(token)) {
160                                i = handlePragmaDirective(tokens, i);
161                                continue;
162                        }
163                        switch (token.getType()) {
164                        case PREPROCESSOR_INCLUDE:
165                                handleInclude(uniformPath, token, result, inclusionLimit, seenPaths);
166                                break;
167                        case PREPROCESSOR_DIRECTIVE:
168                                if (isIfDirective(token)) {
169                                        processIfDirective(tokens, i, ignoredRegions);
170                                } else {
171                                        handleMacroPreprocessorDirective(token, result);
172                                }
173                                break;
174                        case CONCATENATION:
175                                if (i <= 0 || i >= tokens.size() - 1) {
176                                        result.add(token);
177                                        break;
178                                }
179                                IToken left = tokens.get(i - 1);
180                                IToken right = tokens.get(i + 1);
181                                tokens.set(i - 1, left.newToken(ETokenType.IDENTIFIER, left.getOffset(), left.getLineNumber(),
182                                                left.getText() + right.getText(), MACRO_ORIGIN));
183                                if (CollectionUtils.getLast(result).equals(left)) {
184                                        result.remove(result.size() - 1);
185                                }
186                                tokens.remove(i); // remove the CONCATENATION token
187                                tokens.remove(i); // remove right
188                                i -= 2; // restart at new token
189                                break;
190                        case IDENTIFIER:
191                                if (macroProvider.isDefined(token.getText())) {
192                                        i = expandMacro(tokens, i, result);
193                                        break;
194                                }
195                                // fall-through in else-case intended
196                        default:
197                                result.add(token);
198                        }
199                }
200                return result;
201        }
202
203        /**
204         * Skips tokens belonging to a pragma directive
205         */
206        private static int handlePragmaDirective(List<IToken> tokens, int i) {
207                CCSMAssert.isTrue(
208                                MacroHandlingCPreprocessorBase.PRAGMA_DIRECTIVE_START_PATTERN.matcher(tokens.get(i).getText()).find(),
209                                "This method handles only pragma directives.");
210                if (tokens.get(i).getType() == ETokenType.PREPROCESSOR_DIRECTIVE) {
211                        // simply ignore this token (pragma arguments are included in the token)
212                        return i;
213                }
214                // this is a "_pragma (...)" pragma (or starting with __)
215                if (tokens.size() > i + 1 && tokens.get(i + 1).getType() == ETokenType.LPAREN) {
216                        int closingIndex = TokenStreamUtils.findFirstTopLevel(tokens, i + 2, EnumSet.of(ETokenType.RPAREN),
217                                        Collections.singletonList(ETokenType.LPAREN), Collections.singletonList(ETokenType.RPAREN));
218                        if (closingIndex != TokenStreamUtils.NOT_FOUND) {
219                                return closingIndex;
220                        }
221                }
222                // simply ignore this token (_pragma without arguments)
223                return i;
224        }
225
226        /**
227         * Expands the macro at the given position in the token stream. Appends the
228         * results to the given result list and returns the index to continue at.
229         */
230        private int expandMacro(List<IToken> tokens, int index, List<IToken> result) {
231                List<IToken> expanded = new ArrayList<>();
232                int tokensConsumed = expandMacro(tokens, index, expanded, EXPANSION_LIMIT, new HashSet<String>());
233
234                // skip pragma directives in expanded macro
235                List<IToken> expandedFiltered = new ArrayList<>();
236                for (int i = 0; i < expanded.size(); i++) {
237                        if (isPragmaDirective(expanded.get(i))) {
238                                i = handlePragmaDirective(expanded, i);
239                        } else {
240                                expandedFiltered.add(expanded.get(i));
241                        }
242                }
243
244                // in some cases we need a full rescan of the generated tokens
245                if (containsFurtherMacros(expandedFiltered, tokens.subList(index, index + 1 + tokensConsumed))
246                                || TokenStreamUtils.containsAny(expandedFiltered, ETokenType.CONCATENATION)) {
247                        index = mergeExpansionIntoTokens(tokens, index, expandedFiltered, tokensConsumed);
248                } else {
249                        result.addAll(expandedFiltered);
250                        index += tokensConsumed;
251                }
252                return index;
253        }
254
255        /**
256         * Merges the expanded tokens back into the tokens. The size of the tokens list
257         * will be preserved, but parts before <code>index</code> might be replaced with
258         * null.
259         *
260         * @return one before the index to continue scanning from.
261         */
262        protected static int mergeExpansionIntoTokens(List<IToken> tokens, int index, List<IToken> expanded,
263                        int tokensConsumed) {
264                expanded.addAll(tokens.subList(index + tokensConsumed + 1, tokens.size()));
265                int padding = Math.max(0, tokens.size() - expanded.size());
266                tokens.clear();
267                tokens.addAll(Collections.nCopies(padding, null));
268                tokens.addAll(expanded);
269                return padding - 1;
270        }
271
272        /**
273         * Returns whether the given list of tokens contains any potential macros that
274         * need expansion and that have not been before in the list of known tokens.
275         */
276        private boolean containsFurtherMacros(List<IToken> tokens, List<IToken> knownTokens) {
277                Set<String> knownIdentifiers = new HashSet<>(CollectionUtils.filterAndMap(knownTokens,
278                                token -> token.getType() == ETokenType.IDENTIFIER, IToken::getText));
279
280                return tokens.stream().anyMatch(token -> token.getType() == ETokenType.IDENTIFIER
281                                && !knownIdentifiers.contains(token.getText()) && macroProvider.isDefined(token.getText()));
282        }
283
284        /** Returns whether the given token is an if/ifdef/ifndef directive. */
285        protected static boolean isIfDirective(IToken token) {
286                return IF_DIRECTIVE_START_PATTERN.matcher(token.getText()).find();
287        }
288
289        /** Returns whether the given token is the start of a pragma directive. */
290        protected static boolean isPragmaDirective(IToken token) {
291                return PRAGMA_DIRECTIVE_START_PATTERN.matcher(token.getText()).find();
292        }
293
294        /**
295         * Handles the include directive.
296         *
297         * @param uniformPath
298         *            the uniform path of the file with the include.
299         */
300        protected abstract void handleInclude(String uniformPath, IToken token, List<IToken> result, int inclusionLimit,
301                        Set<String> seenPaths);
302
303        /**
304         * Processes an if/ifdef/ifndef directive. This only updates ignored regions, to
305         * exclude parts of the tokens.
306         *
307         * @param tokens
308         *            the tokens being processed.
309         * @param ifIndex
310         *            the index in the tokens list, where the if directive is found.
311         * @param ignoredRegions
312         *            the regions of indexes to be ignored during parsing.
313         */
314        protected abstract void processIfDirective(List<IToken> tokens, int ifIndex, RegionSet ignoredRegions);
315
316        /** Handles any preprocessor directives affecting macro definitions. */
317        private void handleMacroPreprocessorDirective(IToken token, List<IToken> result) {
318                // strip leading '#'
319                String content = token.getText().trim().substring(1);
320
321                List<IToken> subTokens = parseMacroContent(content);
322                if (subTokens.isEmpty()) {
323                        return;
324                }
325
326                switch (subTokens.get(0).getText()) {
327                case "define":
328                        if (subTokens.size() > 1) {
329                                macroProvider.define(subTokens.get(1).getText(),
330                                                StringUtils.stripPrefix(content.trim(), "define").trim());
331                        }
332                        break;
333                case "undef":
334                        if (subTokens.size() > 1) {
335                                macroProvider.undefine(subTokens.get(1).getText());
336                        }
337                        break;
338                default:
339                        // ignore other/unknown directives
340                        result.add(token);
341                }
342        }
343
344        /** Parses a macor's content. */
345        protected static List<IToken> parseMacroContent(String content) {
346                return MACRO_TOKEN_CACHE.obtain(new ObtainMacroTokensKey(content));
347        }
348
349        /**
350         * Expands the macro at the given start position in the token stream.
351         *
352         * @param tokens
353         *            the input token stream where the macros was found.
354         * @param macroIndex
355         *            the index in the token stream where the macro was found.
356         * @param result
357         *            the list to append any new tokens to.
358         * @param expansionLimit
359         *            the maximal number of expansion performed
360         * @param ignoreMacros
361         *            macro names that will not be expanded (necessary to handle macro
362         *            nesting correctly)
363         * @return the additional number of tokens consumed (besides the macro name
364         *         itself).
365         */
366        private int expandMacro(List<IToken> tokens, int macroIndex, List<IToken> result, int expansionLimit,
367                        HashSet<String> ignoreMacros) {
368                IToken macroNameToken = tokens.get(macroIndex);
369                String name = macroNameToken.getText();
370                String definition = macroProvider.getDefinition(name);
371                if (expansionLimit <= 0) {
372                        result.clear();
373                        return 0;
374                }
375                if (definition == null || ignoreMacros.contains(name) || ignoredTokens.contains(macroNameToken)) {
376                        result.add(macroNameToken);
377                        ignoredTokens.add(macroNameToken);
378                        return 0;
379                }
380                expansionLimit -= 1;
381
382                List<IToken> definitionTokens = parseMacroContent(definition);
383                int indexOfFirstExpandedToken = result.size();
384                if (isFunctionMacro(definitionTokens)) {
385                        if (tokens.size() <= macroIndex + 1 || tokens.get(macroIndex + 1).getType() != ETokenType.LPAREN) {
386                                result.add(macroNameToken);
387                                return 0;
388                        }
389
390                        Map<String, Integer> parameterNameToPosition = determineParameterNames(definitionTokens);
391                        List<List<IToken>> actualArguments = extractActualArguments(tokens, macroIndex);
392                        List<IToken> macroContent = definitionTokens
393                                        .subList(1 + determineParameterTokenCount(parameterNameToPosition), definitionTokens.size());
394                        expandMacroContent(name, macroContent, parameterNameToPosition, actualArguments, result, expansionLimit,
395                                        ignoreMacros);
396                        correctExpandedTokenOffsetsAndLines(macroNameToken, result, indexOfFirstExpandedToken);
397                        return determineArgumentTokenCount(actualArguments);
398                }
399                expandMacroContent(name, definitionTokens.subList(1, definitionTokens.size()),
400                                CollectionUtils.<String, Integer>emptyMap(), CollectionUtils.<List<IToken>>emptyList(), result,
401                                expansionLimit, ignoreMacros);
402                correctExpandedTokenOffsetsAndLines(macroNameToken, result, indexOfFirstExpandedToken);
403                return 0;
404        }
405
406        /**
407         * Corrects the offsets and names of tokens introduced during macro expansion.
408         * These tokens get the offset/line of the macro-name token (the first token
409         * that is replaced during expansion). This is necessary to generate a token
410         * list with increasing (not strictly increasing) offsets/line numbers.
411         *
412         * This method assumes that all tokens from the given offset to the end of the
413         * <code>result</code> list are macro-expansion tokens. All of these tokens will
414         * be corrected/replaced.
415         *
416         * We also replace macro-parameter tokens (even though they come from the
417         * non-expanded token list) since they can occur multiple times in the expansion
418         * (<code>macro(X)</code> can be expanded to <code> foo(X,X);</code>).
419         */
420        private void correctExpandedTokenOffsetsAndLines(IToken macroNameToken, List<IToken> result,
421                        int indexOfFirstExpandedToken) {
422                for (int i = indexOfFirstExpandedToken; i < result.size(); i++) {
423                        IToken currentToken = result.get(i);
424                        IToken macroTokenReplacement = currentToken.newToken(currentToken.getType(), macroNameToken.getOffset(),
425                                        macroNameToken.getLineNumber(), currentToken.getText(), MACRO_ORIGIN);
426                        if (ignoredTokens.contains(currentToken)) {
427                                // since we replace the token in the result list, we also need to replace it in
428                                // the ignoredTokens list. This will not work if the token is inserted multiple
429                                // times in the same expansion.
430                                ignoredTokens.remove(currentToken);
431                                ignoredTokens.add(macroTokenReplacement);
432                        }
433                        result.set(i, macroTokenReplacement);
434                }
435        }
436
437        /** Returns the number of tokens used to represent the given arguments. */
438        private static int determineParameterTokenCount(Map<String, Integer> parameterNameToPosition) {
439                if (parameterNameToPosition.isEmpty()) {
440                        return 2;
441                }
442                int result = 1 + 2 * parameterNameToPosition.size();
443                for (Entry<String, Integer> entry : parameterNameToPosition.entrySet()) {
444                        if (entry.getValue() < 0 && !DEFAULT_VAR_ARGS_NAME.equals(entry.getKey())) {
445                                result += 1;
446                        }
447                }
448                return result;
449        }
450
451        /**
452         * Returns a mapping from parameter name to parameter index. A var args
453         * parameter will be encoded using a negative index (decremented by 1 to make it
454         * distinguishable from 0).
455         */
456        private static Map<String, Integer> determineParameterNames(List<IToken> definitionTokens) {
457                Map<String, Integer> argumentNameToPosition = new HashMap<>();
458                for (int i = 2; i < definitionTokens.size(); i += 2) {
459                        if (definitionTokens.get(i - 1).getType() == ETokenType.RPAREN) {
460                                break;
461                        }
462                        if (definitionTokens.get(i).getType() == ETokenType.IDENTIFIER) {
463                                argumentNameToPosition.put(definitionTokens.get(i).getText(), i / 2 - 1);
464                                if (i + 1 < definitionTokens.size() && definitionTokens.get(i + 1).getType() == ETokenType.ELLIPSIS) {
465                                        argumentNameToPosition.put(definitionTokens.get(i).getText(), -(i / 2 - 1) - 1);
466                                        break;
467                                }
468                        } else if (definitionTokens.get(i).getType() == ETokenType.ELLIPSIS) {
469                                argumentNameToPosition.put(DEFAULT_VAR_ARGS_NAME, -(i / 2 - 1) - 1);
470                        } else {
471                                break;
472                        }
473                }
474                return argumentNameToPosition;
475        }
476
477        /**
478         * Returns the number of tokens that are used to describe the given macro
479         * arguments.
480         */
481        private static int determineArgumentTokenCount(List<List<IToken>> actualArguments) {
482                int additionalTokens = 1 + actualArguments.size();
483                for (List<IToken> argument : actualArguments) {
484                        additionalTokens += argument.size();
485                }
486                return additionalTokens;
487        }
488
489        /** Extracts the actual arguments to a macro. */
490        private static List<List<IToken>> extractActualArguments(List<IToken> tokens, int macroIndex) {
491                List<List<IToken>> actualArguments = new ArrayList<>();
492                List<IToken> currentArgument = new ArrayList<>();
493                int nesting = 0;
494                for (int i = macroIndex + 2; i < tokens.size(); ++i) {
495                        switch (tokens.get(i).getType()) {
496                        case LPAREN:
497                                nesting += 1;
498                                currentArgument.add(tokens.get(i));
499                                break;
500                        case RPAREN:
501                                if (nesting == 0) {
502                                        actualArguments.add(currentArgument);
503                                        return actualArguments;
504                                }
505
506                                nesting -= 1;
507                                currentArgument.add(tokens.get(i));
508                                break;
509                        case COMMA:
510                                if (nesting > 0) {
511                                        currentArgument.add(tokens.get(i));
512                                } else {
513                                        actualArguments.add(currentArgument);
514                                        currentArgument = new ArrayList<>();
515                                }
516                                break;
517                        default:
518                                currentArgument.add(tokens.get(i));
519                        }
520                }
521                return actualArguments;
522        }
523
524        /**
525         * Returns whether the macro defined by the given tokens is a function macro.
526         * This is the case if the second token is a parenthesis and follows
527         * <b>without</b> any whitespace to the macro's name.
528         */
529        private static boolean isFunctionMacro(List<IToken> definitionTokens) {
530                if (definitionTokens.size() < 2) {
531                        return false;
532                }
533
534                IToken nameToken = definitionTokens.get(0);
535                IToken secondToken = definitionTokens.get(1);
536
537                return secondToken.getType() == ETokenType.LPAREN && nameToken.getEndOffset() + 1 == secondToken.getOffset();
538        }
539
540        /**
541         * Expands a macro's content. This applies recursive macro expansion to both the
542         * arguments and the completed expansion (also see
543         * https://gcc.gnu.org/onlinedocs/cpp/Argument-Prescan.html for an explanation
544         * of this).
545         */
546        private void expandMacroContent(String currentMacroName, List<IToken> macroContent,
547                        Map<String, Integer> parameterNameToPosition, List<List<IToken>> actualArguments, List<IToken> result,
548                        int expansionLimit, HashSet<String> ignoreMacros) {
549                List<IToken> expansion = new ArrayList<>();
550
551                for (int i = 0; i < macroContent.size(); ++i) {
552                        IToken token = macroContent.get(i);
553                        if (i + 2 < macroContent.size() && macroContent.get(i + 1).getType() == ETokenType.CONCATENATION) {
554                                List<IToken> argument1 = getParameterValue(macroContent, parameterNameToPosition, actualArguments, i);
555                                List<IToken> argument2 = getParameterValue(macroContent, parameterNameToPosition, actualArguments,
556                                                i + 2);
557                                appendMerged(argument1, argument2, expansion);
558                                i += 2;
559                        } else if (token.getType() == ETokenType.HASH && i + 1 < macroContent.size()) {
560                                i += 1;
561                                String text = toStringLiteral(
562                                                getParameterValue(macroContent, parameterNameToPosition, actualArguments, i));
563                                expansion.add(TokenStreamUtils.createToken(token, text, ETokenType.STRING_LITERAL));
564                        } else if (token.getType() == ETokenType.IDENTIFIER
565                                        && parameterNameToPosition.containsKey(token.getText())) {
566                                expansion.addAll(
567                                                expandParameter(token, parameterNameToPosition, actualArguments, ignoreMacros, expansionLimit));
568                        } else {
569                                expansion.add(token);
570                        }
571                }
572                List<IToken> expanded = new ArrayList<>();
573                ignoreMacros.add(currentMacroName);
574                appendWithMacroExpansion(expansion, expansionLimit, ignoreMacros, expanded);
575                ignoreMacros.remove(currentMacroName);
576                result.addAll(expanded);
577        }
578
579        private static List<IToken> getParameterValue(List<IToken> macroContent,
580                        Map<String, Integer> parameterNameToPosition, List<List<IToken>> actualArguments, int i) {
581                String parameterName = macroContent.get(i).getText();
582                if (parameterNameToPosition.containsKey(parameterName)) {
583                        int parameterPosition = parameterNameToPosition.get(parameterName);
584                        if (parameterPosition < 0) {
585                                return extractVarArgs(actualArguments, parameterPosition);
586                        }
587                        return actualArguments.get(parameterPosition);
588                }
589                return Collections.singletonList(macroContent.get(i));
590        }
591
592        /**
593         * Appends both token lists to the result list but merges the joining tokens.
594         */
595        private static void appendMerged(List<IToken> argument1, List<IToken> argument2, List<IToken> result) {
596                if (argument1.isEmpty() || argument2.isEmpty()) {
597                        result.addAll(argument1);
598                        result.addAll(argument2);
599                        return;
600                }
601
602                result.addAll(argument1.subList(0, argument1.size() - 1));
603
604                IToken merge1 = CollectionUtils.getLast(argument1);
605                IToken merge2 = argument2.get(0);
606                result.addAll(parseMacroContent(merge1.getText() + merge2.getText()));
607
608                result.addAll(argument2.subList(1, argument2.size()));
609        }
610
611        /**
612         * Returns the expanded parameter. Returns the parameter name token itself, if
613         * the name is not a valid parameter name.
614         */
615        private List<IToken> expandParameter(IToken parameterNameToken, Map<String, Integer> parameterNameToPosition,
616                        List<List<IToken>> actualArguments, HashSet<String> ignoreMacros, int expansionLimit) {
617                Integer index = parameterNameToPosition.get(parameterNameToken.getText());
618                if (index == null) {
619                        return Collections.singletonList(parameterNameToken);
620                }
621                if (index >= actualArguments.size()) {
622                        return CollectionUtils.emptyList();
623                }
624
625                // handle var args
626                if (index < 0) {
627                        List<IToken> result = extractVarArgs(actualArguments, index);
628                        List<IToken> expandedResult = new ArrayList<>();
629                        appendWithRepeatedMacroExpansion(result, expansionLimit, ignoreMacros, expandedResult);
630                        return expandedResult;
631                }
632                List<IToken> expandedResult = new ArrayList<>();
633                appendWithRepeatedMacroExpansion(actualArguments.get(index), expansionLimit, ignoreMacros, expandedResult);
634                return expandedResult;
635        }
636
637        /**
638         * Extracts the var args from the given list of actual arguments. Var args are
639         * specified by "..." in the list of formal arguments and used with __VA_ARGS__
640         * in the macro replacement.
641         *
642         * @param varargsStart
643         *            negated number (1-based) of the first varargs argument. E.g., in
644         *            <code>#define FOO(a,b, ...)
645         *                  FOO(1,2,3,4,5)</code>, varargsStart would be -3
646         */
647        @Nonnull
648        private static List<IToken> extractVarArgs(List<List<IToken>> actualArguments, Integer varargsStart) {
649                List<IToken> result = new ArrayList<>();
650                for (int i = -(varargsStart + 1); i < actualArguments.size(); ++i) {
651                        if (!result.isEmpty()) {
652                                IToken last = CollectionUtils.getLast(result);
653                                result.add(last.newToken(ETokenType.COMMA, last.getEndOffset(), last.getLineNumber(), ",",
654                                                last.getOriginId()));
655                        }
656                        result.addAll(actualArguments.get(i));
657                }
658                return result;
659        }
660
661        /** Converts tokens to a string literal. */
662        private static String toStringLiteral(List<IToken> tokens) {
663                StringBuilder builder = new StringBuilder("\"");
664                IToken previous = null;
665                for (IToken token : tokens) {
666                        if (previous != null) {
667                                int count = token.getOffset() - previous.getEndOffset() - 1;
668                                for (int i = 0; i < count; ++i) {
669                                        builder.append(StringUtils.SPACE);
670                                }
671                        }
672
673                        builder.append(token.getText());
674                        previous = token;
675                }
676                builder.append("\"");
677                return builder.toString();
678        }
679
680        /**
681         * Appends the given input tokens to the result, applying macro expansion along
682         * the way.
683         */
684        private void appendWithMacroExpansion(List<IToken> input, int expansionLimit, HashSet<String> ignoreMacros,
685                        List<IToken> result) {
686                for (int i = 0; i < input.size(); ++i) {
687                        IToken token = input.get(i);
688                        if (token.getType() == ETokenType.IDENTIFIER && macroProvider.isDefined(token.getText())) {
689                                i += expandMacro(input, i, result, expansionLimit, ignoreMacros);
690                        } else {
691                                result.add(token);
692                        }
693                }
694        }
695
696        /**
697         * Appends the given input tokens to the result, applying macro expansion as
698         * often as possible (within the expansion limit).
699         */
700        private void appendWithRepeatedMacroExpansion(List<IToken> input, int expansionLimit, HashSet<String> ignoreMacros,
701                        List<IToken> result) {
702                Comparator<List<IToken>> tokenListComparator = CollectionUtils
703                                .getListComparator(Comparator.comparing(IToken::getText));
704                List<IToken> previousExpanded = new ArrayList<>();
705                List<IToken> expanded = new ArrayList<>();
706
707                appendWithMacroExpansion(input, expansionLimit, ignoreMacros, expanded);
708                while (tokenListComparator.compare(previousExpanded, expanded) != 0) {
709                        if (expanded.size() > input.size() * 100) {
710                                // abort if expanded tokens gets much larger than initial tokens (explosion)
711                                LogManager.getLogger()
712                                                .warn("Preprocessing generated too many tokens in rescanning step of expanded tokens in file "
713                                                                + uniformPath + " Ignoring partial expansion.");
714                                result = input;
715                                return;
716                        }
717                        previousExpanded = expanded;
718                        expanded = new ArrayList<>();
719                        appendWithMacroExpansion(previousExpanded, expansionLimit, ignoreMacros, expanded);
720                }
721                result.addAll(expanded);
722        }
723}