blob: f38b39635154d5650a3c6dc0a8ad27dbcb1ec2c6 [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 }
90 if ((macro.type == Macro::kTypeFunc) && !isNextTokenLeftParen())
91 {
92 // If the token immediately after the macro name is not a '(',
93 // this macro should not be expanded.
94 break;
95 }
96
97 pushMacro(macro, *token);
98 }
99}
100
Zhenyao Mod526f982014-05-13 14:51:19 -0700101void MacroExpander::getToken(Token *token)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000102{
103 if (mReserveToken.get())
104 {
105 *token = *mReserveToken;
106 mReserveToken.reset();
107 return;
108 }
109
110 // First pop all empty macro contexts.
111 while (!mContextStack.empty() && mContextStack.back()->empty())
112 {
113 popMacro();
114 }
115
116 if (!mContextStack.empty())
117 {
118 *token = mContextStack.back()->get();
119 }
120 else
121 {
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400122 ASSERT(mTotalTokensInContexts == 0);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000123 mLexer->lex(token);
124 }
125}
126
Zhenyao Mod526f982014-05-13 14:51:19 -0700127void MacroExpander::ungetToken(const Token &token)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000128{
129 if (!mContextStack.empty())
130 {
Zhenyao Mod526f982014-05-13 14:51:19 -0700131 MacroContext *context = mContextStack.back();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000132 context->unget();
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400133 ASSERT(context->replacements[context->index] == token);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000134 }
135 else
136 {
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400137 ASSERT(!mReserveToken.get());
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000138 mReserveToken.reset(new Token(token));
139 }
140}
141
142bool MacroExpander::isNextTokenLeftParen()
143{
144 Token token;
145 getToken(&token);
146
147 bool lparen = token.type == '(';
148 ungetToken(token);
149
150 return lparen;
151}
152
Zhenyao Mod526f982014-05-13 14:51:19 -0700153bool MacroExpander::pushMacro(const Macro &macro, const Token &identifier)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000154{
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400155 ASSERT(!macro.disabled);
156 ASSERT(!identifier.expansionDisabled());
157 ASSERT(identifier.type == Token::IDENTIFIER);
158 ASSERT(identifier.text == macro.name);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000159
Corentin Wallezd2f195b2016-09-19 15:53:33 -0400160 macro.expansionCount++;
161
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000162 std::vector<Token> replacements;
163 if (!expandMacro(macro, identifier, &replacements))
164 return false;
165
166 // Macro is disabled for expansion until it is popped off the stack.
167 macro.disabled = true;
168
Zhenyao Mod526f982014-05-13 14:51:19 -0700169 MacroContext *context = new MacroContext;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000170 context->macro = &macro;
171 context->replacements.swap(replacements);
172 mContextStack.push_back(context);
Corentin Wallez2bd9c442016-09-20 16:39:18 -0400173 mTotalTokensInContexts += context->replacements.size();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000174 return true;
175}
176
177void MacroExpander::popMacro()
178{
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400179 ASSERT(!mContextStack.empty());
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000180
Zhenyao Mod526f982014-05-13 14:51:19 -0700181 MacroContext *context = mContextStack.back();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000182 mContextStack.pop_back();
183
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400184 ASSERT(context->empty());
185 ASSERT(context->macro->disabled);
186 ASSERT(context->macro->expansionCount > 0);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000187 context->macro->disabled = false;
Corentin Wallezd2f195b2016-09-19 15:53:33 -0400188 context->macro->expansionCount--;
Corentin Wallez2bd9c442016-09-20 16:39:18 -0400189 mTotalTokensInContexts -= context->replacements.size();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000190 delete context;
191}
192
Zhenyao Mod526f982014-05-13 14:51:19 -0700193bool MacroExpander::expandMacro(const Macro &macro,
194 const Token &identifier,
195 std::vector<Token> *replacements)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000196{
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000197 replacements->clear();
Olli Etuahoe6432c82015-09-08 14:21:38 +0300198
199 // In the case of an object-like macro, the replacement list gets its location
200 // from the identifier, but in the case of a function-like macro, the replacement
201 // list gets its location from the closing parenthesis of the macro invocation.
202 // This is tested by dEQP-GLES3.functional.shaders.preprocessor.predefined_macros.*
203 SourceLocation replacementLocation = identifier.location;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000204 if (macro.type == Macro::kTypeObj)
205 {
206 replacements->assign(macro.replacements.begin(),
207 macro.replacements.end());
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000208
209 if (macro.predefined)
210 {
Zhenyao Mob5e17752014-10-22 10:57:10 -0700211 const char kLine[] = "__LINE__";
212 const char kFile[] = "__FILE__";
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000213
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400214 ASSERT(replacements->size() == 1);
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000215 Token& repl = replacements->front();
216 if (macro.name == kLine)
217 {
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400218 repl.text = ToString(identifier.location.line);
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000219 }
220 else if (macro.name == kFile)
221 {
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400222 repl.text = ToString(identifier.location.file);
alokp@chromium.orgf3cdb462012-06-19 18:39:48 +0000223 }
224 }
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000225 }
226 else
227 {
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400228 ASSERT(macro.type == Macro::kTypeFunc);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000229 std::vector<MacroArg> args;
230 args.reserve(macro.parameters.size());
Olli Etuahoe6432c82015-09-08 14:21:38 +0300231 if (!collectMacroArgs(macro, identifier, &args, &replacementLocation))
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000232 return false;
233
234 replaceMacroParams(macro, args, replacements);
235 }
236
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +0000237 for (std::size_t i = 0; i < replacements->size(); ++i)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000238 {
239 Token& repl = replacements->at(i);
240 if (i == 0)
241 {
242 // The first token in the replacement list inherits the padding
243 // properties of the identifier token.
244 repl.setAtStartOfLine(identifier.atStartOfLine());
245 repl.setHasLeadingSpace(identifier.hasLeadingSpace());
246 }
Olli Etuahoe6432c82015-09-08 14:21:38 +0300247 repl.location = replacementLocation;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000248 }
249 return true;
250}
251
Zhenyao Mod526f982014-05-13 14:51:19 -0700252bool MacroExpander::collectMacroArgs(const Macro &macro,
253 const Token &identifier,
Olli Etuahoe6432c82015-09-08 14:21:38 +0300254 std::vector<MacroArg> *args,
255 SourceLocation *closingParenthesisLocation)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000256{
257 Token token;
258 getToken(&token);
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400259 ASSERT(token.type == '(');
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000260
261 args->push_back(MacroArg());
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400262
263 int openParens = 1;
264 while (openParens != 0)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000265 {
266 getToken(&token);
267
268 if (token.type == Token::LAST)
269 {
Shannon Woods7f2d7942013-11-19 15:07:58 -0500270 mDiagnostics->report(Diagnostics::PP_MACRO_UNTERMINATED_INVOCATION,
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +0000271 identifier.location, identifier.text);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000272 // Do not lose EOF token.
273 ungetToken(token);
274 return false;
275 }
276
277 bool isArg = false; // True if token is part of the current argument.
278 switch (token.type)
279 {
280 case '(':
281 ++openParens;
282 isArg = true;
283 break;
284 case ')':
285 --openParens;
286 isArg = openParens != 0;
Olli Etuahoe6432c82015-09-08 14:21:38 +0300287 *closingParenthesisLocation = token.location;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000288 break;
289 case ',':
290 // The individual arguments are separated by comma tokens, but
291 // the comma tokens between matching inner parentheses do not
292 // seperate arguments.
Zhenyao Mod526f982014-05-13 14:51:19 -0700293 if (openParens == 1)
294 args->push_back(MacroArg());
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000295 isArg = openParens != 1;
296 break;
297 default:
298 isArg = true;
299 break;
300 }
301 if (isArg)
302 {
Zhenyao Mod526f982014-05-13 14:51:19 -0700303 MacroArg &arg = args->back();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000304 // Initial whitespace is not part of the argument.
Zhenyao Mod526f982014-05-13 14:51:19 -0700305 if (arg.empty())
306 token.setHasLeadingSpace(false);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000307 arg.push_back(token);
308 }
309 }
310
Zhenyao Mod526f982014-05-13 14:51:19 -0700311 const Macro::Parameters &params = macro.parameters;
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000312 // If there is only one empty argument, it is equivalent to no argument.
313 if (params.empty() && (args->size() == 1) && args->front().empty())
314 {
315 args->clear();
316 }
317 // Validate the number of arguments.
318 if (args->size() != params.size())
319 {
320 Diagnostics::ID id = args->size() < macro.parameters.size() ?
Shannon Woods7f2d7942013-11-19 15:07:58 -0500321 Diagnostics::PP_MACRO_TOO_FEW_ARGS :
322 Diagnostics::PP_MACRO_TOO_MANY_ARGS;
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +0000323 mDiagnostics->report(id, identifier.location, identifier.text);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000324 return false;
325 }
326
327 // Pre-expand each argument before substitution.
328 // This step expands each argument individually before they are
329 // inserted into the macro body.
Corentin Wallez2bd9c442016-09-20 16:39:18 -0400330 size_t numTokens = 0;
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400331 for (auto &arg : *args)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000332 {
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000333 TokenLexer lexer(&arg);
Olli Etuaho1b2f1622016-03-04 15:06:51 +0200334 MacroExpander expander(&lexer, mMacroSet, mDiagnostics);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000335
336 arg.clear();
337 expander.lex(&token);
338 while (token.type != Token::LAST)
339 {
340 arg.push_back(token);
341 expander.lex(&token);
Corentin Wallez2bd9c442016-09-20 16:39:18 -0400342 numTokens++;
343 if (numTokens + mTotalTokensInContexts > kMaxContextTokens)
344 {
345 mDiagnostics->report(Diagnostics::PP_OUT_OF_MEMORY, token.location, token.text);
346 return false;
347 }
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000348 }
349 }
350 return true;
351}
352
Zhenyao Mod526f982014-05-13 14:51:19 -0700353void MacroExpander::replaceMacroParams(const Macro &macro,
354 const std::vector<MacroArg> &args,
355 std::vector<Token> *replacements)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000356{
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +0000357 for (std::size_t i = 0; i < macro.replacements.size(); ++i)
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000358 {
Corentin Wallez2bd9c442016-09-20 16:39:18 -0400359 if (!replacements->empty() &&
360 replacements->size() + mTotalTokensInContexts > kMaxContextTokens)
361 {
362 const Token &token = replacements->back();
363 mDiagnostics->report(Diagnostics::PP_OUT_OF_MEMORY, token.location, token.text);
364 return;
365 }
366
Zhenyao Mod526f982014-05-13 14:51:19 -0700367 const Token &repl = macro.replacements[i];
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000368 if (repl.type != Token::IDENTIFIER)
369 {
370 replacements->push_back(repl);
371 continue;
372 }
373
374 // TODO(alokp): Optimize this.
375 // There is no need to search for macro params every time.
376 // The param index can be cached with the replacement token.
377 Macro::Parameters::const_iterator iter = std::find(
alokp@chromium.org5b6a68e2012-06-28 20:29:13 +0000378 macro.parameters.begin(), macro.parameters.end(), repl.text);
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000379 if (iter == macro.parameters.end())
380 {
381 replacements->push_back(repl);
382 continue;
383 }
384
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +0000385 std::size_t iArg = std::distance(macro.parameters.begin(), iter);
Zhenyao Mod526f982014-05-13 14:51:19 -0700386 const MacroArg &arg = args[iArg];
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000387 if (arg.empty())
388 {
389 continue;
390 }
daniel@transgaming.coma16a55f2012-12-20 20:51:54 +0000391 std::size_t iRepl = replacements->size();
alokp@chromium.org7fc38dd2012-06-14 18:23:23 +0000392 replacements->insert(replacements->end(), arg.begin(), arg.end());
393 // The replacement token inherits padding properties from
394 // macro replacement token.
395 replacements->at(iRepl).setHasLeadingSpace(repl.hasLeadingSpace());
396 }
alokp@chromium.org04d7d222012-05-16 19:24:07 +0000397}
398
Corentin Wallez054f7ed2016-09-20 17:15:59 -0400399MacroExpander::MacroContext::MacroContext() : macro(0), index(0)
400{
401}
402
403bool MacroExpander::MacroContext::empty() const
404{
405 return index == replacements.size();
406}
407
408const Token &MacroExpander::MacroContext::get()
409{
410 return replacements[index++];
411}
412
413void MacroExpander::MacroContext::unget()
414{
415 ASSERT(index > 0);
416 --index;
417}
418
alokp@chromium.org04d7d222012-05-16 19:24:07 +0000419} // namespace pp
420