Skip to content

Commit 6aad38a

Browse files
author
Tim Foley
authored
Fix a preprocessor bug affecting X-macros (shader-slang#1436)
* Fix a preprocessor bug affecting X-macros Fixes shader-slang#1435 This bug exhibited as nondeterministic output from the preprocessor in release builds, but using a debug build it was narrowed down to a use-after-free issue. The core problem is subtle, but relates to how we set up the linked list that represents the "busy" status of macros in a particular expansion environment. Consider this scenario: ```hlsl X(A) ``` The flow we expect from the preprocessor is something like: 1. Read the `X` token in `X(A)` and recognize the start of a function-like macro invocation. Create an expansion environment for `X`, with the global environment as a parent, read in the arguments (just `A`), and push that expansion onto the stack. 2. Read the `M` token that starts the expansion of `M`, and recognize it as an invocation of the object-like macro representing the argument `M`. Create an expansion environment for the definition of `M` (which is just `A`), and push it onto the stack. 3. Read the token `A` from the expansion for the argument `M`, and recognize it as an invocation of the function-like macro `A`. Create an expansion environemnt for `A`, with the current environment as its parent, read in the arguments (just `0`), and push that expansion onto the stack. 4. Read the token `y` from the expansion for `A`, and recognize it as an invocation of the object-like macro representing the argument `y`. Create an expansion environment for the definition of `y` (which is just `0`) and push it onto the stack. 5. Read `0`. 6. Read a bunch of end-of-file tokens that cause all of these expansions to be popped. That all looks fine as written, but the gotcha is that the input stream for the expansion in step (2) is only a single token (`A`), which means that during step (3) the current input stream at the time we *create* the macro expansion for `A` is at the end of its input, and by the time we've read in the macro arguments that expansion will have been popped. The problem, then, is that the logic for setting up the stack of "busy" macros was being performed at the beginning of the expansion (the part referred to as "create an expansion" above), when it should only have been set up as part of pushing the xpansion onto the stack (since at that point we have a guarantee that the parent expansion cannot be popped until the child expansion has been). The fix here is thus pretty simple: we already have distinct operations for `initializeMacroExpansion()` and `pushMacroExpansion()`, and I simply moved the logic for setting up the "busy" state from the former to the latter. * fixup: typo
1 parent 2503280 commit 6aad38a

File tree

2 files changed

+31
-11
lines changed

2 files changed

+31
-11
lines changed

source/slang/slang-preprocessor.cpp

+18-11
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,7 @@ static bool _isMacroBusy(PreprocessorMacro* macro, PreprocessorEnvironment* env)
586586
// Reading Tokens With Expansion
587587
//
588588

589-
static void InitializeMacroExpansion(
589+
static void initializeMacroExpansion(
590590
Preprocessor* preprocessor,
591591
MacroExpansion* expansion,
592592
PreprocessorMacro* macro)
@@ -627,6 +627,16 @@ static void InitializeMacroExpansion(
627627
// macro, that environment will be the environment where
628628
// the function-like macro was *invoked*, which might be
629629
// in the context of another macro expansion.
630+
}
631+
632+
static void pushMacroExpansion(
633+
Preprocessor* preprocessor,
634+
MacroExpansion* expansion)
635+
{
636+
// Before pushing a macro as an input stream,
637+
// we need to set the appropraite "busy" state
638+
// that will be used during expansions of that
639+
// macro's definition.
630640

631641
// A macro is always busy in its own expansion environment,
632642
// to prevent recursive expansion. Here we construct a
@@ -639,6 +649,7 @@ static void InitializeMacroExpansion(
639649
// expansion. We could try to avoid the extra step at
640650
// the cost of a bit more code complexity here.
641651
//
652+
auto macro = expansion->macro;
642653
expansion->busy.macro = macro;
643654
expansion->expansionEnvironment.busyMacros = &expansion->busy;
644655

@@ -653,23 +664,19 @@ static void InitializeMacroExpansion(
653664
// This happens to be what is stored in the parent
654665
// environment.
655666
//
667+
auto parentEnvironment = expansion->expansionEnvironment.parent;
656668
expansion->busy.next = parentEnvironment->busyMacros;
657669
}
658670
else
659671
{
660-
// For the other cases (function-like and objet-like
672+
// For the other cases (function-like and object-like
661673
// macros), the busy list should include anything
662674
// that was already busy in the environment that
663675
// is beginning to expand a macro.
664676
//
665677
expansion->busy.next = preprocessor->inputStream->environment->busyMacros;
666678
}
667-
}
668679

669-
static void PushMacroExpansion(
670-
Preprocessor* preprocessor,
671-
MacroExpansion* expansion)
672-
{
673680
PushInputStream(preprocessor, expansion);
674681
}
675682

@@ -930,7 +937,7 @@ static void MaybeBeginMacroExpansion(
930937
}
931938

932939
MacroExpansion* expansion = new MacroExpansion();
933-
InitializeMacroExpansion(preprocessor, expansion, macro);
940+
initializeMacroExpansion(preprocessor, expansion, macro);
934941

935942
// Consume the opening `(`
936943
Token leftParen = AdvanceRawToken(preprocessor);
@@ -963,7 +970,7 @@ static void MaybeBeginMacroExpansion(
963970
// Now that the arguments have been parsed and validated,
964971
// we are ready to proceed with expansion of the macro body.
965972
//
966-
PushMacroExpansion(preprocessor, expansion);
973+
pushMacroExpansion(preprocessor, expansion);
967974
}
968975
else
969976
{
@@ -972,8 +979,8 @@ static void MaybeBeginMacroExpansion(
972979

973980
// Object-like macros are the easy case.
974981
MacroExpansion* expansion = new MacroExpansion();
975-
InitializeMacroExpansion(preprocessor, expansion, macro);
976-
PushMacroExpansion(preprocessor, expansion);
982+
initializeMacroExpansion(preprocessor, expansion, macro);
983+
pushMacroExpansion(preprocessor, expansion);
977984
}
978985
}
979986
}

tests/preprocessor/x-macro.slang

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// x-macro.slang
2+
//TEST:SIMPLE:
3+
4+
// Test the case of an "X macro" that takes another macro as a parameter
5+
6+
#define X(M) M(0) M(1) M(2) M(3) M(4) M(5) M(6) M(7)
7+
8+
#define A(x) + x + x + x
9+
10+
int sum()
11+
{
12+
return X(A);
13+
}

0 commit comments

Comments
 (0)