tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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> &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 
     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 &macro,
    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 &macro,
    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 &params = 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 &macro,
    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