Skip to content

Commit 1ede7b4

Browse files
committed
[clang][modules] Avoid serializing all diag mappings in non-deterministic order
When writing a pcm, we serialize diagnostic mappings in order to accurately reproduce the diagnostic environment inside any headers from that module. However, the diagnostic state mapping table contains entries for every diagnostic ID ever accessed, while we only want to serialize the ones that are actually modified from their default value. Futher, we need to serialize them in a deterministic order. rdar://111477511 Differential Revision: https://reviews.llvm.org/D154016
1 parent 92fbb60 commit 1ede7b4

File tree

5 files changed

+141
-22
lines changed

5 files changed

+141
-22
lines changed

clang/include/clang/Basic/DiagnosticIDs.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,10 @@ class DiagnosticMapping {
159159
Result.Severity = Bits & 0x7;
160160
return Result;
161161
}
162+
163+
bool operator==(DiagnosticMapping Other) const {
164+
return serialize() == Other.serialize();
165+
}
162166
};
163167

164168
/// Used for handling and querying diagnostic IDs.
@@ -208,6 +212,9 @@ class DiagnosticIDs : public RefCountedBase<DiagnosticIDs> {
208212
/// default.
209213
static bool isDefaultMappingAsError(unsigned DiagID);
210214

215+
/// Get the default mapping for this diagnostic.
216+
static DiagnosticMapping getDefaultMapping(unsigned DiagID);
217+
211218
/// Determine whether the given built-in diagnostic ID is a Note.
212219
static bool isBuiltinNote(unsigned DiagID);
213220

clang/lib/Basic/Diagnostic.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,18 @@ void DiagnosticsEngine::ReportDelayed() {
160160
Report(ID) << DelayedDiagArg1 << DelayedDiagArg2 << DelayedDiagArg3;
161161
}
162162

163+
DiagnosticMapping &
164+
DiagnosticsEngine::DiagState::getOrAddMapping(diag::kind Diag) {
165+
std::pair<iterator, bool> Result =
166+
DiagMap.insert(std::make_pair(Diag, DiagnosticMapping()));
167+
168+
// Initialize the entry if we added it.
169+
if (Result.second)
170+
Result.first->second = DiagnosticIDs::getDefaultMapping(Diag);
171+
172+
return Result.first->second;
173+
}
174+
163175
void DiagnosticsEngine::DiagStateMap::appendFirst(DiagState *State) {
164176
assert(Files.empty() && "not first");
165177
FirstDiagState = CurDiagState = State;

clang/lib/Basic/DiagnosticIDs.cpp

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ CATEGORY(REFACTORING, ANALYSIS)
256256
return Found;
257257
}
258258

259-
static DiagnosticMapping GetDefaultDiagMapping(unsigned DiagID) {
259+
DiagnosticMapping DiagnosticIDs::getDefaultMapping(unsigned DiagID) {
260260
DiagnosticMapping Info = DiagnosticMapping::Make(
261261
diag::Severity::Fatal, /*IsUser=*/false, /*IsPragma=*/false);
262262

@@ -293,21 +293,6 @@ namespace {
293293
};
294294
}
295295

296-
// Unfortunately, the split between DiagnosticIDs and Diagnostic is not
297-
// particularly clean, but for now we just implement this method here so we can
298-
// access GetDefaultDiagMapping.
299-
DiagnosticMapping &
300-
DiagnosticsEngine::DiagState::getOrAddMapping(diag::kind Diag) {
301-
std::pair<iterator, bool> Result =
302-
DiagMap.insert(std::make_pair(Diag, DiagnosticMapping()));
303-
304-
// Initialize the entry if we added it.
305-
if (Result.second)
306-
Result.first->second = GetDefaultDiagMapping(Diag);
307-
308-
return Result.first->second;
309-
}
310-
311296
static const StaticDiagCategoryRec CategoryNameTable[] = {
312297
#define GET_CATEGORY_TABLE
313298
#define CATEGORY(X, ENUM) { X, STR_SIZE(X, uint8_t) },
@@ -449,15 +434,15 @@ bool DiagnosticIDs::isBuiltinExtensionDiag(unsigned DiagID,
449434
return false;
450435

451436
EnabledByDefault =
452-
GetDefaultDiagMapping(DiagID).getSeverity() != diag::Severity::Ignored;
437+
getDefaultMapping(DiagID).getSeverity() != diag::Severity::Ignored;
453438
return true;
454439
}
455440

456441
bool DiagnosticIDs::isDefaultMappingAsError(unsigned DiagID) {
457442
if (DiagID >= diag::DIAG_UPPER_LIMIT)
458443
return false;
459444

460-
return GetDefaultDiagMapping(DiagID).getSeverity() >= diag::Severity::Error;
445+
return getDefaultMapping(DiagID).getSeverity() >= diag::Severity::Error;
461446
}
462447

463448
/// getDescription - Given a diagnostic ID, return a description of the

clang/lib/Serialization/ASTWriter.cpp

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2997,20 +2997,41 @@ void ASTWriter::WritePragmaDiagnosticMappings(const DiagnosticsEngine &Diag,
29972997
assert(Flags == EncodeDiagStateFlags(State) &&
29982998
"diag state flags vary in single AST file");
29992999

3000+
// If we ever serialize non-pragma mappings outside the initial state, the
3001+
// code below will need to consider more than getDefaultMapping.
3002+
assert(!IncludeNonPragmaStates ||
3003+
State == Diag.DiagStatesByLoc.FirstDiagState);
3004+
30003005
unsigned &DiagStateID = DiagStateIDMap[State];
30013006
Record.push_back(DiagStateID);
30023007

30033008
if (DiagStateID == 0) {
30043009
DiagStateID = ++CurrID;
3010+
SmallVector<std::pair<unsigned, DiagnosticMapping>> Mappings;
30053011

30063012
// Add a placeholder for the number of mappings.
30073013
auto SizeIdx = Record.size();
30083014
Record.emplace_back();
30093015
for (const auto &I : *State) {
3010-
if (I.second.isPragma() || IncludeNonPragmaStates) {
3011-
Record.push_back(I.first);
3012-
Record.push_back(I.second.serialize());
3013-
}
3016+
// Maybe skip non-pragmas.
3017+
if (!I.second.isPragma() && !IncludeNonPragmaStates)
3018+
continue;
3019+
// Skip default mappings. We have a mapping for every diagnostic ever
3020+
// emitted, regardless of whether it was customized.
3021+
if (!I.second.isPragma() &&
3022+
I.second == DiagnosticIDs::getDefaultMapping(I.first))
3023+
continue;
3024+
Mappings.push_back(I);
3025+
}
3026+
3027+
// Sort by diag::kind for deterministic output.
3028+
llvm::sort(Mappings, [](const auto &LHS, const auto &RHS) {
3029+
return LHS.first < RHS.first;
3030+
});
3031+
3032+
for (const auto &I : Mappings) {
3033+
Record.push_back(I.first);
3034+
Record.push_back(I.second.serialize());
30143035
}
30153036
// Update the placeholder.
30163037
Record[SizeIdx] = (Record.size() - SizeIdx) / 2;

clang/test/Modules/diag-mappings.c

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Test that diagnostic mappings are emitted only when needed and in order of
2+
// diagnostic ID rather than non-deterministically. This test passes 3
3+
// -W options and expects exactly 3 mappings to be emitted in the pcm. The -W
4+
// options are chosen to be far apart in ID (see DiagnosticIDs.h) so we can
5+
// check they are ordered. We also intentionally trigger several other warnings
6+
// inside the module and ensure they do not show up in the pcm as mappings.
7+
8+
// RUN: rm -rf %t
9+
// RUN: split-file %s %t
10+
11+
// RUN: %clang_cc1 -fmodules -fimplicit-module-maps \
12+
// RUN: -fmodules-cache-path=%t/cache -triple x86_64-apple-macosx10.11.0 \
13+
// RUN: %t/main.m -fdisable-module-hash \
14+
// RUN: -Werror=stack-protector -Werror=empty-translation-unit -Werror=float-equal
15+
16+
// RUN: mv %t/cache/A.pcm %t/A1.pcm
17+
18+
// RUN: llvm-bcanalyzer --dump --disable-histogram %t/A1.pcm | FileCheck %s
19+
20+
// CHECK: <DIAG_PRAGMA_MAPPINGS
21+
22+
// == Initial mappings
23+
// Number of mappings = 3
24+
// CHECK-SAME: op2=3
25+
// Common diag id is < 1000 (see DiagnosticIDs.h)
26+
// CHECK-SAME: op3=[[STACK_PROT:[0-9][0-9]?[0-9]?]] op4=
27+
// Parse diag id is somewhere in 1000..2999, leaving room for changes
28+
// CHECK-SAME: op5=[[EMPTY_TU:[12][0-9][0-9][0-9]]] op6=
29+
// Sema diag id is > 2000
30+
// CHECK-SAME: op7=[[FLOAT_EQ:[2-9][0-9][0-9][0-9]]] op8=
31+
32+
// == Pragmas:
33+
// Each pragma creates a mapping table; and each copies the previous table. The
34+
// initial mappings are copied as well, but are not serialized since they have
35+
// isPragma=false.
36+
37+
// == ignored "-Wfloat-equal"
38+
// CHECK-SAME: op{{[0-9]+}}=1
39+
// CHECK-SAME: op{{[0-9]+}}=[[FLOAT_EQ]] op{{[0-9]+}}=
40+
41+
// == ignored "-Wstack-protector"
42+
// CHECK-SAME: op{{[0-9]+}}=2
43+
// CHECK-SAME: op{{[0-9]+}}=[[STACK_PROT]] op{{[0-9]+}}=
44+
// CHECK-SAME: op{{[0-9]+}}=[[FLOAT_EQ]] op{{[0-9]+}}=
45+
46+
// == warning "-Wempty-translation-unit"
47+
// CHECK-SAME: op{{[0-9]+}}=3
48+
// CHECK-SAME: op{{[0-9]+}}=[[STACK_PROT]] op{{[0-9]+}}=
49+
// CHECK-SAME: op{{[0-9]+}}=[[EMPTY_TU]] op{{[0-9]+}}=
50+
// CHECK-SAME: op{{[0-9]+}}=[[FLOAT_EQ]] op{{[0-9]+}}=
51+
52+
// == warning "-Wstack-protector"
53+
// CHECK-SAME: op{{[0-9]+}}=3
54+
// CHECK-SAME: op{{[0-9]+}}=[[STACK_PROT]] op{{[0-9]+}}=
55+
// CHECK-SAME: op{{[0-9]+}}=[[EMPTY_TU]] op{{[0-9]+}}=
56+
// CHECK-SAME: op{{[0-9]+}}=[[FLOAT_EQ]] op{{[0-9]+}}=
57+
58+
// RUN: %clang_cc1 -fmodules -fimplicit-module-maps \
59+
// RUN: -fmodules-cache-path=%t/cache -triple x86_64-apple-macosx10.11.0 \
60+
// RUN: %t/main.m -fdisable-module-hash \
61+
// RUN: -Werror=stack-protector -Werror=empty-translation-unit -Werror=float-equal
62+
63+
// RUN: diff %t/cache/A.pcm %t/A1.pcm
64+
65+
//--- module.modulemap
66+
module A { header "a.h" }
67+
68+
//--- a.h
69+
// Lex warning
70+
#warning "w"
71+
72+
static inline void f() {
73+
// Parse warning
74+
;
75+
}
76+
77+
#pragma clang diagnostic push
78+
#pragma clang diagnostic ignored "-Wfloat-equal"
79+
#pragma clang diagnostic ignored "-Wstack-protector"
80+
81+
static inline void g() {
82+
// Sema warning
83+
int x;
84+
}
85+
86+
#pragma clang diagnostic push
87+
#pragma clang diagnostic warning "-Wempty-translation-unit"
88+
#pragma clang diagnostic warning "-Wstack-protector"
89+
90+
#pragma clang diagnostic pop
91+
#pragma clang diagnostic pop
92+
93+
//--- main.m
94+
#import "a.h"

0 commit comments

Comments
 (0)