diff options
| author | T. Foley <tfoleyNV@users.noreply.github.com> | 2021-06-06 08:59:28 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-06-06 11:59:28 -0400 |
| commit | 58d7af37f45f88130641188b1e39b00569acd575 (patch) | |
| tree | 05c8142fb0d5ae5b9e760ffc025b3c9bc54877a0 /source/slang/slang-preprocessor.cpp | |
| parent | 1617c2d4d7145435f34619d8d6706c5845b898c0 (diff) | |
Fix a bug in preprocessor "busy" logic (#1875)
* Fix a bug in preprocessor "busy" logic
This bug manifested in both incorrect preprocessor output for certain complicated cases, and also (more importantly) a use-after-free issue.
One of the "clever" design choices in the Slang preprocessor implementation is that the set of "busy" macros during expansion is implicitly defined by a linked list of those invocations that are actively being read from as part of the input stack. This logic works very well for checking whether a macro name is busy before triggering expansion, and for computing what macros should be considered busy during expansion of an object-like macro.
The problem case here was with function-like macros where the preprocessor was re-using the same list of busy macros that had been fetched when reading the macro name, but doing so *after* all the macro argument tokens had been read. Because additional tokens had been read from the input stream stack, there was no guarantee that the invocations that had been active before were still live.
The new logic computes the set of busy macros fresh before starting expansion of a function-like macro invocation. A test is included to ensure that the case that showed the use-after-free bug has been fixed.
In addition, the new logic is careful to compute the busy macros only based on where subsequent tokens will come from and not based on any macros that might have contributed to the argument tokens themselves. A test case has been added that relies on getting this detail correct.
* Update slang-preprocessor.cpp
Remove a test typo.
Co-authored-by: jsmall-nvidia <jsmall@nvidia.com>
Diffstat (limited to 'source/slang/slang-preprocessor.cpp')
| -rw-r--r-- | source/slang/slang-preprocessor.cpp | 62 |
1 files changed, 50 insertions, 12 deletions
diff --git a/source/slang/slang-preprocessor.cpp b/source/slang/slang-preprocessor.cpp index d54b35d0b..73d9f7e50 100644 --- a/source/slang/slang-preprocessor.cpp +++ b/source/slang/slang-preprocessor.cpp @@ -451,6 +451,8 @@ struct InputStreamStack } /// Get the input stream that the next token would come from + /// + /// If the input stack is at its end, this will just be the top-most stream. InputStream* getNextStream() { SLANG_ASSERT(m_top); @@ -673,14 +675,13 @@ struct MacroInvocation : InputStream MacroInvocation( Preprocessor* preprocessor, MacroDefinition* macro, - MacroInvocation* nextBusyMacroInvocation, SourceLoc macroInvocationLoc, SourceLoc initiatingMacroInvocationLoc); /// Prime the input stream /// /// This operation *must* be called before the first `readToken()` or `peekToken()` - void prime(); + void prime(MacroInvocation* nextBusyMacroInvocation); // The `readToken()` and `peekToken()` operations for a macro invocation // will be implemented by using one token of lookahead, which makes the @@ -729,7 +730,7 @@ private: List<Arg> m_args; /// Additional macros that should be considered "busy" during this expansion - MacroInvocation* m_nextBusyMacroInvocation; + MacroInvocation* m_nextBusyMacroInvocation = nullptr; /// Locatin of the macro invocation that led to this expansion SourceLoc m_macroInvocationLoc; @@ -1088,20 +1089,20 @@ bool MacroInvocation::isBusy(MacroDefinition* macro, MacroInvocation* duringMacr MacroInvocation::MacroInvocation( Preprocessor* preprocessor, MacroDefinition* macro, - MacroInvocation* nextBusyMacroInvocation, SourceLoc macroInvocationLoc, SourceLoc initiatingMacroInvocationLoc) : Super(preprocessor) { m_macro = macro; - m_nextBusyMacroInvocation = nextBusyMacroInvocation; m_firstBusyMacroInvocation = this; m_macroInvocationLoc = macroInvocationLoc; m_initiatingMacroInvocationLoc = initiatingMacroInvocationLoc; } -void MacroInvocation::prime() +void MacroInvocation::prime(MacroInvocation* nextBusyMacroInvocation) { + m_nextBusyMacroInvocation = nextBusyMacroInvocation; + _initCurrentOpStream(); m_lookaheadToken = _readTokenImpl(); } @@ -1388,8 +1389,12 @@ void ExpansionInputStream::_maybeBeginMacroInvocation() // prime its input stream, and then push it onto our stack of active // macro invocations. // - MacroInvocation* invocation = new MacroInvocation(preprocessor, macro, busyMacros, token.loc, m_initiatingMacroInvocationLoc); - invocation->prime(); + // Note: the macros that should be considered "busy" during the invocation + // are all those that were busy at the time we read the name of the macro + // to be expanded. + // + MacroInvocation* invocation = new MacroInvocation(preprocessor, macro, token.loc, m_initiatingMacroInvocationLoc); + invocation->prime(busyMacros); _pushMacroInvocation(invocation); } break; @@ -1441,7 +1446,7 @@ void ExpansionInputStream::_maybeBeginMacroInvocation() // If we saw an opening `(`, then we know we are starting some kind of // macro invocation, although we don't yet know if it is well-formed. // - MacroInvocation* invocation = new MacroInvocation(preprocessor, macro, busyMacros, token.loc, m_initiatingMacroInvocationLoc); + MacroInvocation* invocation = new MacroInvocation(preprocessor, macro, token.loc, m_initiatingMacroInvocationLoc); // We start by consuming the opening `(` that we checked for above. // @@ -1483,7 +1488,36 @@ void ExpansionInputStream::_maybeBeginMacroInvocation() // Now that the arguments have been parsed and validated, // we are ready to proceed with expansion of the macro invocation. // - invocation->prime(); + // The main subtle thing we have to figure out is which macros should be considered "busy" + // during the expansion of this function-like macro invocation. + // + // In the case of an object-like macro invocation: + // + // 1 + M + 2 + // ^ + // + // Input will have just read in the `M` token that names the macro + // so we needed to consider whatever macro invocations had been in + // flight (even if they were at their end) when checking if `M` + // was busy. + // + // In contrast, for a function-like macro invocation: + // + // 1 + F ( A, B, C ) + 2 + // ^ + // + // We will have just read in the `)` from the argument list, but + // we don't actually need/want to worry about any macro invocation + // that might have yielded the `)` token, since expanding that macro + // again would *not* be able to lead to a recursive case. + // + // Instead, we really only care about the active stream that the + // next token would be read from. + // + auto nextStream = m_inputStreams.getNextStream(); + auto busyMacrosForFunctionLikeInvocation = nextStream->getFirstBusyMacroInvocation(); + + invocation->prime(busyMacrosForFunctionLikeInvocation); _pushMacroInvocation(invocation); } break; @@ -3708,8 +3742,12 @@ Result findMacroValue( if(macro->flavor != MacroDefinition::Flavor::ObjectLike) return SLANG_FAIL; - MacroInvocation* invocation = new MacroInvocation(preprocessor, macro, nullptr, SourceLoc(), SourceLoc()); - invocation->prime(); + MacroInvocation* invocation = new MacroInvocation(preprocessor, macro, SourceLoc(), SourceLoc()); + + // Note: Since we are only expanding the one macro, we should not treat any + // other macros as "busy" at the start of expansion. + // + invocation->prime(/*nextBusyMacroInvocation:*/ nullptr); String value; for(bool first = true;;first = false) |
