diff --git a/clang/include/clang/Basic/SourceManager.h b/clang/include/clang/Basic/SourceManager.h index ce33423551039c..d2e2e914327f2b 100644 --- a/clang/include/clang/Basic/SourceManager.h +++ b/clang/include/clang/Basic/SourceManager.h @@ -1676,6 +1676,11 @@ class SourceManager : public RefCountedBase { isInTheSameTranslationUnit(std::pair &LOffs, std::pair &ROffs) const; + /// \param Loc a source location in a loaded AST (of a PCH/Module file). + /// \returns a FileID uniquely identifies the AST of a loaded + /// module/PCH where `Loc` is at. + FileID getUniqueLoadedASTFileID(SourceLocation Loc) const; + /// Determines whether the two decomposed source location is in the same TU. bool isInTheSameTranslationUnitImpl( const std::pair &LOffs, diff --git a/clang/include/clang/Lex/Preprocessor.h b/clang/include/clang/Lex/Preprocessor.h index 9b1628d2d86f9e..9d8a1aae23df3e 100644 --- a/clang/include/clang/Lex/Preprocessor.h +++ b/clang/include/clang/Lex/Preprocessor.h @@ -2883,11 +2883,41 @@ class Preprocessor { /// otherwise. SourceLocation CurrentSafeBufferOptOutStart; // It is used to report the start location of an never-closed region. - // An ordered sequence of "-Wunsafe-buffer-usage" opt-out regions in one - // translation unit. Each region is represented by a pair of start and end - // locations. A region is "open" if its' start and end locations are - // identical. - SmallVector, 8> SafeBufferOptOutMap; + using SafeBufferOptOutRegionsTy = + SmallVector, 16>; + // An ordered sequence of "-Wunsafe-buffer-usage" opt-out regions in this + // translation unit. Each region is represented by a pair of start and + // end locations. + SafeBufferOptOutRegionsTy SafeBufferOptOutMap; + + // The "-Wunsafe-buffer-usage" opt-out regions in loaded ASTs. We use the + // following structure to manage them by their ASTs. + struct { + // A map from unique IDs to region maps of loaded ASTs. The ID identifies a + // loaded AST. See `SourceManager::getUniqueLoadedASTID`. + llvm::DenseMap LoadedRegions; + + // Returns a reference to the safe buffer opt-out regions of the loaded + // AST where `Loc` belongs to. (Construct if absent) + SafeBufferOptOutRegionsTy & + findAndConsLoadedOptOutMap(SourceLocation Loc, SourceManager &SrcMgr) { + return LoadedRegions[SrcMgr.getUniqueLoadedASTFileID(Loc)]; + } + + // Returns a reference to the safe buffer opt-out regions of the loaded + // AST where `Loc` belongs to. (This const function returns nullptr if + // absent.) + const SafeBufferOptOutRegionsTy * + lookupLoadedOptOutMap(SourceLocation Loc, + const SourceManager &SrcMgr) const { + FileID FID = SrcMgr.getUniqueLoadedASTFileID(Loc); + auto Iter = LoadedRegions.find(FID); + + if (Iter == LoadedRegions.end()) + return nullptr; + return &Iter->getSecond(); + } + } LoadedSafeBufferOptOutMap; public: /// \return true iff the given `Loc` is in a "-Wunsafe-buffer-usage" opt-out @@ -2918,6 +2948,18 @@ class Preprocessor { /// opt-out region bool isPPInSafeBufferOptOutRegion(SourceLocation &StartLoc); + /// \return a sequence of SourceLocations representing ordered opt-out regions + /// specified by + /// `\#pragma clang unsafe_buffer_usage begin/end`s of this translation unit. + SmallVector serializeSafeBufferOptOutMap() const; + + /// \param SrcLocSeqs a sequence of SourceLocations deserialized from a + /// record of code `PP_UNSAFE_BUFFER_USAGE`. + /// \return true iff the `Preprocessor` has been updated; false `Preprocessor` + /// is same as itself before the call. + bool setDeserializedSafeBufferOptOutMap( + const SmallVectorImpl &SrcLocSeqs); + private: /// Helper functions to forward lexing to the actual lexer. They all share the /// same signature. diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h index 52a6c5e10f8025..4ce6cd74dd834f 100644 --- a/clang/include/clang/Serialization/ASTBitCodes.h +++ b/clang/include/clang/Serialization/ASTBitCodes.h @@ -694,6 +694,9 @@ enum ASTRecordTypes { /// Record code for lexical and visible block for delayed namespace in /// reduced BMI. DELAYED_NAMESPACE_LEXICAL_VISIBLE_RECORD = 68, + + /// Record code for \#pragma clang unsafe_buffer_usage begin/end + PP_UNSAFE_BUFFER_USAGE = 69, }; /// Record types used within a source manager block. diff --git a/clang/lib/Basic/SourceManager.cpp b/clang/lib/Basic/SourceManager.cpp index 753601e01b5c30..f0af1a3e3a38b5 100644 --- a/clang/lib/Basic/SourceManager.cpp +++ b/clang/lib/Basic/SourceManager.cpp @@ -1915,6 +1915,24 @@ SourceManager::getDecomposedIncludedLoc(FileID FID) const { return DecompLoc; } +FileID SourceManager::getUniqueLoadedASTFileID(SourceLocation Loc) const { + assert(isLoadedSourceLocation(Loc) && + "Must be a source location in a loaded PCH/Module file"); + + auto [FID, Ignore] = getDecomposedLoc(Loc); + // `LoadedSLocEntryAllocBegin` stores the sorted lowest FID of each loaded + // allocation. Later allocations have lower FileIDs. The call below is to find + // the lowest FID of a loaded allocation from any FID in the same allocation. + // The lowest FID is used to identify a loaded allocation. + const FileID *FirstFID = + llvm::lower_bound(LoadedSLocEntryAllocBegin, FID, std::greater{}); + + assert(FirstFID && + "The failure to find the first FileID of a " + "loaded AST from a loaded source location was unexpected."); + return *FirstFID; +} + bool SourceManager::isInTheSameTranslationUnitImpl( const std::pair &LOffs, const std::pair &ROffs) const { diff --git a/clang/lib/Lex/Preprocessor.cpp b/clang/lib/Lex/Preprocessor.cpp index 0b70192743a399..44b69a58f34110 100644 --- a/clang/lib/Lex/Preprocessor.cpp +++ b/clang/lib/Lex/Preprocessor.cpp @@ -58,6 +58,7 @@ #include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" +#include "llvm/ADT/iterator_range.h" #include "llvm/Support/Capacity.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/MemoryBuffer.h" @@ -1483,26 +1484,56 @@ void Preprocessor::emitFinalMacroWarning(const Token &Identifier, } bool Preprocessor::isSafeBufferOptOut(const SourceManager &SourceMgr, - const SourceLocation &Loc) const { - // Try to find a region in `SafeBufferOptOutMap` where `Loc` is in: - auto FirstRegionEndingAfterLoc = llvm::partition_point( - SafeBufferOptOutMap, - [&SourceMgr, - &Loc](const std::pair &Region) { - return SourceMgr.isBeforeInTranslationUnit(Region.second, Loc); - }); + const SourceLocation &Loc) const { + // The lambda that tests if a `Loc` is in an opt-out region given one opt-out + // region map: + auto TestInMap = [&SourceMgr](const SafeBufferOptOutRegionsTy &Map, + const SourceLocation &Loc) -> bool { + // Try to find a region in `SafeBufferOptOutMap` where `Loc` is in: + auto FirstRegionEndingAfterLoc = llvm::partition_point( + Map, [&SourceMgr, + &Loc](const std::pair &Region) { + return SourceMgr.isBeforeInTranslationUnit(Region.second, Loc); + }); + + if (FirstRegionEndingAfterLoc != Map.end()) { + // To test if the start location of the found region precedes `Loc`: + return SourceMgr.isBeforeInTranslationUnit( + FirstRegionEndingAfterLoc->first, Loc); + } + // If we do not find a region whose end location passes `Loc`, we want to + // check if the current region is still open: + if (!Map.empty() && Map.back().first == Map.back().second) + return SourceMgr.isBeforeInTranslationUnit(Map.back().first, Loc); + return false; + }; - if (FirstRegionEndingAfterLoc != SafeBufferOptOutMap.end()) { - // To test if the start location of the found region precedes `Loc`: - return SourceMgr.isBeforeInTranslationUnit(FirstRegionEndingAfterLoc->first, - Loc); - } - // If we do not find a region whose end location passes `Loc`, we want to - // check if the current region is still open: - if (!SafeBufferOptOutMap.empty() && - SafeBufferOptOutMap.back().first == SafeBufferOptOutMap.back().second) - return SourceMgr.isBeforeInTranslationUnit(SafeBufferOptOutMap.back().first, - Loc); + // What the following does: + // + // If `Loc` belongs to the local TU, we just look up `SafeBufferOptOutMap`. + // Otherwise, `Loc` is from a loaded AST. We look up the + // `LoadedSafeBufferOptOutMap` first to get the opt-out region map of the + // loaded AST where `Loc` is at. Then we find if `Loc` is in an opt-out + // region w.r.t. the region map. If the region map is absent, it means there + // is no opt-out pragma in that loaded AST. + // + // Opt-out pragmas in the local TU or a loaded AST is not visible to another + // one of them. That means if you put the pragmas around a `#include + // "module.h"`, where module.h is a module, it is not actually suppressing + // warnings in module.h. This is fine because warnings in module.h will be + // reported when module.h is compiled in isolation and nothing in module.h + // will be analyzed ever again. So you will not see warnings from the file + // that imports module.h anyway. And you can't even do the same thing for PCHs + // because they can only be included from the command line. + + if (SourceMgr.isLocalSourceLocation(Loc)) + return TestInMap(SafeBufferOptOutMap, Loc); + + const SafeBufferOptOutRegionsTy *LoadedRegions = + LoadedSafeBufferOptOutMap.lookupLoadedOptOutMap(Loc, SourceMgr); + + if (LoadedRegions) + return TestInMap(*LoadedRegions, Loc); return false; } @@ -1551,6 +1582,47 @@ bool Preprocessor::isPPInSafeBufferOptOutRegion(SourceLocation &StartLoc) { return InSafeBufferOptOutRegion; } +SmallVector +Preprocessor::serializeSafeBufferOptOutMap() const { + assert(!InSafeBufferOptOutRegion && + "Attempt to serialize safe buffer opt-out regions before file being " + "completely preprocessed"); + + SmallVector SrcSeq; + + for (const auto &[begin, end] : SafeBufferOptOutMap) { + SrcSeq.push_back(begin); + SrcSeq.push_back(end); + } + // Only `SafeBufferOptOutMap` gets serialized. No need to serialize + // `LoadedSafeBufferOptOutMap` because if this TU loads a pch/module, every + // pch/module in the pch-chain/module-DAG will be loaded one by one in order. + // It means that for each loading pch/module m, it just needs to load m's own + // `SafeBufferOptOutMap`. + return SrcSeq; +} + +bool Preprocessor::setDeserializedSafeBufferOptOutMap( + const SmallVectorImpl &SourceLocations) { + if (SourceLocations.size() == 0) + return false; + + assert(SourceLocations.size() % 2 == 0 && + "ill-formed SourceLocation sequence"); + + auto It = SourceLocations.begin(); + SafeBufferOptOutRegionsTy &Regions = + LoadedSafeBufferOptOutMap.findAndConsLoadedOptOutMap(*It, SourceMgr); + + do { + SourceLocation Begin = *It++; + SourceLocation End = *It++; + + Regions.emplace_back(Begin, End); + } while (It != SourceLocations.end()); + return true; +} + ModuleLoader::~ModuleLoader() = default; CommentHandler::~CommentHandler() = default; diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp index 59338b44db32f3..89bab014c86ba1 100644 --- a/clang/lib/Serialization/ASTReader.cpp +++ b/clang/lib/Serialization/ASTReader.cpp @@ -3583,6 +3583,17 @@ llvm::Error ASTReader::ReadASTBlock(ModuleFile &F, break; } + case PP_UNSAFE_BUFFER_USAGE: { + if (!Record.empty()) { + SmallVector SrcLocs; + unsigned Idx = 0; + while (Idx < Record.size()) + SrcLocs.push_back(ReadSourceLocation(F, Record, Idx)); + PP.setDeserializedSafeBufferOptOutMap(SrcLocs); + } + break; + } + case PP_CONDITIONAL_STACK: if (!Record.empty()) { unsigned Idx = 0, End = Record.size() - 1; diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp index ee3e687636e6a0..ef165979f9a9e7 100644 --- a/clang/lib/Serialization/ASTWriter.cpp +++ b/clang/lib/Serialization/ASTWriter.cpp @@ -926,6 +926,7 @@ void ASTWriter::WriteBlockInfoBlock() { RECORD(PP_CONDITIONAL_STACK); RECORD(DECLS_TO_CHECK_FOR_DEFERRED_DIAGS); RECORD(PP_ASSUME_NONNULL_LOC); + RECORD(PP_UNSAFE_BUFFER_USAGE); // SourceManager Block. BLOCK(SOURCE_MANAGER_BLOCK); @@ -2518,6 +2519,12 @@ void ASTWriter::WritePreprocessor(const Preprocessor &PP, bool IsModule) { Record.clear(); } + // Write the safe buffer opt-out region map in PP + for (SourceLocation &S : PP.serializeSafeBufferOptOutMap()) + AddSourceLocation(S, Record); + Stream.EmitRecord(PP_UNSAFE_BUFFER_USAGE, Record); + Record.clear(); + // Enter the preprocessor block. Stream.EnterSubblock(PREPROCESSOR_BLOCK_ID, 3); diff --git a/clang/test/Modules/safe_buffers_optout.cpp b/clang/test/Modules/safe_buffers_optout.cpp new file mode 100644 index 00000000000000..2129db65da7525 --- /dev/null +++ b/clang/test/Modules/safe_buffers_optout.cpp @@ -0,0 +1,151 @@ +// RUN: rm -rf %t +// RUN: mkdir -p %t +// RUN: split-file %s %t + +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -emit-module -fmodule-name=safe_buffers_test_base -x c++ %t/safe_buffers_test.modulemap -std=c++20\ +// RUN: -o %t/safe_buffers_test_base.pcm -Wunsafe-buffer-usage +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -emit-module -fmodule-name=safe_buffers_test_textual -x c++ %t/safe_buffers_test.modulemap -std=c++20\ +// RUN: -o %t/safe_buffers_test_textual.pcm -Wunsafe-buffer-usage +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -emit-module -fmodule-name=safe_buffers_test_optout -x c++ %t/safe_buffers_test.modulemap -std=c++20\ +// RUN: -fmodule-file=%t/safe_buffers_test_base.pcm -fmodule-file=%t/safe_buffers_test_textual.pcm \ +// RUN: -o %t/safe_buffers_test_optout.pcm -Wunsafe-buffer-usage +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodule-file=%t/safe_buffers_test_optout.pcm -I %t -std=c++20 -Wunsafe-buffer-usage\ +// RUN: -verify %t/safe_buffers_optout-explicit.cpp + + +// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -verify -fmodules-cache-path=%t -fmodule-map-file=%t/safe_buffers_test.modulemap -I%t\ +// RUN: -x c++ -std=c++20 -Wunsafe-buffer-usage %t/safe_buffers_optout-implicit.cpp + +//--- safe_buffers_test.modulemap +module safe_buffers_test_base { + header "base.h" +} + +module safe_buffers_test_textual { + textual header "textual.h" +} + +module safe_buffers_test_optout { + explicit module test_sub1 { header "test_sub1.h" } + explicit module test_sub2 { header "test_sub2.h" } + use safe_buffers_test_base +} + +//--- base.h +#ifdef __cplusplus +int base(int *p) { + int x = p[5]; +#pragma clang unsafe_buffer_usage begin + int y = p[5]; +#pragma clang unsafe_buffer_usage end + return x + y; +} +#endif + +//--- test_sub1.h +#include "base.h" + +#ifdef __cplusplus +int sub1(int *p) { + int x = p[5]; +#pragma clang unsafe_buffer_usage begin + int y = p[5]; +#pragma clang unsafe_buffer_usage end + return x + y + base(p); +} + +template +T sub1_T(T *p) { + T x = p[5]; +#pragma clang unsafe_buffer_usage begin + T y = p[5]; +#pragma clang unsafe_buffer_usage end + return x + y; +} +#endif + +//--- test_sub2.h +#include "base.h" + +#ifdef __cplusplus +int sub2(int *p) { + int x = p[5]; +#pragma clang unsafe_buffer_usage begin + int y = p[5]; +#pragma clang unsafe_buffer_usage end + return x + y + base(p); +} +#endif + +//--- textual.h +#ifdef __cplusplus +int textual(int *p) { + int x = p[5]; + int y = p[5]; + return x + y; +} +#endif + +//--- safe_buffers_optout-explicit.cpp +#include "test_sub1.h" +#include "test_sub2.h" + +// Testing safe buffers opt-out region serialization with modules: this +// file loads 2 submodules from top-level module +// `safe_buffers_test_optout`, which uses another top-level module +// `safe_buffers_test_base`. (So the module dependencies form a DAG.) + +// No expected warnings from base.h because base.h is a separate +// module and in a separate TU that is not textually included. The +// explicit command that builds base.h has no `-Wunsafe-buffer-usage`. + +// expected-warning@base.h:3{{unsafe buffer access}} +// expected-note@base.h:3{{pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions}} +// expected-warning@test_sub1.h:5{{unsafe buffer access}} +// expected-note@test_sub1.h:5{{pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions}} +// expected-warning@test_sub1.h:14{{unsafe buffer access}} +// expected-note@test_sub1.h:14{{pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions}} +// expected-warning@test_sub2.h:5{{unsafe buffer access}} +// expected-note@test_sub2.h:5{{pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions}} +int foo(int * p) { + int x = p[5]; // expected-warning{{unsafe buffer access}} expected-note{{pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions}} +#pragma clang unsafe_buffer_usage begin + int y = p[5]; +#pragma clang unsafe_buffer_usage end + sub1_T(p); // instantiate template + return sub1(p) + sub2(p); +} + +#pragma clang unsafe_buffer_usage begin +#include "textual.h" // This header is textually included (i.e., it is in the same TU as %s), so warnings are suppressed +#pragma clang unsafe_buffer_usage end + +//--- safe_buffers_optout-implicit.cpp +#include "test_sub1.h" +#include "test_sub2.h" + +// Testing safe buffers opt-out region serialization with modules: this +// file loads 2 submodules from top-level module +// `safe_buffers_test_optout`, which uses another top-level module +// `safe_buffers_test_base`. (So the module dependencies form a DAG.) + +// expected-warning@base.h:3{{unsafe buffer access}} +// expected-note@base.h:3{{pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions}} +// expected-warning@test_sub1.h:5{{unsafe buffer access}} +// expected-note@test_sub1.h:5{{pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions}} +// expected-warning@test_sub1.h:14{{unsafe buffer access}} +// expected-note@test_sub1.h:14{{pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions}} +// expected-warning@test_sub2.h:5{{unsafe buffer access}} +// expected-note@test_sub2.h:5{{pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions}} +int foo(int * p) { + int x = p[5]; // expected-warning{{unsafe buffer access}} expected-note{{pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions}} +#pragma clang unsafe_buffer_usage begin + int y = p[5]; +#pragma clang unsafe_buffer_usage end + sub1_T(p); // instantiate template + return sub1(p) + sub2(p); +} + +#pragma clang unsafe_buffer_usage begin +#include "textual.h" // This header is textually included (i.e., it is in the same TU as %s), so warnings are suppressed +#pragma clang unsafe_buffer_usage end diff --git a/clang/test/PCH/unsafe-buffer-usage-pragma-pch-complex.cpp b/clang/test/PCH/unsafe-buffer-usage-pragma-pch-complex.cpp new file mode 100644 index 00000000000000..03bf01dc08c356 --- /dev/null +++ b/clang/test/PCH/unsafe-buffer-usage-pragma-pch-complex.cpp @@ -0,0 +1,63 @@ +// Test PCHs: +// MAIN - includes textual_1.h +// \ loads pch_1.h - includes textual_2.h +// \ loads pch_2.h + +// RUN: rm -rf %t +// RUN: mkdir -p %t +// RUN: split-file %s %t + +// RUN: %clang_cc1 -Wno-unused-value -std=c++20 -emit-pch -o %t/pch_2.h.pch %t/pch_2.h -x c++ +// RUN: %clang_cc1 -Wno-unused-value -std=c++20 -include-pch %t/pch_2.h.pch -emit-pch -o %t/pch_1.h.pch %t/pch_1.h -x c++ +// RUN: %clang_cc1 -Wno-unused-value -std=c++20 -include-pch %t/pch_1.h.pch -verify %t/main.cpp -Wunsafe-buffer-usage + + +//--- textual_1.h +int a(int *s) { + s[2]; // <- expected warning here +#pragma clang unsafe_buffer_usage begin + return s[1]; +#pragma clang unsafe_buffer_usage end +} + +//--- textual_2.h +int b(int *s) { + s[2]; // <- expected warning here +#pragma clang unsafe_buffer_usage begin + return s[1]; +#pragma clang unsafe_buffer_usage end +} + +//--- pch_1.h +#include "textual_2.h" + +int c(int *s) { + s[2]; // <- expected warning here +#pragma clang unsafe_buffer_usage begin + return s[1]; +#pragma clang unsafe_buffer_usage end +} + +//--- pch_2.h +int d(int *s) { + s[2]; // <- expected warning here +#pragma clang unsafe_buffer_usage begin + return s[1]; +#pragma clang unsafe_buffer_usage end +} + + +//--- main.cpp +#include "textual_1.h" +// expected-warning@textual_1.h:2{{unsafe buffer access}} \ + expected-note@textual_1.h:2{{pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions}} +// expected-warning@textual_2.h:2{{unsafe buffer access}} \ + expected-note@textual_2.h:2{{pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions}} +// expected-warning@pch_1.h:4{{unsafe buffer access}} \ + expected-note@pch_1.h:4{{pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions}} +// expected-warning@pch_2.h:2{{unsafe buffer access}} \ + expected-note@pch_2.h:2{{pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions}} +int main() { + int s[] = {1, 2, 3}; + return a(s) + b(s) + c(s) + d(s); +} diff --git a/clang/test/PCH/unsafe-buffer-usage-pragma-pch-cross-files-2.cpp b/clang/test/PCH/unsafe-buffer-usage-pragma-pch-cross-files-2.cpp new file mode 100644 index 00000000000000..66b3f13c712efa --- /dev/null +++ b/clang/test/PCH/unsafe-buffer-usage-pragma-pch-cross-files-2.cpp @@ -0,0 +1,25 @@ +// RUN: rm -rf %t +// RUN: mkdir -p %t +// RUN: split-file %s %t + +// RUN: %clang_cc1 -Wno-unused-value -std=c++20 -emit-pch -o %t/header.pch %t/header.h -x c++ +// RUN: %clang_cc1 -Wno-unused-value -Wunsafe-buffer-usage -std=c++20 -include-pch %t/header.pch -verify %t/main.cpp + +//--- header.h +int foo(int *p) { + return p[5]; // This will be warned +} + +#pragma clang unsafe_buffer_usage begin // The opt-out region spans over two files of one TU +#include "header-2.h" + + +//--- header-2.h +int bar(int *p) { + return p[5]; // suppressed by the cross-file opt-out region +} +#pragma clang unsafe_buffer_usage end + +//--- main.cpp +// expected-warning@header.h:2 {{unsafe buffer access}} +// expected-note@header.h:2 {{pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions}} diff --git a/clang/test/PCH/unsafe-buffer-usage-pragma-pch-cross-files.cpp b/clang/test/PCH/unsafe-buffer-usage-pragma-pch-cross-files.cpp new file mode 100644 index 00000000000000..ace9d0e4fe9efd --- /dev/null +++ b/clang/test/PCH/unsafe-buffer-usage-pragma-pch-cross-files.cpp @@ -0,0 +1,29 @@ +// RUN: rm -rf %t +// RUN: mkdir -p %t +// RUN: split-file %s %t +// +// RUN: %clang_cc1 -Wno-unused-value -std=c++20 -emit-pch -o %t/header.pch %t/header.h -x c++ +// RUN: %clang_cc1 -Wno-unused-value -Wunsafe-buffer-usage -std=c++20 -include-pch %t/header.pch -verify %t/main.cpp + +//--- header.h +int foo(int *p) { + return p[5]; // This will be warned +} + +#pragma clang unsafe_buffer_usage begin +#include "header-2.h" +#pragma clang unsafe_buffer_usage end + +//--- header-2.h +// Included by the PCH in the traditional way. The include directive +// in the PCH is enclosed in an opt-out region, so unsafe operations +// here is suppressed. + +int bar(int *p) { + return p[5]; +} + + +//--- main.cpp +// expected-warning@header.h:2 {{unsafe buffer access}} +// expected-note@header.h:2 {{pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions}} diff --git a/clang/test/SemaCXX/warn-unsafe-buffer-usage-pragma-pch.cpp b/clang/test/SemaCXX/warn-unsafe-buffer-usage-pragma-pch.cpp new file mode 100644 index 00000000000000..abd3f0ffe95652 --- /dev/null +++ b/clang/test/SemaCXX/warn-unsafe-buffer-usage-pragma-pch.cpp @@ -0,0 +1,27 @@ +// The original example from https://github.com/llvm/llvm-project/issues/90501 + +// Test without PCH +// RUN: %clang_cc1 -Wno-unused-value -Wunsafe-buffer-usage -std=c++20 -include %s -verify %s +// Test with PCH +// RUN: %clang_cc1 -Wno-unused-value -std=c++20 -emit-pch -o %t %s +// RUN: %clang_cc1 -Wno-unused-value -Wunsafe-buffer-usage -std=c++20 -include-pch %t -verify %s + +#ifndef A_H +#define A_H + +int a(int *s) { + s[2]; // <- expected warning here +#pragma clang unsafe_buffer_usage begin + return s[1]; +#pragma clang unsafe_buffer_usage end +} + +#else +// expected-warning@-7{{unsafe buffer access}} +// expected-note@-8{{pass -fsafe-buffer-usage-suggestions to receive code hardening suggestions}} +int main() { + int s[] = {1, 2, 3}; + return a(s); +} + +#endif