1 //===-- CXLoadedDiagnostic.cpp - Handling of persisent diags ----*- C++ -*-===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 //
10 // Implements handling of persisent diagnostics.
11 //
12 //===----------------------------------------------------------------------===//
13 
14 #include "CXLoadedDiagnostic.h"
15 #include "CXString.h"
16 #include "clang/Basic/Diagnostic.h"
17 #include "clang/Basic/FileManager.h"
18 #include "clang/Basic/LLVM.h"
19 #include "clang/Frontend/SerializedDiagnosticReader.h"
20 #include "clang/Frontend/SerializedDiagnostics.h"
21 #include "llvm/ADT/Optional.h"
22 #include "llvm/ADT/STLExtras.h"
23 #include "llvm/ADT/StringRef.h"
24 #include "llvm/ADT/Twine.h"
25 #include "llvm/Bitcode/BitstreamReader.h"
26 #include "llvm/Support/ErrorHandling.h"
27 #include "llvm/Support/MemoryBuffer.h"
28 
29 using namespace clang;
30 
31 //===----------------------------------------------------------------------===//
32 // Extend CXDiagnosticSetImpl which contains strings for diagnostics.
33 //===----------------------------------------------------------------------===//
34 
35 typedef llvm::DenseMap<unsigned, const char *> Strings;
36 
37 namespace {
38 class CXLoadedDiagnosticSetImpl : public CXDiagnosticSetImpl {
39 public:
CXLoadedDiagnosticSetImpl()40   CXLoadedDiagnosticSetImpl() : CXDiagnosticSetImpl(true), FakeFiles(FO) {}
~CXLoadedDiagnosticSetImpl()41   ~CXLoadedDiagnosticSetImpl() override {}
42 
43   llvm::BumpPtrAllocator Alloc;
44   Strings Categories;
45   Strings WarningFlags;
46   Strings FileNames;
47 
48   FileSystemOptions FO;
49   FileManager FakeFiles;
50   llvm::DenseMap<unsigned, const FileEntry *> Files;
51 
52   /// \brief Copy the string into our own allocator.
copyString(StringRef Blob)53   const char *copyString(StringRef Blob) {
54     char *mem = Alloc.Allocate<char>(Blob.size() + 1);
55     memcpy(mem, Blob.data(), Blob.size());
56     mem[Blob.size()] = '\0';
57     return mem;
58   }
59 };
60 } // end anonymous namespace
61 
62 //===----------------------------------------------------------------------===//
63 // Cleanup.
64 //===----------------------------------------------------------------------===//
65 
~CXLoadedDiagnostic()66 CXLoadedDiagnostic::~CXLoadedDiagnostic() {}
67 
68 //===----------------------------------------------------------------------===//
69 // Public CXLoadedDiagnostic methods.
70 //===----------------------------------------------------------------------===//
71 
getSeverity() const72 CXDiagnosticSeverity CXLoadedDiagnostic::getSeverity() const {
73   // FIXME: Fail more softly if the diagnostic level is unknown?
74   auto severityAsLevel = static_cast<serialized_diags::Level>(severity);
75   assert(severity == static_cast<unsigned>(severityAsLevel) &&
76          "unknown serialized diagnostic level");
77 
78   switch (severityAsLevel) {
79 #define CASE(X) case serialized_diags::X: return CXDiagnostic_##X;
80   CASE(Ignored)
81   CASE(Note)
82   CASE(Warning)
83   CASE(Error)
84   CASE(Fatal)
85 #undef CASE
86   // The 'Remark' level isn't represented in the stable API.
87   case serialized_diags::Remark: return CXDiagnostic_Warning;
88   }
89 
90   llvm_unreachable("Invalid diagnostic level");
91 }
92 
makeLocation(const CXLoadedDiagnostic::Location * DLoc)93 static CXSourceLocation makeLocation(const CXLoadedDiagnostic::Location *DLoc) {
94   // The lowest bit of ptr_data[0] is always set to 1 to indicate this
95   // is a persistent diagnostic.
96   uintptr_t V = (uintptr_t) DLoc;
97   V |= 0x1;
98   CXSourceLocation Loc = { {  (void*) V, nullptr }, 0 };
99   return Loc;
100 }
101 
getLocation() const102 CXSourceLocation CXLoadedDiagnostic::getLocation() const {
103   // The lowest bit of ptr_data[0] is always set to 1 to indicate this
104   // is a persistent diagnostic.
105   return makeLocation(&DiagLoc);
106 }
107 
getSpelling() const108 CXString CXLoadedDiagnostic::getSpelling() const {
109   return cxstring::createRef(Spelling);
110 }
111 
getDiagnosticOption(CXString * Disable) const112 CXString CXLoadedDiagnostic::getDiagnosticOption(CXString *Disable) const {
113   if (DiagOption.empty())
114     return cxstring::createEmpty();
115 
116   // FIXME: possibly refactor with logic in CXStoredDiagnostic.
117   if (Disable)
118     *Disable = cxstring::createDup((Twine("-Wno-") + DiagOption).str());
119   return cxstring::createDup((Twine("-W") + DiagOption).str());
120 }
121 
getCategory() const122 unsigned CXLoadedDiagnostic::getCategory() const {
123   return category;
124 }
125 
getCategoryText() const126 CXString CXLoadedDiagnostic::getCategoryText() const {
127   return cxstring::createDup(CategoryText);
128 }
129 
getNumRanges() const130 unsigned CXLoadedDiagnostic::getNumRanges() const {
131   return Ranges.size();
132 }
133 
getRange(unsigned Range) const134 CXSourceRange CXLoadedDiagnostic::getRange(unsigned Range) const {
135   assert(Range < Ranges.size());
136   return Ranges[Range];
137 }
138 
getNumFixIts() const139 unsigned CXLoadedDiagnostic::getNumFixIts() const {
140   return FixIts.size();
141 }
142 
getFixIt(unsigned FixIt,CXSourceRange * ReplacementRange) const143 CXString CXLoadedDiagnostic::getFixIt(unsigned FixIt,
144                                       CXSourceRange *ReplacementRange) const {
145   assert(FixIt < FixIts.size());
146   if (ReplacementRange)
147     *ReplacementRange = FixIts[FixIt].first;
148   return cxstring::createRef(FixIts[FixIt].second);
149 }
150 
decodeLocation(CXSourceLocation location,CXFile * file,unsigned int * line,unsigned int * column,unsigned int * offset)151 void CXLoadedDiagnostic::decodeLocation(CXSourceLocation location,
152                                         CXFile *file,
153                                         unsigned int *line,
154                                         unsigned int *column,
155                                         unsigned int *offset) {
156 
157 
158   // CXSourceLocation consists of the following fields:
159   //
160   //   void *ptr_data[2];
161   //   unsigned int_data;
162   //
163   // The lowest bit of ptr_data[0] is always set to 1 to indicate this
164   // is a persistent diagnostic.
165   //
166   // For now, do the unoptimized approach and store the data in a side
167   // data structure.  We can optimize this case later.
168 
169   uintptr_t V = (uintptr_t) location.ptr_data[0];
170   assert((V & 0x1) == 1);
171   V &= ~(uintptr_t)1;
172 
173   const Location &Loc = *((Location*)V);
174 
175   if (file)
176     *file = Loc.file;
177   if (line)
178     *line = Loc.line;
179   if (column)
180     *column = Loc.column;
181   if (offset)
182     *offset = Loc.offset;
183 }
184 
185 //===----------------------------------------------------------------------===//
186 // Deserialize diagnostics.
187 //===----------------------------------------------------------------------===//
188 
189 namespace {
190 class DiagLoader : serialized_diags::SerializedDiagnosticReader {
191   enum CXLoadDiag_Error *error;
192   CXString *errorString;
193   std::unique_ptr<CXLoadedDiagnosticSetImpl> TopDiags;
194   SmallVector<std::unique_ptr<CXLoadedDiagnostic>, 8> CurrentDiags;
195 
reportBad(enum CXLoadDiag_Error code,llvm::StringRef err)196   std::error_code reportBad(enum CXLoadDiag_Error code, llvm::StringRef err) {
197     if (error)
198       *error = code;
199     if (errorString)
200       *errorString = cxstring::createDup(err);
201     return serialized_diags::SDError::HandlerFailed;
202   }
203 
reportInvalidFile(llvm::StringRef err)204   std::error_code reportInvalidFile(llvm::StringRef err) {
205     return reportBad(CXLoadDiag_InvalidFile, err);
206   }
207 
208   std::error_code readRange(const serialized_diags::Location &SDStart,
209                             const serialized_diags::Location &SDEnd,
210                             CXSourceRange &SR);
211 
212   std::error_code readLocation(const serialized_diags::Location &SDLoc,
213                                CXLoadedDiagnostic::Location &LoadedLoc);
214 
215 protected:
216   std::error_code visitStartOfDiagnostic() override;
217   std::error_code visitEndOfDiagnostic() override;
218 
219   std::error_code visitCategoryRecord(unsigned ID, StringRef Name) override;
220 
221   std::error_code visitDiagFlagRecord(unsigned ID, StringRef Name) override;
222 
223   std::error_code visitDiagnosticRecord(
224       unsigned Severity, const serialized_diags::Location &Location,
225       unsigned Category, unsigned Flag, StringRef Message) override;
226 
227   std::error_code visitFilenameRecord(unsigned ID, unsigned Size,
228                                       unsigned Timestamp,
229                                       StringRef Name) override;
230 
231   std::error_code visitFixitRecord(const serialized_diags::Location &Start,
232                                    const serialized_diags::Location &End,
233                                    StringRef CodeToInsert) override;
234 
235   std::error_code
236   visitSourceRangeRecord(const serialized_diags::Location &Start,
237                          const serialized_diags::Location &End) override;
238 
239 public:
DiagLoader(enum CXLoadDiag_Error * e,CXString * es)240   DiagLoader(enum CXLoadDiag_Error *e, CXString *es)
241       : SerializedDiagnosticReader(), error(e), errorString(es) {
242     if (error)
243       *error = CXLoadDiag_None;
244     if (errorString)
245       *errorString = cxstring::createEmpty();
246   }
247 
248   CXDiagnosticSet load(const char *file);
249 };
250 } // end anonymous namespace
251 
load(const char * file)252 CXDiagnosticSet DiagLoader::load(const char *file) {
253   TopDiags = llvm::make_unique<CXLoadedDiagnosticSetImpl>();
254 
255   std::error_code EC = readDiagnostics(file);
256   if (EC) {
257     switch (EC.value()) {
258     case static_cast<int>(serialized_diags::SDError::HandlerFailed):
259       // We've already reported the problem.
260       break;
261     case static_cast<int>(serialized_diags::SDError::CouldNotLoad):
262       reportBad(CXLoadDiag_CannotLoad, EC.message());
263       break;
264     default:
265       reportInvalidFile(EC.message());
266       break;
267     }
268     return nullptr;
269   }
270 
271   return (CXDiagnosticSet)TopDiags.release();
272 }
273 
274 std::error_code
readLocation(const serialized_diags::Location & SDLoc,CXLoadedDiagnostic::Location & LoadedLoc)275 DiagLoader::readLocation(const serialized_diags::Location &SDLoc,
276                          CXLoadedDiagnostic::Location &LoadedLoc) {
277   unsigned FileID = SDLoc.FileID;
278   if (FileID == 0)
279     LoadedLoc.file = nullptr;
280   else {
281     LoadedLoc.file = const_cast<FileEntry *>(TopDiags->Files[FileID]);
282     if (!LoadedLoc.file)
283       return reportInvalidFile("Corrupted file entry in source location");
284   }
285   LoadedLoc.line = SDLoc.Line;
286   LoadedLoc.column = SDLoc.Col;
287   LoadedLoc.offset = SDLoc.Offset;
288   return std::error_code();
289 }
290 
291 std::error_code
readRange(const serialized_diags::Location & SDStart,const serialized_diags::Location & SDEnd,CXSourceRange & SR)292 DiagLoader::readRange(const serialized_diags::Location &SDStart,
293                       const serialized_diags::Location &SDEnd,
294                       CXSourceRange &SR) {
295   CXLoadedDiagnostic::Location *Start, *End;
296   Start = TopDiags->Alloc.Allocate<CXLoadedDiagnostic::Location>();
297   End = TopDiags->Alloc.Allocate<CXLoadedDiagnostic::Location>();
298 
299   std::error_code EC;
300   if ((EC = readLocation(SDStart, *Start)))
301     return EC;
302   if ((EC = readLocation(SDEnd, *End)))
303     return EC;
304 
305   CXSourceLocation startLoc = makeLocation(Start);
306   CXSourceLocation endLoc = makeLocation(End);
307   SR = clang_getRange(startLoc, endLoc);
308   return std::error_code();
309 }
310 
visitStartOfDiagnostic()311 std::error_code DiagLoader::visitStartOfDiagnostic() {
312   CurrentDiags.push_back(llvm::make_unique<CXLoadedDiagnostic>());
313   return std::error_code();
314 }
315 
visitEndOfDiagnostic()316 std::error_code DiagLoader::visitEndOfDiagnostic() {
317   auto D = CurrentDiags.pop_back_val();
318   if (CurrentDiags.empty())
319     TopDiags->appendDiagnostic(std::move(D));
320   else
321     CurrentDiags.back()->getChildDiagnostics().appendDiagnostic(std::move(D));
322   return std::error_code();
323 }
324 
visitCategoryRecord(unsigned ID,StringRef Name)325 std::error_code DiagLoader::visitCategoryRecord(unsigned ID, StringRef Name) {
326   // FIXME: Why do we care about long strings?
327   if (Name.size() > 65536)
328     return reportInvalidFile("Out-of-bounds string in category");
329   TopDiags->Categories[ID] = TopDiags->copyString(Name);
330   return std::error_code();
331 }
332 
visitDiagFlagRecord(unsigned ID,StringRef Name)333 std::error_code DiagLoader::visitDiagFlagRecord(unsigned ID, StringRef Name) {
334   // FIXME: Why do we care about long strings?
335   if (Name.size() > 65536)
336     return reportInvalidFile("Out-of-bounds string in warning flag");
337   TopDiags->WarningFlags[ID] = TopDiags->copyString(Name);
338   return std::error_code();
339 }
340 
visitFilenameRecord(unsigned ID,unsigned Size,unsigned Timestamp,StringRef Name)341 std::error_code DiagLoader::visitFilenameRecord(unsigned ID, unsigned Size,
342                                                 unsigned Timestamp,
343                                                 StringRef Name) {
344   // FIXME: Why do we care about long strings?
345   if (Name.size() > 65536)
346     return reportInvalidFile("Out-of-bounds string in filename");
347   TopDiags->FileNames[ID] = TopDiags->copyString(Name);
348   TopDiags->Files[ID] =
349       TopDiags->FakeFiles.getVirtualFile(Name, Size, Timestamp);
350   return std::error_code();
351 }
352 
353 std::error_code
visitSourceRangeRecord(const serialized_diags::Location & Start,const serialized_diags::Location & End)354 DiagLoader::visitSourceRangeRecord(const serialized_diags::Location &Start,
355                                    const serialized_diags::Location &End) {
356   CXSourceRange SR;
357   if (std::error_code EC = readRange(Start, End, SR))
358     return EC;
359   CurrentDiags.back()->Ranges.push_back(SR);
360   return std::error_code();
361 }
362 
363 std::error_code
visitFixitRecord(const serialized_diags::Location & Start,const serialized_diags::Location & End,StringRef CodeToInsert)364 DiagLoader::visitFixitRecord(const serialized_diags::Location &Start,
365                              const serialized_diags::Location &End,
366                              StringRef CodeToInsert) {
367   CXSourceRange SR;
368   if (std::error_code EC = readRange(Start, End, SR))
369     return EC;
370   // FIXME: Why do we care about long strings?
371   if (CodeToInsert.size() > 65536)
372     return reportInvalidFile("Out-of-bounds string in FIXIT");
373   CurrentDiags.back()->FixIts.push_back(
374       std::make_pair(SR, TopDiags->copyString(CodeToInsert)));
375   return std::error_code();
376 }
377 
visitDiagnosticRecord(unsigned Severity,const serialized_diags::Location & Location,unsigned Category,unsigned Flag,StringRef Message)378 std::error_code DiagLoader::visitDiagnosticRecord(
379     unsigned Severity, const serialized_diags::Location &Location,
380     unsigned Category, unsigned Flag, StringRef Message) {
381   CXLoadedDiagnostic &D = *CurrentDiags.back();
382   D.severity = Severity;
383   if (std::error_code EC = readLocation(Location, D.DiagLoc))
384     return EC;
385   D.category = Category;
386   D.DiagOption = Flag ? TopDiags->WarningFlags[Flag] : "";
387   D.CategoryText = Category ? TopDiags->Categories[Category] : "";
388   D.Spelling = TopDiags->copyString(Message);
389   return std::error_code();
390 }
391 
392 extern "C" {
clang_loadDiagnostics(const char * file,enum CXLoadDiag_Error * error,CXString * errorString)393 CXDiagnosticSet clang_loadDiagnostics(const char *file,
394                                       enum CXLoadDiag_Error *error,
395                                       CXString *errorString) {
396   DiagLoader L(error, errorString);
397   return L.load(file);
398 }
399 } // end extern 'C'.
400