MacroExpander.cpp (15924B)
1 // 2 // Copyright 2011 The ANGLE Project Authors. All rights reserved. 3 // Use of this source code is governed by a BSD-style license that can be 4 // found in the LICENSE file. 5 // 6 7 #include "compiler/preprocessor/MacroExpander.h" 8 9 #include <GLSLANG/ShaderLang.h> 10 #include <algorithm> 11 12 #include "common/debug.h" 13 #include "compiler/preprocessor/DiagnosticsBase.h" 14 #include "compiler/preprocessor/Token.h" 15 16 namespace angle 17 { 18 19 namespace pp 20 { 21 22 namespace 23 { 24 25 const size_t kMaxContextTokens = 10000; 26 27 class TokenLexer : public Lexer 28 { 29 public: 30 typedef std::vector<Token> TokenVector; 31 32 TokenLexer(TokenVector *tokens) 33 { 34 tokens->swap(mTokens); 35 mIter = mTokens.begin(); 36 } 37 38 void lex(Token *token) override 39 { 40 if (mIter == mTokens.end()) 41 { 42 token->reset(); 43 token->type = Token::LAST; 44 } 45 else 46 { 47 *token = *mIter++; 48 } 49 } 50 51 private: 52 TokenVector mTokens; 53 TokenVector::const_iterator mIter; 54 }; 55 56 } // anonymous namespace 57 58 class [[nodiscard]] MacroExpander::ScopedMacroReenabler final : angle::NonCopyable 59 { 60 public: 61 ScopedMacroReenabler(MacroExpander *expander); 62 ~ScopedMacroReenabler(); 63 64 private: 65 MacroExpander *mExpander; 66 }; 67 68 MacroExpander::ScopedMacroReenabler::ScopedMacroReenabler(MacroExpander *expander) 69 : mExpander(expander) 70 { 71 mExpander->mDeferReenablingMacros = true; 72 } 73 74 MacroExpander::ScopedMacroReenabler::~ScopedMacroReenabler() 75 { 76 mExpander->mDeferReenablingMacros = false; 77 for (const std::shared_ptr<Macro> ¯o : mExpander->mMacrosToReenable) 78 { 79 // Copying the string here by using substr is a check for use-after-free. It detects 80 // use-after-free more reliably than just toggling the disabled flag. 81 ASSERT(macro->name.substr() != ""); 82 macro->disabled = false; 83 } 84 mExpander->mMacrosToReenable.clear(); 85 } 86 87 MacroExpander::MacroExpander(Lexer *lexer, 88 MacroSet *macroSet, 89 Diagnostics *diagnostics, 90 const PreprocessorSettings &settings, 91 bool parseDefined) 92 : mLexer(lexer), 93 mMacroSet(macroSet), 94 mDiagnostics(diagnostics), 95 mParseDefined(parseDefined), 96 mTotalTokensInContexts(0), 97 mSettings(settings), 98 mDeferReenablingMacros(false) 99 {} 100 101 MacroExpander::~MacroExpander() 102 { 103 ASSERT(mMacrosToReenable.empty()); 104 for (MacroContext *context : mContextStack) 105 { 106 delete context; 107 } 108 } 109 110 void MacroExpander::lex(Token *token) 111 { 112 while (true) 113 { 114 getToken(token); 115 116 if (token->type != Token::IDENTIFIER) 117 break; 118 119 // Defined operator is parsed here since it may be generated by macro expansion. 120 // Defined operator produced by macro expansion has undefined behavior according to C++ 121 // spec, which the GLSL spec references (see C++14 draft spec section 16.1.4), but this 122 // behavior is needed for passing dEQP tests, which enforce stricter compatibility between 123 // implementations. 124 if (mParseDefined && token->text == kDefined) 125 { 126 // Defined inside a macro is forbidden in WebGL. 127 if (!mContextStack.empty() && sh::IsWebGLBasedSpec(mSettings.shaderSpec)) 128 break; 129 130 bool paren = false; 131 getToken(token); 132 if (token->type == '(') 133 { 134 paren = true; 135 getToken(token); 136 } 137 if (token->type != Token::IDENTIFIER) 138 { 139 mDiagnostics->report(Diagnostics::PP_UNEXPECTED_TOKEN, token->location, 140 token->text); 141 break; 142 } 143 auto iter = mMacroSet->find(token->text); 144 std::string expression = iter != mMacroSet->end() ? "1" : "0"; 145 146 if (paren) 147 { 148 getToken(token); 149 if (token->type != ')') 150 { 151 mDiagnostics->report(Diagnostics::PP_UNEXPECTED_TOKEN, token->location, 152 token->text); 153 break; 154 } 155 } 156 157 // We have a valid defined operator. 158 // Convert the current token into a CONST_INT token. 159 token->type = Token::CONST_INT; 160 token->text = expression; 161 break; 162 } 163 164 if (token->expansionDisabled()) 165 break; 166 167 MacroSet::const_iterator iter = mMacroSet->find(token->text); 168 if (iter == mMacroSet->end()) 169 break; 170 171 std::shared_ptr<Macro> macro = iter->second; 172 if (macro->disabled) 173 { 174 // If a particular token is not expanded, it is never expanded. 175 token->setExpansionDisabled(true); 176 break; 177 } 178 179 // Bump the expansion count before peeking if the next token is a '(' 180 // otherwise there could be a #undef of the macro before the next token. 181 macro->expansionCount++; 182 if ((macro->type == Macro::kTypeFunc) && !isNextTokenLeftParen()) 183 { 184 // If the token immediately after the macro name is not a '(', 185 // this macro should not be expanded. 186 macro->expansionCount--; 187 break; 188 } 189 190 pushMacro(macro, *token); 191 } 192 } 193 194 void MacroExpander::getToken(Token *token) 195 { 196 if (mReserveToken.get()) 197 { 198 *token = *mReserveToken; 199 mReserveToken.reset(); 200 return; 201 } 202 203 // First pop all empty macro contexts. 204 while (!mContextStack.empty() && mContextStack.back()->empty()) 205 { 206 popMacro(); 207 } 208 209 if (!mContextStack.empty()) 210 { 211 *token = mContextStack.back()->get(); 212 } 213 else 214 { 215 ASSERT(mTotalTokensInContexts == 0); 216 mLexer->lex(token); 217 } 218 } 219 220 void MacroExpander::ungetToken(const Token &token) 221 { 222 if (!mContextStack.empty()) 223 { 224 MacroContext *context = mContextStack.back(); 225 context->unget(); 226 ASSERT(context->replacements[context->index] == token); 227 } 228 else 229 { 230 ASSERT(!mReserveToken.get()); 231 mReserveToken.reset(new Token(token)); 232 } 233 } 234 235 bool MacroExpander::isNextTokenLeftParen() 236 { 237 Token token; 238 getToken(&token); 239 240 bool lparen = token.type == '('; 241 ungetToken(token); 242 243 return lparen; 244 } 245 246 bool MacroExpander::pushMacro(std::shared_ptr<Macro> macro, const Token &identifier) 247 { 248 ASSERT(!macro->disabled); 249 ASSERT(!identifier.expansionDisabled()); 250 ASSERT(identifier.type == Token::IDENTIFIER); 251 ASSERT(identifier.text == macro->name); 252 253 std::vector<Token> replacements; 254 if (!expandMacro(*macro, identifier, &replacements)) 255 return false; 256 257 // Macro is disabled for expansion until it is popped off the stack. 258 macro->disabled = true; 259 260 MacroContext *context = new MacroContext; 261 context->macro = macro; 262 context->replacements.swap(replacements); 263 mContextStack.push_back(context); 264 mTotalTokensInContexts += context->replacements.size(); 265 return true; 266 } 267 268 void MacroExpander::popMacro() 269 { 270 ASSERT(!mContextStack.empty()); 271 272 MacroContext *context = mContextStack.back(); 273 mContextStack.pop_back(); 274 275 ASSERT(context->empty()); 276 ASSERT(context->macro->disabled); 277 ASSERT(context->macro->expansionCount > 0); 278 if (mDeferReenablingMacros) 279 { 280 mMacrosToReenable.push_back(context->macro); 281 } 282 else 283 { 284 context->macro->disabled = false; 285 } 286 context->macro->expansionCount--; 287 mTotalTokensInContexts -= context->replacements.size(); 288 delete context; 289 } 290 291 bool MacroExpander::expandMacro(const Macro ¯o, 292 const Token &identifier, 293 std::vector<Token> *replacements) 294 { 295 replacements->clear(); 296 297 // In the case of an object-like macro, the replacement list gets its location 298 // from the identifier, but in the case of a function-like macro, the replacement 299 // list gets its location from the closing parenthesis of the macro invocation. 300 // This is tested by dEQP-GLES3.functional.shaders.preprocessor.predefined_macros.* 301 SourceLocation replacementLocation = identifier.location; 302 if (macro.type == Macro::kTypeObj) 303 { 304 replacements->assign(macro.replacements.begin(), macro.replacements.end()); 305 306 if (macro.predefined) 307 { 308 const char kLine[] = "__LINE__"; 309 const char kFile[] = "__FILE__"; 310 311 ASSERT(replacements->size() == 1); 312 Token &repl = replacements->front(); 313 if (macro.name == kLine) 314 { 315 repl.text = ToString(identifier.location.line); 316 } 317 else if (macro.name == kFile) 318 { 319 repl.text = ToString(identifier.location.file); 320 } 321 } 322 } 323 else 324 { 325 ASSERT(macro.type == Macro::kTypeFunc); 326 std::vector<MacroArg> args; 327 args.reserve(macro.parameters.size()); 328 if (!collectMacroArgs(macro, identifier, &args, &replacementLocation)) 329 return false; 330 331 replaceMacroParams(macro, args, replacements); 332 } 333 334 for (std::size_t i = 0; i < replacements->size(); ++i) 335 { 336 Token &repl = replacements->at(i); 337 if (i == 0) 338 { 339 // The first token in the replacement list inherits the padding 340 // properties of the identifier token. 341 repl.setAtStartOfLine(identifier.atStartOfLine()); 342 repl.setHasLeadingSpace(identifier.hasLeadingSpace()); 343 } 344 repl.location = replacementLocation; 345 } 346 return true; 347 } 348 349 bool MacroExpander::collectMacroArgs(const Macro ¯o, 350 const Token &identifier, 351 std::vector<MacroArg> *args, 352 SourceLocation *closingParenthesisLocation) 353 { 354 Token token; 355 getToken(&token); 356 ASSERT(token.type == '('); 357 358 args->push_back(MacroArg()); 359 360 // Defer reenabling macros until args collection is finished to avoid the possibility of 361 // infinite recursion. Otherwise infinite recursion might happen when expanding the args after 362 // macros have been popped from the context stack when parsing the args. 363 ScopedMacroReenabler deferReenablingMacros(this); 364 365 int openParens = 1; 366 while (openParens != 0) 367 { 368 getToken(&token); 369 370 if (token.type == Token::LAST) 371 { 372 mDiagnostics->report(Diagnostics::PP_MACRO_UNTERMINATED_INVOCATION, identifier.location, 373 identifier.text); 374 // Do not lose EOF token. 375 ungetToken(token); 376 return false; 377 } 378 379 bool isArg = false; // True if token is part of the current argument. 380 switch (token.type) 381 { 382 case '(': 383 ++openParens; 384 isArg = true; 385 break; 386 case ')': 387 --openParens; 388 isArg = openParens != 0; 389 *closingParenthesisLocation = token.location; 390 break; 391 case ',': 392 // The individual arguments are separated by comma tokens, but 393 // the comma tokens between matching inner parentheses do not 394 // seperate arguments. 395 if (openParens == 1) 396 args->push_back(MacroArg()); 397 isArg = openParens != 1; 398 break; 399 default: 400 isArg = true; 401 break; 402 } 403 if (isArg) 404 { 405 MacroArg &arg = args->back(); 406 // Initial whitespace is not part of the argument. 407 if (arg.empty()) 408 token.setHasLeadingSpace(false); 409 arg.push_back(token); 410 } 411 } 412 413 const Macro::Parameters ¶ms = macro.parameters; 414 // If there is only one empty argument, it is equivalent to no argument. 415 if (params.empty() && (args->size() == 1) && args->front().empty()) 416 { 417 args->clear(); 418 } 419 // Validate the number of arguments. 420 if (args->size() != params.size()) 421 { 422 Diagnostics::ID id = args->size() < macro.parameters.size() 423 ? Diagnostics::PP_MACRO_TOO_FEW_ARGS 424 : Diagnostics::PP_MACRO_TOO_MANY_ARGS; 425 mDiagnostics->report(id, identifier.location, identifier.text); 426 return false; 427 } 428 429 // Pre-expand each argument before substitution. 430 // This step expands each argument individually before they are 431 // inserted into the macro body. 432 size_t numTokens = 0; 433 for (auto &arg : *args) 434 { 435 TokenLexer lexer(&arg); 436 if (mSettings.maxMacroExpansionDepth < 1) 437 { 438 mDiagnostics->report(Diagnostics::PP_MACRO_INVOCATION_CHAIN_TOO_DEEP, token.location, 439 token.text); 440 return false; 441 } 442 PreprocessorSettings nestedSettings(mSettings.shaderSpec); 443 nestedSettings.maxMacroExpansionDepth = mSettings.maxMacroExpansionDepth - 1; 444 MacroExpander expander(&lexer, mMacroSet, mDiagnostics, nestedSettings, mParseDefined); 445 446 arg.clear(); 447 expander.lex(&token); 448 while (token.type != Token::LAST) 449 { 450 arg.push_back(token); 451 expander.lex(&token); 452 numTokens++; 453 if (numTokens + mTotalTokensInContexts > kMaxContextTokens) 454 { 455 mDiagnostics->report(Diagnostics::PP_OUT_OF_MEMORY, token.location, token.text); 456 return false; 457 } 458 } 459 } 460 return true; 461 } 462 463 void MacroExpander::replaceMacroParams(const Macro ¯o, 464 const std::vector<MacroArg> &args, 465 std::vector<Token> *replacements) 466 { 467 for (std::size_t i = 0; i < macro.replacements.size(); ++i) 468 { 469 if (!replacements->empty() && 470 replacements->size() + mTotalTokensInContexts > kMaxContextTokens) 471 { 472 const Token &token = replacements->back(); 473 mDiagnostics->report(Diagnostics::PP_OUT_OF_MEMORY, token.location, token.text); 474 return; 475 } 476 477 const Token &repl = macro.replacements[i]; 478 if (repl.type != Token::IDENTIFIER) 479 { 480 replacements->push_back(repl); 481 continue; 482 } 483 484 // TODO(alokp): Optimize this. 485 // There is no need to search for macro params every time. 486 // The param index can be cached with the replacement token. 487 Macro::Parameters::const_iterator iter = 488 std::find(macro.parameters.begin(), macro.parameters.end(), repl.text); 489 if (iter == macro.parameters.end()) 490 { 491 replacements->push_back(repl); 492 continue; 493 } 494 495 std::size_t iArg = std::distance(macro.parameters.begin(), iter); 496 const MacroArg &arg = args[iArg]; 497 if (arg.empty()) 498 { 499 continue; 500 } 501 std::size_t iRepl = replacements->size(); 502 replacements->insert(replacements->end(), arg.begin(), arg.end()); 503 // The replacement token inherits padding properties from 504 // macro replacement token. 505 replacements->at(iRepl).setHasLeadingSpace(repl.hasLeadingSpace()); 506 } 507 } 508 509 MacroExpander::MacroContext::MacroContext() : macro(0), index(0) {} 510 511 MacroExpander::MacroContext::~MacroContext() {} 512 513 bool MacroExpander::MacroContext::empty() const 514 { 515 return index == replacements.size(); 516 } 517 518 const Token &MacroExpander::MacroContext::get() 519 { 520 return replacements[index++]; 521 } 522 523 void MacroExpander::MacroContext::unget() 524 { 525 ASSERT(index > 0); 526 --index; 527 } 528 529 } // namespace pp 530 531 } // namespace angle