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}