diff options
| author | Ronan <ro.cailleau@gmail.com> | 2025-04-25 00:48:37 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-04-24 15:48:37 -0700 |
| commit | 5f632cd204b7a85f3a97b6c316c5a34f9fc8193e (patch) | |
| tree | 7dc74fbe8c80870a4c485bbaa5765ff379914779 | |
| parent | c7ecf3039d2cc8680f1ea5f4bee2d13521ae34f7 (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>
28 files changed, 964 insertions, 11 deletions
diff --git a/source/compiler-core/slang-diagnostic-sink.cpp b/source/compiler-core/slang-diagnostic-sink.cpp index f9a7d9c88..28a98266a 100644 --- a/source/compiler-core/slang-diagnostic-sink.cpp +++ b/source/compiler-core/slang-diagnostic-sink.cpp @@ -612,10 +612,20 @@ bool DiagnosticSink::diagnoseImpl( return true; } -Severity DiagnosticSink::getEffectiveMessageSeverity(DiagnosticInfo const& info) +Severity DiagnosticSink::getEffectiveMessageSeverity( + DiagnosticInfo const& info, + SourceLoc const& location) { Severity effectiveSeverity = info.severity; + if (effectiveSeverity <= Severity::Warning && m_sourceWarningStateTracker) + { + effectiveSeverity = m_sourceWarningStateTracker->consumeWarningSeverity( + location, + info.id, + effectiveSeverity); + } + Severity* pSeverityOverride = m_severityOverrides.tryGetValue(info.id); // See if there is an override @@ -639,7 +649,7 @@ bool DiagnosticSink::diagnoseImpl( DiagnosticArg const* args) { // Override the severity in the 'info' structure to pass it further into formatDiagnostics - info.severity = getEffectiveMessageSeverity(info); + info.severity = getEffectiveMessageSeverity(info, pos); if (info.severity == Severity::Disable) return false; diff --git a/source/compiler-core/slang-diagnostic-sink.h b/source/compiler-core/slang-diagnostic-sink.h index 90f1bfccf..2d60747d9 100644 --- a/source/compiler-core/slang-diagnostic-sink.h +++ b/source/compiler-core/slang-diagnostic-sink.h @@ -81,6 +81,11 @@ public: } }; +struct SourceWarningStateTrackerBase : public RefObject +{ + virtual Severity consumeWarningSeverity(SourceLoc loc, int id, Severity severity) = 0; +}; + class Name; void printDiagnosticArg(StringBuilder& sb, char const* str); @@ -249,6 +254,15 @@ public: void setParentSink(DiagnosticSink* parentSink) { m_parentSink = parentSink; } DiagnosticSink* getParentSink() const { return m_parentSink; } + void setSourceWarningStateTracker(SourceWarningStateTrackerBase* ptr) + { + m_sourceWarningStateTracker = ptr; + } + RefPtr<SourceWarningStateTrackerBase> getSourceWarningStateTracker() const + { + return m_sourceWarningStateTracker; + } + /// Reset state. /// Resets error counts. Resets the output buffer. void reset(); @@ -283,7 +297,7 @@ protected: DiagnosticArg const* args); bool diagnoseImpl(DiagnosticInfo const& info, const UnownedStringSlice& formattedMessage); - Severity getEffectiveMessageSeverity(DiagnosticInfo const& info); + Severity getEffectiveMessageSeverity(DiagnosticInfo const& info, SourceLoc const& location); /// If set all diagnostics (as formatted by *this* sink, will be routed to the parent). DiagnosticSink* m_parentSink = nullptr; @@ -304,6 +318,8 @@ protected: // Configuration that allows the user to control the severity of certain diagnostic messages Dictionary<int, Severity> m_severityOverrides; + + RefPtr<SourceWarningStateTrackerBase> m_sourceWarningStateTracker = nullptr; }; /// An `ISlangWriter` that writes directly to a diagnostic sink. diff --git a/source/compiler-core/slang-lexer.cpp b/source/compiler-core/slang-lexer.cpp index 048c266ca..c2ee5cd69 100644 --- a/source/compiler-core/slang-lexer.cpp +++ b/source/compiler-core/slang-lexer.cpp @@ -374,9 +374,14 @@ static void _lexIdentifier(Lexer* lexer) } } -static SourceLoc _getSourceLoc(Lexer* lexer) +static SourceLoc _getSourceLoc(const Lexer& lexer, const char* it) { - return lexer->m_startLoc + (lexer->m_cursor - lexer->m_begin); + return lexer.m_startLoc + (it - lexer.m_begin); +} + +static SourceLoc _getSourceLoc(const Lexer* lexer) +{ + return _getSourceLoc(*lexer, lexer->m_cursor); } static void _lexDigits(Lexer* lexer, int base) @@ -1857,4 +1862,32 @@ TokenList Lexer::lexAllTokens() return UnownedStringSlice(in.begin() + offset, in.begin() + offset + tok.charsCount); } +SourceLoc Lexer::findNextLineEnd(SourceLoc from, UInt& lineCount) const +{ + const char* it = m_begin + (from.getRaw() - m_startLoc.getRaw()); + if (it >= m_begin && it < m_end) + { + while (it != m_end) + { + const char c = *it; + if (c == '\n' || c == '\r') + { + const char next = ((it + 1) == m_end) ? char(kEOF) : *(it + 1); + if ((next ^ c) == ('\n' ^ '\r')) + { + ++it; + } + --lineCount; + if (lineCount == 0) + { + SourceLoc res = _getSourceLoc(*this, it); + return res; + } + } + ++it; + } + } + return {}; +} + } // namespace Slang diff --git a/source/compiler-core/slang-lexer.h b/source/compiler-core/slang-lexer.h index 0152ff6f5..c39d130b7 100644 --- a/source/compiler-core/slang-lexer.h +++ b/source/compiler-core/slang-lexer.h @@ -149,6 +149,8 @@ struct Lexer return ((m_lexerFlags & kLexerFlag_SuppressDiagnostics) == 0) ? m_sink : nullptr; } + SourceLoc findNextLineEnd(SourceLoc from, UInt& lineCount) const; + SourceView* m_sourceView; DiagnosticSink* m_sink; NamePool* m_namePool; diff --git a/source/compiler-core/slang-source-loc.cpp b/source/compiler-core/slang-source-loc.cpp index 5058a1522..cfde5f1da 100644 --- a/source/compiler-core/slang-source-loc.cpp +++ b/source/compiler-core/slang-source-loc.cpp @@ -1018,4 +1018,44 @@ PathInfo SourceManager::getPathInfo(SourceLoc loc, SourceLocType type) } } +SourceLoc::RawValue SourceView::getAbsoluteLocation(SourceLoc location) const +{ + AbsoluteSegment segment; + if (m_absSegments.getCount()) + { + if (m_absSegments.getFirst().begin > location) + { + segment.begin = m_range.begin; + segment.absoluteBegin = m_absoluteLocationBase; + } + else + { + auto it = std::upper_bound( + m_absSegments.begin(), + m_absSegments.end(), + location, + [](SourceLoc const& loc, AbsoluteSegment const& seg) + { return loc < seg.begin; }) - + 1; + segment = *it; + } + } + else + { + segment = getLastSegment(); + } + auto offset = SourceRange(segment.begin, location).getSize(); + return segment.absoluteBegin + offset; +} + +SourceLoc::RawValue SourceManager::getAbsoluteLocation(SourceLoc location) const +{ + SourceLoc::RawValue res = 0; + if (const SourceView* view = findSourceView(location)) + { + res = view->getAbsoluteLocation(location); + } + return res; +} + } // namespace Slang diff --git a/source/compiler-core/slang-source-loc.h b/source/compiler-core/slang-source-loc.h index c46c9063a..674ed2076 100644 --- a/source/compiler-core/slang-source-loc.h +++ b/source/compiler-core/slang-source-loc.h @@ -147,6 +147,10 @@ public: SLANG_FORCE_INLINE bool operator==(const ThisType& rhs) const { return raw == rhs.raw; } SLANG_FORCE_INLINE bool operator!=(const ThisType& rhs) const { return !(raw == rhs.raw); } + SLANG_FORCE_INLINE bool operator<(const ThisType& rhs) const { return raw < rhs.raw; } + SLANG_FORCE_INLINE bool operator>(const ThisType& rhs) const { return raw > rhs.raw; } + SLANG_FORCE_INLINE bool operator<=(const ThisType& rhs) const { return raw <= rhs.raw; } + SLANG_FORCE_INLINE bool operator>=(const ThisType& rhs) const { return raw >= rhs.raw; } RawValue getRaw() const { return raw; } void setRaw(RawValue value) { raw = value; } @@ -177,7 +181,7 @@ struct SourceRange return rawLoc >= begin.getRaw() && rawLoc <= end.getRaw(); } /// Get the total size - UInt getSize() const { return UInt(end.getRaw() - begin.getRaw()); } + SourceLoc::RawValue getSize() const { return end.getRaw() - begin.getRaw(); } /// Get the offset of a loc in this range int getOffset(SourceLoc loc) const @@ -190,7 +194,7 @@ struct SourceRange SourceLoc getSourceLocFromOffset(uint32_t offset) const { SLANG_ASSERT(offset <= getSize()); - return begin + Int(offset); + return begin + offset; } SourceRange() {} @@ -406,6 +410,48 @@ public: ///< locations. Relative to the line number in the underlying file. }; + // Represents a segment of a source. + // All SourceLoc in segment are linearly mapped relative to absoluteBegin + struct AbsoluteSegment + { + // SourceLoc in the file range. + SourceLoc begin = {}; + // Location in the absolute mapping of locations ordered by includes. + SourceLoc::RawValue absoluteBegin = {}; + }; + + // Set the base of the absolute location mapping for this SourceView. + void setAbsoluteLocationBase(SourceLoc::RawValue absLoc) { m_absoluteLocationBase = absLoc; } + + AbsoluteSegment getLastSegment() const + { + AbsoluteSegment res; + if (m_absSegments.getCount()) + { + res = m_absSegments.getLast(); + } + else + { + res.begin = m_range.begin; + res.absoluteBegin = m_absoluteLocationBase; + } + return res; + } + + // Add a segment of absolute mapping after the previous ones. + void addAbsoluteSegment(SourceLoc begin, SourceLoc::RawValue absoluteBegin) + { + SLANG_ASSERT(m_range.contains(begin)); + SLANG_ASSERT(getLastSegment().begin < begin); + AbsoluteSegment seg; + seg.begin = begin; + seg.absoluteBegin = absoluteBegin; + m_absSegments.add(seg); + } + + // Maps a SourceLoc inside this SourceView to a unique absolute location ordered by includes. + SourceLoc::RawValue getAbsoluteLocation(SourceLoc loc) const; + /// Given a sourceLoc finds the entry associated with it. If returns -1 then no entry is /// associated with this location, and therefore the location should be interpreted as an offset /// into the underlying sourceFile. @@ -495,6 +541,8 @@ protected: SourceFile* m_sourceFile; ///< The source file. Can hold the line breaks List<Entry> m_entries; ///< An array entries describing how we should interpret a range, ///< starting from the start location. + SourceLoc::RawValue m_absoluteLocationBase = 0; ///< Base of the absolute location mapping. + List<AbsoluteSegment> m_absSegments; ///< Segments of absolute location mapping. }; struct SourceManager @@ -564,6 +612,9 @@ struct SourceManager void addSourceFile(const String& uniqueIdentity, SourceFile* sourceFile); void addSourceFileIfNotExist(const String& uniqueIdentity, SourceFile* sourceFile); + // Maps a SourceLoc to an absolute location + SourceLoc::RawValue getAbsoluteLocation(SourceLoc location) const; + /// Get the slice pool StringSlicePool& getStringSlicePool() { return m_slicePool; } diff --git a/source/slang/slang-diagnostic-defs.h b/source/slang/slang-diagnostic-defs.h index dfea9fede..5dcc41bd8 100644 --- a/source/slang/slang-diagnostic-defs.h +++ b/source/slang/slang-diagnostic-defs.h @@ -429,6 +429,34 @@ DIAGNOSTIC( Warning, pragmaOnceIgnored, "pragma once was ignored - this is typically because is not placed in an include") +DIAGNOSTIC(15610, Error, pragmaWarningGenericError, "Error in #pragma warning processing: $0") +DIAGNOSTIC( + 15611, + Warning, + pragmaWarningPopEmpty, + "Detected #pragma warning(pop) with no corresponding #pragma warning(push)") +DIAGNOSTIC( + 15612, + Warning, + pragmaWarningPushNotPopped, + "Detected #pragma warning(push) with no corresponding #pragma warning(pop)") +DIAGNOSTIC(15613, Warning, pragmaWarningUnknownSpecifier, "Unknown #pragma warning specifier '$0'") +DIAGNOSTIC( + 15614, + Warning, + pragmaWarningSuppressCannotIdentifyNextLine, + "Cannot identify the next line to suppress in #pragma warning suppress") +DIAGNOSTIC( + 15615, + Warning, + pragmaWarningCannotInsertHere, + "Cannot insert #pragma warning here for id '$0'") +DIAGNOSTIC( + 15616, + Note, + pragmaWarningPointSuppress, + "#pragma warning for id '$0' was suppressed here") + // 159xx - user-defined error/warning DIAGNOSTIC(15900, Error, userDefinedError, "#error: $0") 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; diff --git a/tests/preprocessor/pragma-warning/default-1.slang b/tests/preprocessor/pragma-warning/default-1.slang new file mode 100644 index 000000000..46046ce69 --- /dev/null +++ b/tests/preprocessor/pragma-warning/default-1.slang @@ -0,0 +1,14 @@ +//TEST:SIMPLE(filecheck=CHECK): +// #ifdef support + +int64_t GetValue(); + +void f() +{ + int i; +#pragma warning (default : 30081) + // CHECK: ([[# @LINE+1]]): warning 30081: + i = GetValue(); +} + +// Simple test of #pragma warning (default)
\ No newline at end of file diff --git a/tests/preprocessor/pragma-warning/defined-1.slang b/tests/preprocessor/pragma-warning/defined-1.slang new file mode 100644 index 000000000..0827aa1f3 --- /dev/null +++ b/tests/preprocessor/pragma-warning/defined-1.slang @@ -0,0 +1,16 @@ +//TEST:SIMPLE(filecheck=CHECK): +// #ifdef support + +int64_t GetValue(); +#define SPECIFIER error +#define IDs 30081 12 23 45 + +void f() +{ + int i; +#pragma warning (SPECIFIER : IDs) + // CHECK: ([[# @LINE+1]]): error 30081: + i = GetValue(); +} + +// Test that #pragma warning works with #defined specifiers and IDs
\ No newline at end of file diff --git a/tests/preprocessor/pragma-warning/defined-2.slang b/tests/preprocessor/pragma-warning/defined-2.slang new file mode 100644 index 000000000..7cde40a5a --- /dev/null +++ b/tests/preprocessor/pragma-warning/defined-2.slang @@ -0,0 +1,18 @@ +//TEST:SIMPLE(filecheck=CHECK): +// #ifdef support + +int64_t GetValue(); +#define SPECIFIER disable +#define IDs 30081 + +void f() +{ + int i; +#pragma warning (error : 30081) + // CHECK: ([[# @LINE+1]]): error 30081: + i = GetValue(); +#pragma warning (SPECIFIER : IDs) +} + +// Test that #pragma warning with #defined specifiers and IDs uses the correct SourceLoc +// (Use the macro invocation's SourceLoc, not the macro definition's SourceLoc)
\ No newline at end of file diff --git a/tests/preprocessor/pragma-warning/disable-1.slang b/tests/preprocessor/pragma-warning/disable-1.slang new file mode 100644 index 000000000..c93845818 --- /dev/null +++ b/tests/preprocessor/pragma-warning/disable-1.slang @@ -0,0 +1,14 @@ +//TEST:SIMPLE(filecheck=CHECK): +// #ifdef support + +int64_t GetValue(); + +void f() +{ + int i; +#pragma warning (disable : 30081) + // CHECK-NOT: ([[# @LINE+1]]): warning 30081: + i = GetValue(); +} + +// Simple test of #pragma warning (disable)
\ No newline at end of file diff --git a/tests/preprocessor/pragma-warning/error-1.slang b/tests/preprocessor/pragma-warning/error-1.slang new file mode 100644 index 000000000..a80e2bd89 --- /dev/null +++ b/tests/preprocessor/pragma-warning/error-1.slang @@ -0,0 +1,14 @@ +//TEST:SIMPLE(filecheck=CHECK): +// #ifdef support + +int64_t GetValue(); + +void f() +{ + int i; +#pragma warning (error : 30081) + // CHECK: ([[# @LINE+1]]): error 30081: + i = GetValue(); +} + +// Simple test of #pragma warning (error)
\ No newline at end of file diff --git a/tests/preprocessor/pragma-warning/helper-1.slang b/tests/preprocessor/pragma-warning/helper-1.slang new file mode 100644 index 000000000..f5555e4f5 --- /dev/null +++ b/tests/preprocessor/pragma-warning/helper-1.slang @@ -0,0 +1 @@ +#pragma warning (disable : 30081)
\ No newline at end of file diff --git a/tests/preprocessor/pragma-warning/helper-2.slang b/tests/preprocessor/pragma-warning/helper-2.slang new file mode 100644 index 000000000..2d448a6d2 --- /dev/null +++ b/tests/preprocessor/pragma-warning/helper-2.slang @@ -0,0 +1,3 @@ +#pragma warning (push) +#pragma warning (disable : 30081) +#pragma warning (pop)
\ No newline at end of file diff --git a/tests/preprocessor/pragma-warning/multi-specifiers-1.slang b/tests/preprocessor/pragma-warning/multi-specifiers-1.slang new file mode 100644 index 000000000..7c299ed13 --- /dev/null +++ b/tests/preprocessor/pragma-warning/multi-specifiers-1.slang @@ -0,0 +1,14 @@ +//TEST:SIMPLE(filecheck=CHECK): +// #ifdef support + +int64_t GetValue(); + +void f() +{ + int i; +#pragma warning (disable : 30081 ; error : 30081) + // CHECK: ([[# @LINE+1]]): error 30081: + i = GetValue(); +} + +// Test that the last specifier is applied
\ No newline at end of file diff --git a/tests/preprocessor/pragma-warning/multi-specifiers-2.slang b/tests/preprocessor/pragma-warning/multi-specifiers-2.slang new file mode 100644 index 000000000..f4960bf75 --- /dev/null +++ b/tests/preprocessor/pragma-warning/multi-specifiers-2.slang @@ -0,0 +1,18 @@ +//TEST:SIMPLE(filecheck=CHECK): +// #ifdef support + +int64_t GetValue(); + +void f() +{ + int i; +#pragma warning (disable : 30081 15205) + // CHECK-NOT: ([[# @LINE+1]]): warning 30081: + i = GetValue(); + // CHECK-NOT: ([[# @LINE+1]]): warning 15205: +#if MY_MACRO + +#endif +} + +// Test a warning specifier on two ids
\ No newline at end of file diff --git a/tests/preprocessor/pragma-warning/once-1.slang b/tests/preprocessor/pragma-warning/once-1.slang new file mode 100644 index 000000000..e8ac29d3e --- /dev/null +++ b/tests/preprocessor/pragma-warning/once-1.slang @@ -0,0 +1,16 @@ +//TEST:SIMPLE(filecheck=CHECK): +// #ifdef support + +int64_t GetValue(); + +void f() +{ + int i; +#pragma warning (once : 30081) + // CHECK: ([[# @LINE+1]]): warning 30081: + i = GetValue(); + // CHECK-NOT: ([[# @LINE+1]]): warning 30081: + i = GetValue(); +} + +// Simple test of #pragma warning (once)
\ No newline at end of file diff --git a/tests/preprocessor/pragma-warning/once-2.slang b/tests/preprocessor/pragma-warning/once-2.slang new file mode 100644 index 000000000..423991407 --- /dev/null +++ b/tests/preprocessor/pragma-warning/once-2.slang @@ -0,0 +1,25 @@ +//TEST:SIMPLE(filecheck=CHECK): +// #ifdef support + +int64_t GetValue(); + +void f() +{ + int i; +#pragma warning (once : 30081) + // CHECK: ([[# @LINE+1]]): warning 30081: + i = GetValue(); + // CHECK-NOT: ([[# @LINE+1]]): warning 30081: + i = GetValue(); +#pragma warning (once : 30081) + // CHECK: ([[# @LINE+1]]): warning 30081: + i = GetValue(); + // CHECK-NOT: ([[# @LINE+1]]): warning 30081: + i = GetValue(); + // CHECK-NOT: ([[# @LINE+1]]): warning 30081: + i = GetValue(); + // CHECK-NOT: ([[# @LINE+1]]): warning 30081: + i = GetValue(); +} + +// Test that #pragma warning (once) are emitted once per segment
\ No newline at end of file diff --git a/tests/preprocessor/pragma-warning/pop-empty.slang b/tests/preprocessor/pragma-warning/pop-empty.slang new file mode 100644 index 000000000..bc1ec1d48 --- /dev/null +++ b/tests/preprocessor/pragma-warning/pop-empty.slang @@ -0,0 +1,7 @@ +//TEST:SIMPLE(filecheck=CHECK): +// #ifdef support + +// CHECK-NOT: ([[# @LINE+1]]): warning 30081: +#pragma warning (pop) + +// Test that #pragma warning (pop) emits a warning when the stack is empty
\ No newline at end of file diff --git a/tests/preprocessor/pragma-warning/push-pop-1.slang b/tests/preprocessor/pragma-warning/push-pop-1.slang new file mode 100644 index 000000000..a6265dc9f --- /dev/null +++ b/tests/preprocessor/pragma-warning/push-pop-1.slang @@ -0,0 +1,14 @@ +//TEST:SIMPLE(filecheck=CHECK): +// #ifdef support +#pragma warning (push) +#pragma warning (error : 30081) +int64_t GetValue(); +#pragma warning (pop) +void f() +{ + int i; + // CHECK: ([[# @LINE+1]]): warning 30081: + i = GetValue(); +} + +// Test that #pragma warning (push) and (pop) work as expected
\ No newline at end of file diff --git a/tests/preprocessor/pragma-warning/push-pop-2.slang b/tests/preprocessor/pragma-warning/push-pop-2.slang new file mode 100644 index 000000000..0ebf5a85d --- /dev/null +++ b/tests/preprocessor/pragma-warning/push-pop-2.slang @@ -0,0 +1,15 @@ +//TEST:SIMPLE(filecheck=CHECK): +// #ifdef support +#pragma warning (error : 30081) +#pragma warning (push) +#pragma warning (disable : 30081) +int64_t GetValue(); +#pragma warning (pop) +void f() +{ + int i; + // CHECK: ([[# @LINE+1]]): error 30081: + i = GetValue(); +} + +// Test that #pragma warning (push) and (pop) work as expected
\ No newline at end of file diff --git a/tests/preprocessor/pragma-warning/push-pop-once.slang b/tests/preprocessor/pragma-warning/push-pop-once.slang new file mode 100644 index 000000000..1521840a8 --- /dev/null +++ b/tests/preprocessor/pragma-warning/push-pop-once.slang @@ -0,0 +1,17 @@ +//TEST:SIMPLE(filecheck=CHECK): +// #ifdef support +int64_t GetValue(); +void f() +{ + int i; +#pragma warning (once : 30081) + // CHECK: ([[# @LINE+1]]): warning 30081: + i = GetValue(); +#pragma warning (push) +#pragma warning (once : 30081) +#pragma warning (pop) + // CHECK-NOT: ([[# @LINE+1]]): warning 30081: + i = GetValue(); +} + +// Test that #pragma warning (push) and (pop) with (once)
\ No newline at end of file diff --git a/tests/preprocessor/pragma-warning/pushed-not-popped.slang b/tests/preprocessor/pragma-warning/pushed-not-popped.slang new file mode 100644 index 000000000..0bcccd53e --- /dev/null +++ b/tests/preprocessor/pragma-warning/pushed-not-popped.slang @@ -0,0 +1,7 @@ +//TEST:SIMPLE(filecheck=CHECK): +// #ifdef support + +// CHECK: ([[# @LINE+1]]): warning 15612: +#pragma warning (push) + +// Test that #pragma warning (push) emits a warning when the stack is not empty at the end
\ No newline at end of file diff --git a/tests/preprocessor/pragma-warning/suppress-1.slang b/tests/preprocessor/pragma-warning/suppress-1.slang new file mode 100644 index 000000000..bafe08b73 --- /dev/null +++ b/tests/preprocessor/pragma-warning/suppress-1.slang @@ -0,0 +1,14 @@ +//TEST:SIMPLE(filecheck=CHECK): +// #ifdef support + +int64_t GetValue(); + +void f() +{ + int i; + // CHECK-NOT: ([[# @LINE+2]]): warning 30081: +#pragma warning (suppress : 30081) + i = GetValue(); +} + +// Simple test of #pragma warning (suppress)
\ No newline at end of file diff --git a/tests/preprocessor/pragma-warning/with-includes-1.slang b/tests/preprocessor/pragma-warning/with-includes-1.slang new file mode 100644 index 000000000..397ef8487 --- /dev/null +++ b/tests/preprocessor/pragma-warning/with-includes-1.slang @@ -0,0 +1,14 @@ +//TEST:SIMPLE(filecheck=CHECK): +// #ifdef support +#include "helper-1.slang" + +int64_t GetValue(); + +void f() +{ + int i; + // CHECK-NOT: ([[# @LINE+1]]): warning 30081: + i = GetValue(); +} + +// Test that the #pragma warning (disable) in helper-1.slang disables the warning on line 10
\ No newline at end of file diff --git a/tests/preprocessor/pragma-warning/with-includes-2.slang b/tests/preprocessor/pragma-warning/with-includes-2.slang new file mode 100644 index 000000000..13e34520f --- /dev/null +++ b/tests/preprocessor/pragma-warning/with-includes-2.slang @@ -0,0 +1,14 @@ +//TEST:SIMPLE(filecheck=CHECK): +// #ifdef support +#include "helper-2.slang" + +int64_t GetValue(); + +void f() +{ + int i; + // CHECK: ([[# @LINE+1]]): warning 30081: + i = GetValue(); +} + +// Test that the #pragma warning (push / pop) englobing helper-2.slang are working
\ No newline at end of file diff --git a/tests/preprocessor/pragma-warning/wrong-specifier.slang b/tests/preprocessor/pragma-warning/wrong-specifier.slang new file mode 100644 index 000000000..8962d4b27 --- /dev/null +++ b/tests/preprocessor/pragma-warning/wrong-specifier.slang @@ -0,0 +1,7 @@ +//TEST:SIMPLE(filecheck=CHECK): +// #ifdef support + +// CHECK: ([[# @LINE+1]]): warning 15613: +#pragma warning (diasble : 30081) + +// Test that #pragma warning emits a warning if the specifier ins't correct
\ No newline at end of file |
