Skip to content

Commit e470919

Browse files
authored
Merge pull request swiftlang#33720 from nkcsgexi/add-allow-list-abi-checker
ABI checker: add a mechansim for specifying allowed ABI breakages
2 parents 1b928b5 + 55e7778 commit e470919

File tree

7 files changed

+174
-28
lines changed

7 files changed

+174
-28
lines changed

include/swift/AST/DiagnosticsModuleDiffer.def

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ ERROR(param_ownership_change,none,"%0 has %1 changing from %2 to %3", (StringRef
7474

7575
ERROR(type_witness_change,none,"%0 has type witness type for %1 changing from %2 to %3", (StringRef, StringRef, StringRef, StringRef))
7676

77-
ERROR(decl_new_witness_table_entry,none,"%0 now requires %select{|no}1 new witness table entry", (StringRef, bool))
77+
ERROR(decl_new_witness_table_entry,none,"%0 now requires%select{| no}1 new witness table entry", (StringRef, bool))
7878

7979
ERROR(new_decl_without_intro,none,"%0 is a new API without @available attribute", (StringRef))
8080

@@ -88,5 +88,7 @@ ERROR(not_inheriting_convenience_inits,none,"%0 no longer inherits convenience i
8888

8989
ERROR(enum_case_added,none,"%0 has been added as a new enum case", (StringRef))
9090

91+
WARNING(cannot_read_allowlist,none,"cannot read breakage allowlist at '%0'", (StringRef))
92+
9193
#define UNDEFINE_DIAGNOSTIC_MACROS
9294
#include "DefineDiagnosticMacros.h"

include/swift/ClangImporter/ClangImporter.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,7 @@ class ClangImporter final : public ClangModuleLoader {
378378
/// construction of the replica.
379379
bool dumpPrecompiledModule(StringRef modulePath, StringRef outputPath);
380380

381+
bool runPreprocessor(StringRef inputPath, StringRef outputPath);
381382
const clang::Module *getClangOwningModule(ClangNode Node) const;
382383
bool hasTypedef(const clang::Decl *typeDecl) const;
383384

lib/ClangImporter/ClangImporter.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1597,6 +1597,30 @@ ClangImporter::emitBridgingPCH(StringRef headerPath,
15971597
return false;
15981598
}
15991599

1600+
bool ClangImporter::runPreprocessor(StringRef inputPath, StringRef outputPath) {
1601+
auto emitInstance = cloneCompilerInstanceForPrecompiling();
1602+
auto &invocation = emitInstance->getInvocation();
1603+
auto LangOpts = invocation.getLangOpts();
1604+
auto &OutputOpts = invocation.getPreprocessorOutputOpts();
1605+
OutputOpts.ShowCPP = 1;
1606+
OutputOpts.ShowComments = 0;
1607+
OutputOpts.ShowLineMarkers = 0;
1608+
OutputOpts.ShowMacros = 0;
1609+
OutputOpts.ShowMacroComments = 0;
1610+
auto language = getLanguageFromOptions(LangOpts);
1611+
auto inputFile = clang::FrontendInputFile(inputPath, language);
1612+
1613+
auto &FrontendOpts = invocation.getFrontendOpts();
1614+
FrontendOpts.Inputs = {inputFile};
1615+
FrontendOpts.OutputFile = outputPath.str();
1616+
FrontendOpts.ProgramAction = clang::frontend::PrintPreprocessedInput;
1617+
1618+
auto action = wrapActionForIndexingIfEnabled(
1619+
FrontendOpts, std::make_unique<clang::PrintPreprocessedAction>());
1620+
emitInstance->ExecuteAction(*action);
1621+
return emitInstance->getDiagnostics().hasErrorOccurred();
1622+
}
1623+
16001624
bool ClangImporter::emitPrecompiledModule(StringRef moduleMapPath,
16011625
StringRef moduleName,
16021626
StringRef outputPath) {
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// REQUIRES: VENDOR=apple
2+
3+
// RUN: %empty-directory(%t)
4+
// RUN: %empty-directory(%t.mod1)
5+
// RUN: %empty-directory(%t.mod2)
6+
// RUN: %empty-directory(%t.sdk)
7+
// RUN: %empty-directory(%t.module-cache)
8+
// RUN: %empty-directory(%t.baseline/ABI)
9+
10+
// RUN: echo "public func foo() {}" > %t.mod1/Foo.swift
11+
// RUN: echo "public func bar() {}" > %t.mod2/Foo.swift
12+
13+
// RUN: echo "Foo: Func foo() has been removed" > %t/incomplete-allowlist.txt
14+
// RUN: echo "Foo: Func foo() has been removed" > %t/complete-allowlist.txt
15+
// RUN: echo "Foo: Func bar() is a new API without @available attribute" >> %t/complete-allowlist.txt
16+
17+
// RUN: %target-swift-frontend -disable-objc-attr-requires-foundation-module -emit-module -o %t.mod1/Foo.swiftmodule %t.mod1/Foo.swift -parse-as-library -enable-library-evolution -emit-module-source-info -emit-module-source-info-path %t.mod1/Foo.swiftsourceinfo -emit-module-interface-path %t.mod1/Foo.swiftinterface -module-name Foo -swift-version 5
18+
19+
// RUN: %target-swift-frontend -disable-objc-attr-requires-foundation-module -emit-module -o %t.mod2/Foo.swiftmodule %t.mod2/Foo.swift -parse-as-library -enable-library-evolution -emit-module-source-info -emit-module-source-info-path %t.mod2/Foo.swiftsourceinfo -emit-module-interface-path %t.mod2/Foo.swiftinterface -module-name Foo -swift-version 5
20+
21+
// RUN: %api-digester -dump-sdk -module Foo -output-dir %t.baseline -module-cache-path %t.module-cache %clang-importer-sdk-nosource -I %t.mod1 -abi -use-interface-for-module Foo
22+
23+
// RUN: %api-digester -diagnose-sdk -print-module -baseline-dir %t.baseline -module Foo -I %t.mod2 -module-cache-path %t.module-cache %clang-importer-sdk-nosource -abi -breakage-allowlist-path %t/complete-allowlist.txt -o %t/expected-diags.txt
24+
25+
// RUN: not %api-digester -diagnose-sdk -print-module -baseline-dir %t.baseline -module Foo -I %t.mod2 -module-cache-path %t.module-cache %clang-importer-sdk-nosource -abi -compiler-style-diags
26+
27+
// RUN: not %api-digester -diagnose-sdk -print-module -baseline-dir %t.baseline -module Foo -I %t.mod2 -module-cache-path %t.module-cache %clang-importer-sdk-nosource -abi -compiler-style-diags -breakage-allowlist-path %t/incomplete-allowlist.txt
28+
29+
// RUN: %api-digester -diagnose-sdk -print-module -baseline-dir %t.baseline -module Foo -I %t.mod2 -module-cache-path %t.module-cache %clang-importer-sdk-nosource -abi -compiler-style-diags -breakage-allowlist-path %t/complete-allowlist.txt
30+
31+
// RUN: not %api-digester -diagnose-sdk -print-module -baseline-dir %t.baseline -module Foo -I %t.mod2 -module-cache-path %t.module-cache %clang-importer-sdk-nosource -abi -serialize-diagnostics-path %t/serialized-diag.dia
32+
// RUN: ls %t/serialized-diag.dia
33+
34+
// RUN: not %api-digester -diagnose-sdk -print-module -baseline-dir %t.baseline -module Foo -I %t.mod2 -module-cache-path %t.module-cache %clang-importer-sdk-nosource -abi -serialize-diagnostics-path %t/serialized-diag.dia -breakage-allowlist-path %t/incomplete-allowlist.txt
35+
// RUN: ls %t/serialized-diag.dia
36+
37+
// RUN: %api-digester -diagnose-sdk -print-module -baseline-dir %t.baseline -module Foo -I %t.mod2 -module-cache-path %t.module-cache %clang-importer-sdk-nosource -abi -serialize-diagnostics-path %t/serialized-diag.dia -breakage-allowlist-path %t/complete-allowlist.txt
38+
// RUN: ls %t/serialized-diag.dia

tools/swift-api-digester/ModuleDiagsConsumer.cpp

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ static StringRef getCategoryName(uint32_t ID) {
7878
case LocalDiagID::not_inheriting_convenience_inits:
7979
return "/* Class Inheritance Change */";
8080
default:
81-
return StringRef();
81+
return "/* Others */";
8282
}
8383
}
8484
}
@@ -122,3 +122,28 @@ swift::ide::api::ModuleDifferDiagsConsumer::~ModuleDifferDiagsConsumer() {
122122
}
123123
}
124124
}
125+
126+
bool swift::ide::api::
127+
FilteringDiagnosticConsumer::shouldProceed(const DiagnosticInfo &Info) {
128+
if (allowedBreakages->empty()) {
129+
return true;
130+
}
131+
llvm::SmallString<256> Text;
132+
{
133+
llvm::raw_svector_ostream Out(Text);
134+
DiagnosticEngine::formatDiagnosticText(Out, Info.FormatString,
135+
Info.FormatArgs);
136+
}
137+
return allowedBreakages->count(Text.str()) == 0;
138+
}
139+
140+
void swift::ide::api::
141+
FilteringDiagnosticConsumer::handleDiagnostic(SourceManager &SM,
142+
const DiagnosticInfo &Info) {
143+
if (shouldProceed(Info)) {
144+
if (Info.Kind == DiagnosticKind::Error) {
145+
HasError = true;
146+
}
147+
subConsumer->handleDiagnostic(SM, Info);
148+
}
149+
}

tools/swift-api-digester/ModuleDiagsConsumer.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,30 @@ class ModuleDifferDiagsConsumer: public PrintingDiagnosticConsumer {
4141
~ModuleDifferDiagsConsumer();
4242
void handleDiagnostic(SourceManager &SM, const DiagnosticInfo &Info) override;
4343
};
44+
45+
class FilteringDiagnosticConsumer: public DiagnosticConsumer {
46+
bool HasError = false;
47+
std::unique_ptr<DiagnosticConsumer> subConsumer;
48+
std::unique_ptr<llvm::StringSet<>> allowedBreakages;
49+
bool shouldProceed(const DiagnosticInfo &Info);
50+
public:
51+
FilteringDiagnosticConsumer(std::unique_ptr<DiagnosticConsumer> subConsumer,
52+
std::unique_ptr<llvm::StringSet<>> allowedBreakages):
53+
subConsumer(std::move(subConsumer)),
54+
allowedBreakages(std::move(allowedBreakages)) {}
55+
~FilteringDiagnosticConsumer() = default;
56+
57+
bool finishProcessing() override { return subConsumer->finishProcessing(); }
58+
bool hasError() const { return HasError; }
59+
void flush() override { subConsumer->flush(); }
60+
61+
void informDriverOfIncompleteBatchModeCompilation() override {
62+
subConsumer->informDriverOfIncompleteBatchModeCompilation();
63+
}
64+
65+
void handleDiagnostic(SourceManager &SM,
66+
const DiagnosticInfo &Info) override;
67+
};
4468
}
4569
}
4670
}

tools/swift-api-digester/swift-api-digester.cpp

Lines changed: 58 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,11 @@ SerializedDiagPath("serialize-diagnostics-path",
264264
llvm::cl::desc("Serialize diagnostics to a path"),
265265
llvm::cl::cat(Category));
266266

267+
static llvm::cl::opt<std::string>
268+
BreakageAllowlistPath("breakage-allowlist-path",
269+
llvm::cl::desc("An allowlist of breakages to not complain about"),
270+
llvm::cl::cat(Category));
271+
267272
} // namespace options
268273

269274
namespace {
@@ -2320,6 +2325,50 @@ createDiagConsumer(llvm::raw_ostream &OS, bool &FailOnError) {
23202325
}
23212326
}
23222327

2328+
static int readFileLineByLine(StringRef Path, llvm::StringSet<> &Lines) {
2329+
auto FileBufOrErr = llvm::MemoryBuffer::getFile(Path);
2330+
if (!FileBufOrErr) {
2331+
llvm::errs() << "error opening file '" << Path << "': "
2332+
<< FileBufOrErr.getError().message() << '\n';
2333+
return 1;
2334+
}
2335+
2336+
StringRef BufferText = FileBufOrErr.get()->getBuffer();
2337+
while (!BufferText.empty()) {
2338+
StringRef Line;
2339+
std::tie(Line, BufferText) = BufferText.split('\n');
2340+
Line = Line.trim();
2341+
if (Line.empty())
2342+
continue;
2343+
if (Line.startswith("// ")) // comment.
2344+
continue;
2345+
Lines.insert(Line);
2346+
}
2347+
return 0;
2348+
}
2349+
2350+
static bool readBreakageAllowlist(SDKContext &Ctx, llvm::StringSet<> &lines) {
2351+
if (options::BreakageAllowlistPath.empty())
2352+
return 0;
2353+
CompilerInstance instance;
2354+
CompilerInvocation invok;
2355+
invok.setModuleName("ForClangImporter");
2356+
if (instance.setup(invok))
2357+
return 1;
2358+
ClangImporterOptions impOpts;
2359+
auto importer = ClangImporter::create(instance.getASTContext(), impOpts);
2360+
SmallString<128> preprocessedFilePath;
2361+
if (auto error = llvm::sys::fs::createTemporaryFile(
2362+
"breakage-allowlist-", "txt", preprocessedFilePath)) {
2363+
return 1;
2364+
}
2365+
if (importer->runPreprocessor(options::BreakageAllowlistPath,
2366+
preprocessedFilePath.str())) {
2367+
return 1;
2368+
}
2369+
return readFileLineByLine(preprocessedFilePath, lines);
2370+
}
2371+
23232372
static int diagnoseModuleChange(SDKContext &Ctx, SDKNodeRoot *LeftModule,
23242373
SDKNodeRoot *RightModule, StringRef OutputPath,
23252374
llvm::StringSet<> ProtocolReqAllowlist) {
@@ -2337,9 +2386,14 @@ static int diagnoseModuleChange(SDKContext &Ctx, SDKNodeRoot *LeftModule,
23372386
OS = FileOS.get();
23382387
}
23392388
bool FailOnError;
2340-
std::unique_ptr<DiagnosticConsumer> pConsumer =
2341-
createDiagConsumer(*OS, FailOnError);
2342-
2389+
auto allowedBreakages = std::make_unique<llvm::StringSet<>>();
2390+
if (readBreakageAllowlist(Ctx, *allowedBreakages)) {
2391+
Ctx.getDiags().diagnose(SourceLoc(), diag::cannot_read_allowlist,
2392+
options::BreakageAllowlistPath);
2393+
}
2394+
auto pConsumer = std::make_unique<FilteringDiagnosticConsumer>(
2395+
createDiagConsumer(*OS, FailOnError), std::move(allowedBreakages));
2396+
SWIFT_DEFER { pConsumer->finishProcessing(); };
23432397
Ctx.addDiagConsumer(*pConsumer);
23442398
Ctx.setCommonVersion(std::min(LeftModule->getJsonFormatVersion(),
23452399
RightModule->getJsonFormatVersion()));
@@ -2352,7 +2406,7 @@ static int diagnoseModuleChange(SDKContext &Ctx, SDKNodeRoot *LeftModule,
23522406
// Find member hoist changes to help refine diagnostics.
23532407
findTypeMemberDiffs(LeftModule, RightModule, Ctx.getTypeMemberDiffs());
23542408
DiagnosisEmitter::diagnosis(LeftModule, RightModule, Ctx);
2355-
return FailOnError && Ctx.getDiags().hadAnyError() ? 1 : 0;
2409+
return FailOnError && pConsumer->hasError() ? 1 : 0;
23562410
}
23572411

23582412
static int diagnoseModuleChange(StringRef LeftPath, StringRef RightPath,
@@ -2493,28 +2547,6 @@ static int generateMigrationScript(StringRef LeftPath, StringRef RightPath,
24932547
return 0;
24942548
}
24952549

2496-
static int readFileLineByLine(StringRef Path, llvm::StringSet<> &Lines) {
2497-
auto FileBufOrErr = llvm::MemoryBuffer::getFile(Path);
2498-
if (!FileBufOrErr) {
2499-
llvm::errs() << "error opening file '" << Path << "': "
2500-
<< FileBufOrErr.getError().message() << '\n';
2501-
return 1;
2502-
}
2503-
2504-
StringRef BufferText = FileBufOrErr.get()->getBuffer();
2505-
while (!BufferText.empty()) {
2506-
StringRef Line;
2507-
std::tie(Line, BufferText) = BufferText.split('\n');
2508-
Line = Line.trim();
2509-
if (Line.empty())
2510-
continue;
2511-
if (Line.startswith("// ")) // comment.
2512-
continue;
2513-
Lines.insert(Line);
2514-
}
2515-
return 0;
2516-
}
2517-
25182550
// This function isn't referenced outside its translation unit, but it
25192551
// can't use the "static" keyword because its address is used for
25202552
// getMainExecutable (since some platforms don't support taking the

0 commit comments

Comments
 (0)