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