blob: 25dc741801514958a054b92233f7ef85fd22c5d7 [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 Etuaho1b2f1622016-03-04 15:06:51 +020054MacroExpander::MacroExpander(Lexer *lexer, MacroSet *macroSet, Diagnostics *diagnostics)
Corentin Wallez2bd9c442016-09-20 16:39:18 -040055 : mLexer(lexer), mMacroSet(macroSet), mDiagnostics(diagnostics), mTotalTokensInContexts(0)
alokp@chromium.org04d7d222012-05-16 19:24:07 +000056{
57}
58
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000059MacroExpander::~MacroExpander()
60{
Corentin Wallez054f7ed2016-09-20 17:15:59 -040061 for (MacroContext *context : mContextStack)
alokp@chromium.org6c0c2d82012-07-18 15:49:56 +000062 {
Corentin Wallez054f7ed2016-09-20 17:15:59 -040063 delete context;
alokp@chromium.org6c0c2d82012-07-18 15:49:56 +000064 }
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000065}
66
Zhenyao Mod526f982014-05-13 14:51:19 -070067void MacroExpander::lex(Token *token)
alokp@chromium.org04d7d222012-05-16 19:24:07 +000068{
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000069 while (true)
70 {
71 getToken(token);
72
73 if (token->type != Token::IDENTIFIER)
74 break;
75
76 if (token->expansionDisabled())
77 break;
78
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +000079 MacroSet::const_iterator iter = mMacroSet->find(token->text);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000080 if (iter == mMacroSet->end())
81 break;
82
83 const Macro& macro = iter->second;
84 if (macro.disabled)
85 {
86 // If a particular token is not expanded, it is never expanded.
87 token->setExpansionDisabled(true);
88 break;
89 }
Corentin Wallez449a8032016-10-26 08:05:54 -040090
91 // Bump the expansion count before peeking if the next token is a '('
92 // otherwise there could be a #undef of the macro before the next token.
93 macro.expansionCount++;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000094 if ((macro.type == Macro::kTypeFunc) && !isNextTokenLeftParen())
95 {
96 // If the token immediately after the macro name is not a '(',
97 // this macro should not be expanded.
Corentin Wallez449a8032016-10-26 08:05:54 -040098 macro.expansionCount--;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +000099 break;
100 }
101
102 pushMacro(macro, *token);
103 }
104}
105
Zhenyao Mod526f982014-05-13 14:51:19 -0700106void MacroExpander::getToken(Token *token)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000107{
108 if (mReserveToken.get())
109 {
110 *token = *mReserveToken;
111 mReserveToken.reset();
112 return;
113 }
114
115 // First pop all empty macro contexts.
116 while (!mContextStack.empty() && mContextStack.back()->empty())
117 {
118 popMacro();
119 }
120
121 if (!mContextStack.empty())
122 {
123 *token = mContextStack.back()->get();
124 }
125 else
126 {
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400127 ASSERT(mTotalTokensInContexts == 0);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000128 mLexer->lex(token);
129 }
130}
131
Zhenyao Mod526f982014-05-13 14:51:19 -0700132void MacroExpander::ungetToken(const Token &token)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000133{
134 if (!mContextStack.empty())
135 {
Zhenyao Mod526f982014-05-13 14:51:19 -0700136 MacroContext *context = mContextStack.back();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000137 context->unget();
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400138 ASSERT(context->replacements[context->index] == token);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000139 }
140 else
141 {
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400142 ASSERT(!mReserveToken.get());
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000143 mReserveToken.reset(new Token(token));
144 }
145}
146
147bool MacroExpander::isNextTokenLeftParen()
148{
149 Token token;
150 getToken(&token);
151
152 bool lparen = token.type == '(';
153 ungetToken(token);
154
155 return lparen;
156}
157
Zhenyao Mod526f982014-05-13 14:51:19 -0700158bool MacroExpander::pushMacro(const Macro &macro, const Token &identifier)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000159{
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400160 ASSERT(!macro.disabled);
161 ASSERT(!identifier.expansionDisabled());
162 ASSERT(identifier.type == Token::IDENTIFIER);
163 ASSERT(identifier.text == macro.name);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000164
165 std::vector<Token> replacements;
166 if (!expandMacro(macro, identifier, &replacements))
167 return false;
168
169 // Macro is disabled for expansion until it is popped off the stack.
170 macro.disabled = true;
171
Zhenyao Mod526f982014-05-13 14:51:19 -0700172 MacroContext *context = new MacroContext;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000173 context->macro = &macro;
174 context->replacements.swap(replacements);
175 mContextStack.push_back(context);
Corentin Wallez2bd9c442016-09-20 16:39:18 -0400176 mTotalTokensInContexts += context->replacements.size();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000177 return true;
178}
179
180void MacroExpander::popMacro()
181{
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400182 ASSERT(!mContextStack.empty());
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000183
Zhenyao Mod526f982014-05-13 14:51:19 -0700184 MacroContext *context = mContextStack.back();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000185 mContextStack.pop_back();
186
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400187 ASSERT(context->empty());
188 ASSERT(context->macro->disabled);
189 ASSERT(context->macro->expansionCount > 0);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000190 context->macro->disabled = false;
Corentin Wallezd2f195b2016-09-19 15:53:33 -0400191 context->macro->expansionCount--;
Corentin Wallez2bd9c442016-09-20 16:39:18 -0400192 mTotalTokensInContexts -= context->replacements.size();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000193 delete context;
194}
195
Zhenyao Mod526f982014-05-13 14:51:19 -0700196bool MacroExpander::expandMacro(const Macro &macro,
197 const Token &identifier,
198 std::vector<Token> *replacements)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000199{
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000200 replacements->clear();
Olli Etuahoe6432c82015-09-08 14:21:38 +0300201
202 // In the case of an object-like macro, the replacement list gets its location
203 // from the identifier, but in the case of a function-like macro, the replacement
204 // list gets its location from the closing parenthesis of the macro invocation.
205 // This is tested by dEQP-GLES3.functional.shaders.preprocessor.predefined_macros.*
206 SourceLocation replacementLocation = identifier.location;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000207 if (macro.type == Macro::kTypeObj)
208 {
209 replacements->assign(macro.replacements.begin(),
210 macro.replacements.end());
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000211
212 if (macro.predefined)
213 {
Zhenyao Mob5e17752014-10-22 10:57:10 -0700214 const char kLine[] = "__LINE__";
215 const char kFile[] = "__FILE__";
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000216
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400217 ASSERT(replacements->size() == 1);
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000218 Token& repl = replacements->front();
219 if (macro.name == kLine)
220 {
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400221 repl.text = ToString(identifier.location.line);
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000222 }
223 else if (macro.name == kFile)
224 {
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400225 repl.text = ToString(identifier.location.file);
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000226 }
227 }
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000228 }
229 else
230 {
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400231 ASSERT(macro.type == Macro::kTypeFunc);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000232 std::vector<MacroArg> args;
233 args.reserve(macro.parameters.size());
Olli Etuahoe6432c82015-09-08 14:21:38 +0300234 if (!collectMacroArgs(macro, identifier, &args, &replacementLocation))
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000235 return false;
236
237 replaceMacroParams(macro, args, replacements);
238 }
239
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +0000240 for (std::size_t i = 0; i < replacements->size(); ++i)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000241 {
242 Token& repl = replacements->at(i);
243 if (i == 0)
244 {
245 // The first token in the replacement list inherits the padding
246 // properties of the identifier token.
247 repl.setAtStartOfLine(identifier.atStartOfLine());
248 repl.setHasLeadingSpace(identifier.hasLeadingSpace());
249 }
Olli Etuahoe6432c82015-09-08 14:21:38 +0300250 repl.location = replacementLocation;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000251 }
252 return true;
253}
254
Zhenyao Mod526f982014-05-13 14:51:19 -0700255bool MacroExpander::collectMacroArgs(const Macro &macro,
256 const Token &identifier,
Olli Etuahoe6432c82015-09-08 14:21:38 +0300257 std::vector<MacroArg> *args,
258 SourceLocation *closingParenthesisLocation)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000259{
260 Token token;
261 getToken(&token);
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400262 ASSERT(token.type == '(');
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000263
264 args->push_back(MacroArg());
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400265
266 int openParens = 1;
267 while (openParens != 0)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000268 {
269 getToken(&token);
270
271 if (token.type == Token::LAST)
272 {
Shannon Woods7f2d7942013-11-19 15:07:58 -0500273 mDiagnostics->report(Diagnostics::PP_MACRO_UNTERMINATED_INVOCATION,
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +0000274 identifier.location, identifier.text);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000275 // Do not lose EOF token.
276 ungetToken(token);
277 return false;
278 }
279
280 bool isArg = false; // True if token is part of the current argument.
281 switch (token.type)
282 {
283 case '(':
284 ++openParens;
285 isArg = true;
286 break;
287 case ')':
288 --openParens;
289 isArg = openParens != 0;
Olli Etuahoe6432c82015-09-08 14:21:38 +0300290 *closingParenthesisLocation = token.location;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000291 break;
292 case ',':
293 // The individual arguments are separated by comma tokens, but
294 // the comma tokens between matching inner parentheses do not
295 // seperate arguments.
Zhenyao Mod526f982014-05-13 14:51:19 -0700296 if (openParens == 1)
297 args->push_back(MacroArg());
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000298 isArg = openParens != 1;
299 break;
300 default:
301 isArg = true;
302 break;
303 }
304 if (isArg)
305 {
Zhenyao Mod526f982014-05-13 14:51:19 -0700306 MacroArg &arg = args->back();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000307 // Initial whitespace is not part of the argument.
Zhenyao Mod526f982014-05-13 14:51:19 -0700308 if (arg.empty())
309 token.setHasLeadingSpace(false);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000310 arg.push_back(token);
311 }
312 }
313
Zhenyao Mod526f982014-05-13 14:51:19 -0700314 const Macro::Parameters &params = macro.parameters;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000315 // If there is only one empty argument, it is equivalent to no argument.
316 if (params.empty() && (args->size() == 1) && args->front().empty())
317 {
318 args->clear();
319 }
320 // Validate the number of arguments.
321 if (args->size() != params.size())
322 {
323 Diagnostics::ID id = args->size() < macro.parameters.size() ?
Shannon Woods7f2d7942013-11-19 15:07:58 -0500324 Diagnostics::PP_MACRO_TOO_FEW_ARGS :
325 Diagnostics::PP_MACRO_TOO_MANY_ARGS;
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +0000326 mDiagnostics->report(id, identifier.location, identifier.text);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000327 return false;
328 }
329
330 // Pre-expand each argument before substitution.
331 // This step expands each argument individually before they are
332 // inserted into the macro body.
Corentin Wallez2bd9c442016-09-20 16:39:18 -0400333 size_t numTokens = 0;
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400334 for (auto &arg : *args)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000335 {
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000336 TokenLexer lexer(&arg);
Olli Etuaho1b2f1622016-03-04 15:06:51 +0200337 MacroExpander expander(&lexer, mMacroSet, mDiagnostics);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000338
339 arg.clear();
340 expander.lex(&token);
341 while (token.type != Token::LAST)
342 {
343 arg.push_back(token);
344 expander.lex(&token);
Corentin Wallez2bd9c442016-09-20 16:39:18 -0400345 numTokens++;
346 if (numTokens + mTotalTokensInContexts > kMaxContextTokens)
347 {
348 mDiagnostics->report(Diagnostics::PP_OUT_OF_MEMORY, token.location, token.text);
349 return false;
350 }
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000351 }
352 }
353 return true;
354}
355
Zhenyao Mod526f982014-05-13 14:51:19 -0700356void MacroExpander::replaceMacroParams(const Macro &macro,
357 const std::vector<MacroArg> &args,
358 std::vector<Token> *replacements)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000359{
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +0000360 for (std::size_t i = 0; i < macro.replacements.size(); ++i)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000361 {
Corentin Wallez2bd9c442016-09-20 16:39:18 -0400362 if (!replacements->empty() &&
363 replacements->size() + mTotalTokensInContexts > kMaxContextTokens)
364 {
365 const Token &token = replacements->back();
366 mDiagnostics->report(Diagnostics::PP_OUT_OF_MEMORY, token.location, token.text);
367 return;
368 }
369
Zhenyao Mod526f982014-05-13 14:51:19 -0700370 const Token &repl = macro.replacements[i];
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000371 if (repl.type != Token::IDENTIFIER)
372 {
373 replacements->push_back(repl);
374 continue;
375 }
376
377 // TODO(alokp): Optimize this.
378 // There is no need to search for macro params every time.
379 // The param index can be cached with the replacement token.
380 Macro::Parameters::const_iterator iter = std::find(
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +0000381 macro.parameters.begin(), macro.parameters.end(), repl.text);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000382 if (iter == macro.parameters.end())
383 {
384 replacements->push_back(repl);
385 continue;
386 }
387
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +0000388 std::size_t iArg = std::distance(macro.parameters.begin(), iter);
Zhenyao Mod526f982014-05-13 14:51:19 -0700389 const MacroArg &arg = args[iArg];
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000390 if (arg.empty())
391 {
392 continue;
393 }
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +0000394 std::size_t iRepl = replacements->size();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000395 replacements->insert(replacements->end(), arg.begin(), arg.end());
396 // The replacement token inherits padding properties from
397 // macro replacement token.
398 replacements->at(iRepl).setHasLeadingSpace(repl.hasLeadingSpace());
399 }
alokp@chromium.org04d7d222012-05-16 19:24:07 +0000400}
401
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400402MacroExpander::MacroContext::MacroContext() : macro(0), index(0)
403{
404}
405
406bool MacroExpander::MacroContext::empty() const
407{
408 return index == replacements.size();
409}
410
411const Token &MacroExpander::MacroContext::get()
412{
413 return replacements[index++];
414}
415
416void MacroExpander::MacroContext::unget()
417{
418 ASSERT(index > 0);
419 --index;
420}
421
alokp@chromium.org04d7d222012-05-16 19:24:07 +0000422} // namespace pp
423