summaryrefslogtreecommitdiffstats
path: root/source/slang/slang-preprocessor.cpp
diff options
context:
space:
mode:
authorRonan <ro.cailleau@gmail.com>2025-04-25 00:48:37 +0200
committerGitHub <noreply@github.com>2025-04-24 15:48:37 -0700
commit5f632cd204b7a85f3a97b6c316c5a34f9fc8193e (patch)
tree7dc74fbe8c80870a4c485bbaa5765ff379914779 /source/slang/slang-preprocessor.cpp
parentc7ecf3039d2cc8680f1ea5f4bee2d13521ae34f7 (diff)
Implemented #pragma warning (#6748)
* Implemented #pragma warning Based on https://learn.microsoft.com/en-us/cpp/preprocessor/warning?view=msvc-170 * Make #pragma warning work with #includes. - SourceLoc are not sorted by inclusion order. - Construct a mapping from SourceLoc to "absolute locations" that are sorted by inclusion order (roughly represents a location in a raw file with all #include resolved). - The absolute location can be used in the pragma warning timeline * Added preprocessor #pragma warning tests. - Fixed #pragma warning (push / pop) SourceLoc - Fixed unused directiveLoc in #pragma warning parsing * #pragma warning: Added some comments and fixed some typos * Cleaned #pragma warning preprocessor implementation. --------- Co-authored-by: Yong He <yonghe@outlook.com>
Diffstat (limited to 'source/slang/slang-preprocessor.cpp')
-rw-r--r--source/slang/slang-preprocessor.cpp519
1 files changed, 515 insertions, 4 deletions
diff --git a/source/slang/slang-preprocessor.cpp b/source/slang/slang-preprocessor.cpp
index 5a4e26f37..3f120d5eb 100644
--- a/source/slang/slang-preprocessor.cpp
+++ b/source/slang/slang-preprocessor.cpp
@@ -200,6 +200,8 @@ struct InputStream
MacroInvocation* getFirstBusyMacroInvocation() { return m_firstBusyMacroInvocation; }
+ virtual SourceLoc findNextLineEndImpl(SourceLoc from, UInt& lineCount) const = 0;
+
protected:
/// The preprocessor that this input stream is being used by
Preprocessor* m_preprocessor = nullptr;
@@ -235,6 +237,14 @@ struct PretokenizedInputStream : InputStream
virtual Token peekToken() SLANG_OVERRIDE { return m_tokenReader.peekToken(); }
+ virtual SourceLoc findNextLineEndImpl(SourceLoc from, UInt& lineCount) const SLANG_OVERRIDE
+ {
+ // Not implemented
+ SLANG_UNUSED(from)
+ SLANG_UNUSED(lineCount)
+ return {};
+ }
+
protected:
/// Initialize an input stream, and assocaite with a specific `preprocessor`
PretokenizedInputStream(Preprocessor* preprocessor)
@@ -467,6 +477,22 @@ struct InputStreamStack
}
}
+ SourceLoc findNextLineEnd(SourceLoc from, UInt lineCount = 1) const
+ {
+ auto top = m_top;
+ SourceLoc res = from;
+ res = top->findNextLineEndImpl(res, lineCount);
+ if (lineCount > 0)
+ {
+ // We did not consume all the lines, but arrived at the end of the current Stream
+ // For now, return none.
+ // We could pop the inputStream stack and continue looking,
+ // but it is an unlikely edge case.
+ res = {};
+ }
+ return res;
+ }
+
private:
/// The top of the stack of input streams
InputStream* m_top = nullptr;
@@ -509,6 +535,11 @@ struct LexerInputStream : InputStream
Token peekToken() SLANG_OVERRIDE { return m_lookaheadToken; }
+ virtual SourceLoc findNextLineEndImpl(SourceLoc from, UInt& lineCount) const SLANG_OVERRIDE
+ {
+ return m_lexer.findNextLineEnd(from, lineCount);
+ }
+
private:
/// Read a token from the lexer, bypassing lookahead
Token _readTokenImpl()
@@ -701,6 +732,14 @@ struct MacroInvocation : InputStream
MacroDefinition* getMacroDefinition() { return m_macro; }
+ virtual SourceLoc findNextLineEndImpl(SourceLoc from, UInt& lineCount) const SLANG_OVERRIDE
+ {
+ // There are no actual lines inside of a macro invocation
+ SLANG_UNUSED(from)
+ SLANG_UNUSED(lineCount)
+ return {};
+ }
+
private:
// Macro invocations are created as part of applying macro expansion
// to a stream, so the `ExpansionInputStream` type takes responsibility
@@ -838,6 +877,19 @@ struct ExpansionInputStream : InputStream
m_isInExpansion = true;
}
+ virtual SourceLoc findNextLineEndImpl(SourceLoc from, UInt& lineCount) const SLANG_OVERRIDE
+ {
+ // Should not be here / not implemented
+ SLANG_UNUSED(from)
+ SLANG_UNUSED(lineCount)
+ return {};
+ }
+
+ SourceLoc findNextLineEnd(SourceLoc from, UInt lineCount = 1) const
+ {
+ return m_inputStreams.findNextLineEnd(from, lineCount);
+ }
+
private:
/// The base stream that macro expansion is being applied to
InputStream* m_base = nullptr;
@@ -950,6 +1002,261 @@ private:
ExpansionInputStream* m_expansionStream;
};
+enum class PragmaWarningSpecifier
+{
+ Default,
+ Disable,
+ Error,
+ Once,
+ Suppress,
+};
+
+struct WarningTimeline
+{
+ struct Entry
+ {
+ PragmaWarningSpecifier specifier = {};
+ SourceLoc::RawValue location = {}; // Absolute location
+ // Used for the once specifier
+ // -1 points to this, but not consumed
+ // -2 points to this, but was consumed
+ // >= 0 points to a previous once entry (sharing the payload)
+ // Used for the suppress specifier to store the original SourceLoc needed to emit a warning
+ union
+ {
+ int payload = 0;
+ SourceLoc::RawValue debugLocation; // Store the raw value for trivial copy
+ };
+
+ bool operator<(Entry const& other) const { return location < other.location; }
+ };
+ // Sorted by location
+ List<Entry> entries = {};
+
+ const Entry* findEntry(SourceLoc::RawValue location) const
+ {
+ const Entry* res = nullptr;
+ if (entries.getCount() && location >= entries.getFirst().location)
+ {
+ auto nextEntryIndex = ::std::upper_bound(
+ entries.begin(),
+ entries.end(),
+ location,
+ [](SourceLoc::RawValue const& lhs, Entry const& rhs)
+ { return lhs < rhs.location; });
+ res = nextEntryIndex - 1;
+ }
+ return res;
+ }
+
+ PragmaWarningSpecifier consumeSpecifier(SourceLoc::RawValue location)
+ {
+ PragmaWarningSpecifier res = PragmaWarningSpecifier::Default;
+ Entry* entry = const_cast<Entry*>(findEntry(location));
+ if (entry)
+ {
+ PragmaWarningSpecifier& spec = entry->specifier;
+ res = spec;
+ if (res == PragmaWarningSpecifier::Once)
+ {
+ int* payload = &entry->payload;
+ if (*payload >= 0)
+ {
+ payload = &entries[*payload].payload;
+ }
+ if (*payload == -1)
+ {
+ res = PragmaWarningSpecifier::Default;
+ --(*payload);
+ }
+ else if (*payload < -1)
+ {
+ res = PragmaWarningSpecifier::Disable;
+ }
+ }
+ }
+ return res;
+ }
+
+ const Entry* getLatestEntry() const
+ {
+ const Entry* res = nullptr;
+ if (entries.getCount())
+ {
+ res = &entries.getLast();
+ }
+ return res;
+ }
+
+ PragmaWarningSpecifier getLatestSpecifier() const
+ {
+ PragmaWarningSpecifier res = PragmaWarningSpecifier::Default;
+ if (entries.getCount())
+ {
+ res = entries.getLast().specifier;
+ }
+ return res;
+ }
+
+ void addEntry(
+ SourceLoc::RawValue location,
+ PragmaWarningSpecifier specifier,
+ const Entry* poppingFrom,
+ DiagnosticSink* sink,
+ int id,
+ SourceLoc debugLoc)
+ {
+ SourceLoc::RawValue maxKnownLocation =
+ entries.getCount() ? entries.getLast().location : SourceLoc::RawValue(0);
+ // Add on top
+ if (location > maxKnownLocation)
+ {
+ // Add a new entry only if necessary
+ if (getLatestSpecifier() != specifier || specifier == PragmaWarningSpecifier::Once)
+ {
+ Entry e;
+ e.specifier = specifier;
+ e.location = location;
+ if (specifier == PragmaWarningSpecifier::Once)
+ {
+ if (poppingFrom)
+ {
+ SLANG_ASSERT(poppingFrom->specifier == PragmaWarningSpecifier::Once);
+ if (poppingFrom->payload >= 0)
+ {
+ e.payload = poppingFrom->payload;
+ }
+ else
+ {
+ e.payload = static_cast<int>(poppingFrom - entries.begin());
+ }
+ }
+ else
+ {
+ e.payload = -1;
+ }
+ }
+ else
+ {
+ e.debugLocation = debugLoc.getRaw();
+ }
+ entries.add(e);
+ }
+ }
+ else
+ {
+ if (sink)
+ {
+ sink->diagnose(debugLoc, Diagnostics::pragmaWarningCannotInsertHere, id);
+ const Entry* prevEntry = findEntry(location);
+ if (prevEntry && prevEntry->specifier == PragmaWarningSpecifier::Suppress)
+ {
+ sink->diagnose(
+ SourceLoc::fromRaw(prevEntry->debugLocation),
+ Diagnostics::pragmaWarningPointSuppress,
+ id);
+ }
+ }
+ }
+ }
+
+ void addEntryForPragmaPop(
+ SourceLoc::RawValue location,
+ SourceLoc::RawValue pushedLocation,
+ DiagnosticSink* sink,
+ int id,
+ SourceLoc debugLoc)
+ {
+ const Entry* poppingFrom = findEntry(pushedLocation);
+ addEntry(
+ location,
+ poppingFrom ? poppingFrom->specifier : PragmaWarningSpecifier::Default,
+ poppingFrom,
+ sink,
+ id,
+ debugLoc);
+ }
+};
+
+struct WarningStateTracker : SourceWarningStateTrackerBase
+{
+ SourceManager* sourceManager = nullptr;
+ Dictionary<int, WarningTimeline> mapDiagnosticIdToTimeline = {};
+ List<SourceLoc> stack = {};
+
+ WarningStateTracker(SourceManager* sourceManager = nullptr)
+ : sourceManager(sourceManager)
+ {
+ }
+
+ SourceLoc::RawValue getAbsoluteLocation(SourceLoc loc) const
+ {
+ return sourceManager ? sourceManager->getAbsoluteLocation(loc) : loc.getRaw();
+ }
+
+ virtual Severity consumeWarningSeverity(SourceLoc location, int id, Severity severity) override
+ {
+ Severity res = severity;
+ WarningTimeline* timeline = mapDiagnosticIdToTimeline.tryGetValue(id);
+ if (!timeline)
+ return res;
+ SourceLoc::RawValue absoluteLoc = getAbsoluteLocation(location);
+ PragmaWarningSpecifier spec = timeline->consumeSpecifier(absoluteLoc);
+ if (spec == PragmaWarningSpecifier::Disable || spec == PragmaWarningSpecifier::Suppress)
+ {
+ res = Severity::Disable;
+ }
+ else if (spec == PragmaWarningSpecifier::Error)
+ {
+ res = Severity::Error;
+ }
+ return res;
+ }
+
+ void addEntry(
+ SourceLoc location,
+ SourceLoc nextLineEnd,
+ int id,
+ PragmaWarningSpecifier specifier,
+ DiagnosticSink* sink = nullptr)
+ {
+ WarningTimeline& timeline = mapDiagnosticIdToTimeline[id];
+ auto absLoc = getAbsoluteLocation(location);
+ PragmaWarningSpecifier prev = timeline.getLatestSpecifier();
+ auto lastEntry = timeline.getLatestEntry();
+ timeline.addEntry(absLoc, specifier, nullptr, sink, id, location);
+ if (specifier == PragmaWarningSpecifier::Suppress)
+ {
+ auto nextAbsLoc = getAbsoluteLocation(nextLineEnd);
+ timeline.addEntry(nextAbsLoc, prev, lastEntry, sink, id, nextLineEnd);
+ }
+ }
+
+ void addPragmaPush(SourceLoc location) { stack.add(location); }
+
+ void addPragmaPop(SourceLoc location, DiagnosticSink* sink = nullptr)
+ {
+ if (stack.getCount())
+ {
+ const SourceLoc pushed = stack.getLast();
+ stack.removeLast();
+ if (mapDiagnosticIdToTimeline.getCount())
+ {
+ const SourceLoc::RawValue absLoc = getAbsoluteLocation(location);
+ const SourceLoc::RawValue absPushed = getAbsoluteLocation(pushed);
+ for (auto& [id, timeline] : mapDiagnosticIdToTimeline)
+ {
+ timeline.addEntryForPragmaPop(absLoc, absPushed, sink, id, location);
+ }
+ }
+ }
+ else if (sink)
+ {
+ sink->diagnose(location, Diagnostics::pragmaWarningPopEmpty);
+ }
+ }
+};
+
/// State of the preprocessor
struct Preprocessor
{
@@ -981,6 +1288,8 @@ struct Preprocessor
/// stop them from being included again.
HashSet<String> pragmaOnceUniqueIdentities;
+ WarningStateTracker* warningStateTracker = nullptr;
+
/// Name pool to use when creating `Name`s from strings
NamePool* namePool = nullptr;
@@ -1002,8 +1311,10 @@ struct Preprocessor
NamePool* getNamePool() { return namePool; }
SourceManager* getSourceManager() { return sourceManager; }
+ SourceLoc::RawValue absoluteSourceLocCounter = 0;
+
/// Push a new input file onto the input stack of the preprocessor
- void pushInputFile(InputFile* inputFile);
+ void pushInputFile(InputFile* inputFile, SourceLoc location);
/// Pop the inner-most input file from the stack of input files
void popInputFile();
@@ -2409,6 +2720,15 @@ static void SkipToEndOfLine(PreprocessorDirectiveContext* context)
}
}
+static SourceLoc FindNextEndOfLine(
+ PreprocessorDirectiveContext* context,
+ SourceLoc from,
+ UInt lineCount = 1)
+{
+ auto inputStream = getInputStream(context);
+ return inputStream->findNextLineEnd(from, lineCount);
+}
+
static bool ExpectRaw(
PreprocessorDirectiveContext* context,
TokenType tokenType,
@@ -3157,8 +3477,20 @@ static SlangResult readFile(
return SLANG_OK;
}
-void Preprocessor::pushInputFile(InputFile* inputFile)
+void Preprocessor::pushInputFile(InputFile* inputFile, SourceLoc loc)
{
+ if (m_currentInputFile)
+ {
+ SourceView* sourceView = m_currentInputFile->getLexer()->m_sourceView;
+ SourceLoc::RawValue offset = SourceRange(sourceView->getLastSegment().begin, loc).getSize();
+ absoluteSourceLocCounter += offset;
+ }
+
+ {
+ SourceView* sourceView = inputFile->getLexer()->m_sourceView;
+ sourceView->setAbsoluteLocationBase(absoluteSourceLocCounter);
+ }
+
inputFile->m_parent = m_currentInputFile;
m_currentInputFile = inputFile;
}
@@ -3281,7 +3613,7 @@ static void HandleIncludeDirective(PreprocessorDirectiveContext* context)
InputFile* inputFile = new InputFile(context->m_preprocessor, sourceView);
- context->m_preprocessor->pushInputFile(inputFile);
+ context->m_preprocessor->pushInputFile(inputFile, directiveLoc);
}
static void _parseMacroOps(
@@ -3819,6 +4151,145 @@ SLANG_PRAGMA_DIRECTIVE_CALLBACK(handlePragmaOnceDirective)
context->m_preprocessor->pragmaOnceUniqueIdentities.add(issuedFromPathInfo.uniqueIdentity);
}
+SLANG_PRAGMA_DIRECTIVE_CALLBACK(handlePragmaWarningDirective)
+{
+ auto directiveLoc = GetDirectiveLoc(context);
+ SLANG_UNUSED(subDirectiveToken)
+ SLANG_UNUSED(directiveLoc);
+ Expect(context, TokenType::LParent, Diagnostics::syntaxError);
+ Token tk = PeekToken(context);
+ auto finish = [&]() -> void { SkipToEndOfLine(context); };
+ if (tk.type == TokenType::Identifier)
+ {
+ // #pragma warning (push)
+ if (tk.getContent() == "push")
+ {
+ AdvanceToken(context);
+ context->m_preprocessor->warningStateTracker->addPragmaPush(tk.loc);
+ }
+ // #pragma warning (pop)
+ else if (tk.getContent() == "pop")
+ {
+ AdvanceToken(context);
+ context->m_preprocessor->warningStateTracker->addPragmaPop(tk.loc, GetSink(context));
+ }
+ else
+ {
+ // #pragma warning (spec : id-list [; ...]), examples:
+ // (disable : 123) : disables 123
+ // (disable : 1 2 ; error 4 5 6) disables 1, then disables 2, then errors 4, ...
+ // (disable : 1 ; default : 1) disables 1, then defaults 1
+ // Parse a list of 'specifier : id-list', separated by ';'
+ while (true)
+ {
+ // Read the specifier
+ // We need the raw token location because if the token is a #definition,
+ // PeekToken().loc would be where the definition is, not the invocation
+ // PeekRawToken().loc is where the invocation of the macro is located:
+ // Example:
+ // #define SPEC suppress // (a)
+ // #pragma warning (SPEC : 12) (b)
+ // Here the raw token is 'SPEC' located at its invocation on line (b)
+ // and the token is 'suppress' located at the macro definition on line (a)
+ // The #pragma warning should take effect from line (b), not line (a),
+ // So we need the raw token location.
+ SourceLoc specifierLocation = PeekRawToken(context).loc;
+ Token id;
+ Expect(context, TokenType::Identifier, Diagnostics::syntaxError, &id);
+ PragmaWarningSpecifier specifier;
+ SourceLoc nextLineEnd = {}; // Needed for suppress
+ if (id.getContent() == "default")
+ {
+ specifier = PragmaWarningSpecifier::Default;
+ }
+ else if (id.getContent() == "disable")
+ {
+ specifier = PragmaWarningSpecifier::Disable;
+ }
+ else if (id.getContent() == "error")
+ {
+ specifier = PragmaWarningSpecifier::Error;
+ }
+ else if (id.getContent() == "once")
+ {
+ specifier = PragmaWarningSpecifier::Once;
+ }
+ else if (id.getContent() == "suppress")
+ {
+ specifier = PragmaWarningSpecifier::Suppress;
+ // We need to start from subDirectiveToken.loc because the next tokens
+ // might be macro invocations, and located before this #pragma warning line.
+ nextLineEnd = FindNextEndOfLine(context, specifierLocation, 2);
+ if (!nextLineEnd.isValid())
+ {
+ GetSink(context)->diagnose(
+ specifierLocation,
+ Diagnostics::pragmaWarningSuppressCannotIdentifyNextLine);
+ return finish();
+ }
+ }
+ else
+ {
+ GetSink(context)->diagnose(
+ specifierLocation,
+ Diagnostics::pragmaWarningUnknownSpecifier,
+ id.getContent());
+ return finish();
+ }
+ Expect(context, TokenType::Colon, Diagnostics::syntaxError);
+ // Read the id list
+ while (true)
+ {
+ // Same logic as for the specifierLocation
+ SourceLoc idLocation = PeekRawToken(context).loc;
+ Token warningNumberToken = PeekToken(context);
+ if (warningNumberToken.type == TokenType::IntegerLiteral)
+ {
+ AdvanceToken(context);
+ int warningNumber = stringToInt(warningNumberToken.getContent());
+ context->m_preprocessor->warningStateTracker->addEntry(
+ idLocation,
+ nextLineEnd,
+ warningNumber,
+ specifier,
+ GetSink(context));
+ }
+ else
+ {
+ break;
+ }
+ }
+ SourceLoc endLoc = PeekRawToken(context).loc;
+ Token end = PeekToken(context);
+ if (end.type == TokenType::Semicolon)
+ {
+ // We need to parse the next 'spec : id-list'
+ AdvanceToken(context);
+ continue;
+ }
+ else if (end.type == TokenType::RParent)
+ {
+ break;
+ }
+ else
+ {
+ GetSink(context)->diagnose(
+ endLoc,
+ Diagnostics::unexpectedToken,
+ end.getContent());
+ return finish();
+ }
+ }
+ }
+ }
+ else
+ {
+ GetSink(context)->diagnose(tk, Diagnostics::syntaxError);
+ return finish();
+ }
+ Expect(context, TokenType::RParent, Diagnostics::syntaxError);
+}
+
// Information about a specific `#pragma` directive
struct PragmaDirective
{
@@ -3833,6 +4304,8 @@ struct PragmaDirective
static const PragmaDirective kPragmaDirectives[] = {
{"once", &handlePragmaOnceDirective},
+ {"warning", &handlePragmaWarningDirective},
+
{NULL, NULL},
};
@@ -4071,6 +4544,13 @@ void Preprocessor::popInputFile()
conditional->ifToken.getContent());
}
+ {
+ SourceView* sourceView = inputFile->getLexer()->m_sourceView;
+ auto lastSegment = sourceView->getLastSegment();
+ absoluteSourceLocCounter +=
+ SourceRange(lastSegment.begin, sourceView->getRange().end).getSize();
+ }
+
// We will update the current file to the parent of whatever
// the `inputFile` was (usually the file that `#include`d it).
//
@@ -4086,6 +4566,13 @@ void Preprocessor::popInputFile()
{
endOfFileToken = eofToken;
}
+ else
+ {
+ SourceView* sourceView = parentFile->getLexer()->m_sourceView;
+ sourceView->addAbsoluteSegment(
+ parentFile->getExpansionStream()->peekLoc(),
+ absoluteSourceLocCounter);
+ }
delete inputFile;
}
@@ -4230,6 +4717,20 @@ static TokenList ReadAllTokens(Preprocessor* preprocessor)
}
}
+static void finalCheckPragmaWarnings(Preprocessor* preprocessor)
+{
+ auto tracker = preprocessor->warningStateTracker;
+ if (tracker)
+ {
+ auto sink = GetSink(preprocessor);
+ for (const auto& pushed : tracker->stack)
+ {
+ sink->diagnose(pushed, Diagnostics::pragmaWarningPushNotPopped);
+ }
+ tracker->stack.clearAndDeallocate();
+ }
+}
+
} // namespace preprocessor
/// Try to look up a macro with the given `macroName` and produce its value as a string
@@ -4300,6 +4801,11 @@ TokenList preprocessSource(
{
desc.contentAssistInfo = &linkage->contentAssistInfo.preprocessorInfo;
}
+
+ preprocessor::WarningStateTracker* wst =
+ new preprocessor::WarningStateTracker(desc.sourceManager);
+ desc.sink->setSourceWarningStateTracker(wst);
+
return preprocessSource(file, desc, outDetectedLanguage);
}
@@ -4321,6 +4827,9 @@ TokenList preprocessSource(
preprocessor.endOfFileToken.flags = TokenFlag::AtStartOfLine;
preprocessor.contentAssistInfo = desc.contentAssistInfo;
+ preprocessor.warningStateTracker =
+ dynamicCast<preprocessor::WarningStateTracker>(desc.sink->getSourceWarningStateTracker());
+
// Add builtin macros
{
auto namePool = desc.namePool;
@@ -4366,7 +4875,7 @@ TokenList preprocessSource(
// create an initial input stream based on the provided buffer
InputFile* primaryInputFile = new InputFile(&preprocessor, sourceView);
- preprocessor.pushInputFile(primaryInputFile);
+ preprocessor.pushInputFile(primaryInputFile, sourceView->getRange().begin);
}
TokenList tokens = ReadAllTokens(&preprocessor);
@@ -4376,6 +4885,8 @@ TokenList preprocessSource(
handler->handleEndOfTranslationUnit(&preprocessor);
}
+ finalCheckPragmaWarnings(&preprocessor);
+
// debugging: build the pre-processed source back together
#if 0
StringBuilder sb;