blob: 11f677e23b7157004c918534ab5393671d8b2a4a [file] [log] [blame]
alokp@chromium.org04d7d222012-05-16 19:24:07 +00001//
2// Copyright (c) 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
Corentin Wallez054f7ed2016-09-20 17:15:59 -04007#include "compiler/preprocessor/MacroExpander.h"
alokp@chromium.org04d7d222012-05-16 19:24:07 +00008
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +00009#include <algorithm>
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000010
Corentin Wallez054f7ed2016-09-20 17:15:59 -040011#include "common/debug.h"
12#include "compiler/preprocessor/DiagnosticsBase.h"
13#include "compiler/preprocessor/Token.h"
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000014
alokp@chromium.org04d7d222012-05-16 19:24:07 +000015namespace pp
16{
17
Corentin Wallez2bd9c442016-09-20 16:39:18 -040018namespace
19{
20
21const size_t kMaxContextTokens = 10000;
22
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000023class TokenLexer : public Lexer
24{
25 public:
26 typedef std::vector<Token> TokenVector;
27
Zhenyao Mod526f982014-05-13 14:51:19 -070028 TokenLexer(TokenVector *tokens)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000029 {
30 tokens->swap(mTokens);
31 mIter = mTokens.begin();
32 }
33
Corentin Walleze5a1f272015-08-21 02:58:25 +020034 void lex(Token *token) override
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000035 {
36 if (mIter == mTokens.end())
37 {
38 token->reset();
39 token->type = Token::LAST;
40 }
41 else
42 {
43 *token = *mIter++;
44 }
45 }
46
47 private:
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000048 TokenVector mTokens;
49 TokenVector::const_iterator mIter;
50};
51
Corentin Wallez2bd9c442016-09-20 16:39:18 -040052} // anonymous namespace
53
Olli Etuaho78b0c912016-11-21 14:23:06 +000054class MacroExpander::ScopedMacroReenabler final : angle::NonCopyable
55{
56 public:
57 ScopedMacroReenabler(MacroExpander *expander);
58 ~ScopedMacroReenabler();
59
60 private:
61 MacroExpander *mExpander;
62};
63
64MacroExpander::ScopedMacroReenabler::ScopedMacroReenabler(MacroExpander *expander)
65 : mExpander(expander)
66{
67 mExpander->mDeferReenablingMacros = true;
68}
69
70MacroExpander::ScopedMacroReenabler::~ScopedMacroReenabler()
71{
72 mExpander->mDeferReenablingMacros = false;
73 for (auto *macro : mExpander->mMacrosToReenable)
74 {
75 macro->disabled = false;
76 }
77 mExpander->mMacrosToReenable.clear();
78}
79
Olli Etuaho1b2f1622016-03-04 15:06:51 +020080MacroExpander::MacroExpander(Lexer *lexer, MacroSet *macroSet, Diagnostics *diagnostics)
Olli Etuaho78b0c912016-11-21 14:23:06 +000081 : mLexer(lexer),
82 mMacroSet(macroSet),
83 mDiagnostics(diagnostics),
84 mTotalTokensInContexts(0),
85 mDeferReenablingMacros(false)
alokp@chromium.org04d7d222012-05-16 19:24:07 +000086{
87}
88
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000089MacroExpander::~MacroExpander()
90{
Olli Etuaho78b0c912016-11-21 14:23:06 +000091 ASSERT(mMacrosToReenable.empty());
Corentin Wallez054f7ed2016-09-20 17:15:59 -040092 for (MacroContext *context : mContextStack)
alokp@chromium.org6c0c2d82012-07-18 15:49:56 +000093 {
Corentin Wallez054f7ed2016-09-20 17:15:59 -040094 delete context;
alokp@chromium.org6c0c2d82012-07-18 15:49:56 +000095 }
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000096}
97
Zhenyao Mod526f982014-05-13 14:51:19 -070098void MacroExpander::lex(Token *token)
alokp@chromium.org04d7d222012-05-16 19:24:07 +000099{
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000100 while (true)
101 {
102 getToken(token);
103
104 if (token->type != Token::IDENTIFIER)
105 break;
106
107 if (token->expansionDisabled())
108 break;
109
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +0000110 MacroSet::const_iterator iter = mMacroSet->find(token->text);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000111 if (iter == mMacroSet->end())
112 break;
113
114 const Macro& macro = iter->second;
115 if (macro.disabled)
116 {
117 // If a particular token is not expanded, it is never expanded.
118 token->setExpansionDisabled(true);
119 break;
120 }
Corentin Wallez449a8032016-10-26 08:05:54 -0400121
122 // Bump the expansion count before peeking if the next token is a '('
123 // otherwise there could be a #undef of the macro before the next token.
124 macro.expansionCount++;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000125 if ((macro.type == Macro::kTypeFunc) && !isNextTokenLeftParen())
126 {
127 // If the token immediately after the macro name is not a '(',
128 // this macro should not be expanded.
Corentin Wallez449a8032016-10-26 08:05:54 -0400129 macro.expansionCount--;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000130 break;
131 }
132
133 pushMacro(macro, *token);
134 }
135}
136
Zhenyao Mod526f982014-05-13 14:51:19 -0700137void MacroExpander::getToken(Token *token)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000138{
139 if (mReserveToken.get())
140 {
141 *token = *mReserveToken;
142 mReserveToken.reset();
143 return;
144 }
145
146 // First pop all empty macro contexts.
147 while (!mContextStack.empty() && mContextStack.back()->empty())
148 {
149 popMacro();
150 }
151
152 if (!mContextStack.empty())
153 {
154 *token = mContextStack.back()->get();
155 }
156 else
157 {
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400158 ASSERT(mTotalTokensInContexts == 0);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000159 mLexer->lex(token);
160 }
161}
162
Zhenyao Mod526f982014-05-13 14:51:19 -0700163void MacroExpander::ungetToken(const Token &token)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000164{
165 if (!mContextStack.empty())
166 {
Zhenyao Mod526f982014-05-13 14:51:19 -0700167 MacroContext *context = mContextStack.back();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000168 context->unget();
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400169 ASSERT(context->replacements[context->index] == token);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000170 }
171 else
172 {
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400173 ASSERT(!mReserveToken.get());
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000174 mReserveToken.reset(new Token(token));
175 }
176}
177
178bool MacroExpander::isNextTokenLeftParen()
179{
180 Token token;
181 getToken(&token);
182
183 bool lparen = token.type == '(';
184 ungetToken(token);
185
186 return lparen;
187}
188
Zhenyao Mod526f982014-05-13 14:51:19 -0700189bool MacroExpander::pushMacro(const Macro &macro, const Token &identifier)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000190{
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400191 ASSERT(!macro.disabled);
192 ASSERT(!identifier.expansionDisabled());
193 ASSERT(identifier.type == Token::IDENTIFIER);
194 ASSERT(identifier.text == macro.name);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000195
196 std::vector<Token> replacements;
197 if (!expandMacro(macro, identifier, &replacements))
198 return false;
199
200 // Macro is disabled for expansion until it is popped off the stack.
201 macro.disabled = true;
202
Zhenyao Mod526f982014-05-13 14:51:19 -0700203 MacroContext *context = new MacroContext;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000204 context->macro = &macro;
205 context->replacements.swap(replacements);
206 mContextStack.push_back(context);
Corentin Wallez2bd9c442016-09-20 16:39:18 -0400207 mTotalTokensInContexts += context->replacements.size();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000208 return true;
209}
210
211void MacroExpander::popMacro()
212{
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400213 ASSERT(!mContextStack.empty());
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000214
Zhenyao Mod526f982014-05-13 14:51:19 -0700215 MacroContext *context = mContextStack.back();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000216 mContextStack.pop_back();
217
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400218 ASSERT(context->empty());
219 ASSERT(context->macro->disabled);
220 ASSERT(context->macro->expansionCount > 0);
Olli Etuaho78b0c912016-11-21 14:23:06 +0000221 if (mDeferReenablingMacros)
222 {
223 mMacrosToReenable.push_back(context->macro);
224 }
225 else
226 {
227 context->macro->disabled = false;
228 }
Corentin Wallezd2f195b2016-09-19 15:53:33 -0400229 context->macro->expansionCount--;
Corentin Wallez2bd9c442016-09-20 16:39:18 -0400230 mTotalTokensInContexts -= context->replacements.size();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000231 delete context;
232}
233
Zhenyao Mod526f982014-05-13 14:51:19 -0700234bool MacroExpander::expandMacro(const Macro &macro,
235 const Token &identifier,
236 std::vector<Token> *replacements)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000237{
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000238 replacements->clear();
Olli Etuahoe6432c82015-09-08 14:21:38 +0300239
240 // In the case of an object-like macro, the replacement list gets its location
241 // from the identifier, but in the case of a function-like macro, the replacement
242 // list gets its location from the closing parenthesis of the macro invocation.
243 // This is tested by dEQP-GLES3.functional.shaders.preprocessor.predefined_macros.*
244 SourceLocation replacementLocation = identifier.location;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000245 if (macro.type == Macro::kTypeObj)
246 {
247 replacements->assign(macro.replacements.begin(),
248 macro.replacements.end());
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000249
250 if (macro.predefined)
251 {
Zhenyao Mob5e17752014-10-22 10:57:10 -0700252 const char kLine[] = "__LINE__";
253 const char kFile[] = "__FILE__";
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000254
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400255 ASSERT(replacements->size() == 1);
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000256 Token& repl = replacements->front();
257 if (macro.name == kLine)
258 {
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400259 repl.text = ToString(identifier.location.line);
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000260 }
261 else if (macro.name == kFile)
262 {
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400263 repl.text = ToString(identifier.location.file);
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000264 }
265 }
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000266 }
267 else
268 {
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400269 ASSERT(macro.type == Macro::kTypeFunc);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000270 std::vector<MacroArg> args;
271 args.reserve(macro.parameters.size());
Olli Etuahoe6432c82015-09-08 14:21:38 +0300272 if (!collectMacroArgs(macro, identifier, &args, &replacementLocation))
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000273 return false;
274
275 replaceMacroParams(macro, args, replacements);
276 }
277
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +0000278 for (std::size_t i = 0; i < replacements->size(); ++i)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000279 {
280 Token& repl = replacements->at(i);
281 if (i == 0)
282 {
283 // The first token in the replacement list inherits the padding
284 // properties of the identifier token.
285 repl.setAtStartOfLine(identifier.atStartOfLine());
286 repl.setHasLeadingSpace(identifier.hasLeadingSpace());
287 }
Olli Etuahoe6432c82015-09-08 14:21:38 +0300288 repl.location = replacementLocation;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000289 }
290 return true;
291}
292
Zhenyao Mod526f982014-05-13 14:51:19 -0700293bool MacroExpander::collectMacroArgs(const Macro &macro,
294 const Token &identifier,
Olli Etuahoe6432c82015-09-08 14:21:38 +0300295 std::vector<MacroArg> *args,
296 SourceLocation *closingParenthesisLocation)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000297{
298 Token token;
299 getToken(&token);
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400300 ASSERT(token.type == '(');
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000301
302 args->push_back(MacroArg());
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400303
Olli Etuaho78b0c912016-11-21 14:23:06 +0000304 // Defer reenabling macros until args collection is finished to avoid the possibility of
305 // infinite recursion. Otherwise infinite recursion might happen when expanding the args after
306 // macros have been popped from the context stack when parsing the args.
307 ScopedMacroReenabler deferReenablingMacros(this);
308
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400309 int openParens = 1;
310 while (openParens != 0)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000311 {
312 getToken(&token);
313
314 if (token.type == Token::LAST)
315 {
Shannon Woods7f2d7942013-11-19 15:07:58 -0500316 mDiagnostics->report(Diagnostics::PP_MACRO_UNTERMINATED_INVOCATION,
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +0000317 identifier.location, identifier.text);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000318 // Do not lose EOF token.
319 ungetToken(token);
320 return false;
321 }
322
323 bool isArg = false; // True if token is part of the current argument.
324 switch (token.type)
325 {
326 case '(':
327 ++openParens;
328 isArg = true;
329 break;
330 case ')':
331 --openParens;
332 isArg = openParens != 0;
Olli Etuahoe6432c82015-09-08 14:21:38 +0300333 *closingParenthesisLocation = token.location;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000334 break;
335 case ',':
336 // The individual arguments are separated by comma tokens, but
337 // the comma tokens between matching inner parentheses do not
338 // seperate arguments.
Zhenyao Mod526f982014-05-13 14:51:19 -0700339 if (openParens == 1)
340 args->push_back(MacroArg());
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000341 isArg = openParens != 1;
342 break;
343 default:
344 isArg = true;
345 break;
346 }
347 if (isArg)
348 {
Zhenyao Mod526f982014-05-13 14:51:19 -0700349 MacroArg &arg = args->back();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000350 // Initial whitespace is not part of the argument.
Zhenyao Mod526f982014-05-13 14:51:19 -0700351 if (arg.empty())
352 token.setHasLeadingSpace(false);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000353 arg.push_back(token);
354 }
355 }
356
Zhenyao Mod526f982014-05-13 14:51:19 -0700357 const Macro::Parameters &params = macro.parameters;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000358 // If there is only one empty argument, it is equivalent to no argument.
359 if (params.empty() && (args->size() == 1) && args->front().empty())
360 {
361 args->clear();
362 }
363 // Validate the number of arguments.
364 if (args->size() != params.size())
365 {
366 Diagnostics::ID id = args->size() < macro.parameters.size() ?
Shannon Woods7f2d7942013-11-19 15:07:58 -0500367 Diagnostics::PP_MACRO_TOO_FEW_ARGS :
368 Diagnostics::PP_MACRO_TOO_MANY_ARGS;
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +0000369 mDiagnostics->report(id, identifier.location, identifier.text);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000370 return false;
371 }
372
373 // Pre-expand each argument before substitution.
374 // This step expands each argument individually before they are
375 // inserted into the macro body.
Corentin Wallez2bd9c442016-09-20 16:39:18 -0400376 size_t numTokens = 0;
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400377 for (auto &arg : *args)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000378 {
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000379 TokenLexer lexer(&arg);
Olli Etuaho1b2f1622016-03-04 15:06:51 +0200380 MacroExpander expander(&lexer, mMacroSet, mDiagnostics);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000381
382 arg.clear();
383 expander.lex(&token);
384 while (token.type != Token::LAST)
385 {
386 arg.push_back(token);
387 expander.lex(&token);
Corentin Wallez2bd9c442016-09-20 16:39:18 -0400388 numTokens++;
389 if (numTokens + mTotalTokensInContexts > kMaxContextTokens)
390 {
391 mDiagnostics->report(Diagnostics::PP_OUT_OF_MEMORY, token.location, token.text);
392 return false;
393 }
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000394 }
395 }
396 return true;
397}
398
Zhenyao Mod526f982014-05-13 14:51:19 -0700399void MacroExpander::replaceMacroParams(const Macro &macro,
400 const std::vector<MacroArg> &args,
401 std::vector<Token> *replacements)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000402{
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +0000403 for (std::size_t i = 0; i < macro.replacements.size(); ++i)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000404 {
Corentin Wallez2bd9c442016-09-20 16:39:18 -0400405 if (!replacements->empty() &&
406 replacements->size() + mTotalTokensInContexts > kMaxContextTokens)
407 {
408 const Token &token = replacements->back();
409 mDiagnostics->report(Diagnostics::PP_OUT_OF_MEMORY, token.location, token.text);
410 return;
411 }
412
Zhenyao Mod526f982014-05-13 14:51:19 -0700413 const Token &repl = macro.replacements[i];
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000414 if (repl.type != Token::IDENTIFIER)
415 {
416 replacements->push_back(repl);
417 continue;
418 }
419
420 // TODO(alokp): Optimize this.
421 // There is no need to search for macro params every time.
422 // The param index can be cached with the replacement token.
423 Macro::Parameters::const_iterator iter = std::find(
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +0000424 macro.parameters.begin(), macro.parameters.end(), repl.text);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000425 if (iter == macro.parameters.end())
426 {
427 replacements->push_back(repl);
428 continue;
429 }
430
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +0000431 std::size_t iArg = std::distance(macro.parameters.begin(), iter);
Zhenyao Mod526f982014-05-13 14:51:19 -0700432 const MacroArg &arg = args[iArg];
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000433 if (arg.empty())
434 {
435 continue;
436 }
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +0000437 std::size_t iRepl = replacements->size();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000438 replacements->insert(replacements->end(), arg.begin(), arg.end());
439 // The replacement token inherits padding properties from
440 // macro replacement token.
441 replacements->at(iRepl).setHasLeadingSpace(repl.hasLeadingSpace());
442 }
alokp@chromium.org04d7d222012-05-16 19:24:07 +0000443}
444
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400445MacroExpander::MacroContext::MacroContext() : macro(0), index(0)
446{
447}
448
449bool MacroExpander::MacroContext::empty() const
450{
451 return index == replacements.size();
452}
453
454const Token &MacroExpander::MacroContext::get()
455{
456 return replacements[index++];
457}
458
459void MacroExpander::MacroContext::unget()
460{
461 ASSERT(index > 0);
462 --index;
463}
464
alokp@chromium.org04d7d222012-05-16 19:24:07 +0000465} // namespace pp
466