PlistDiagnostics.cpp revision 218887
1218887Sdim//===--- PlistDiagnostics.cpp - Plist Diagnostics for Paths -----*- C++ -*-===//
2218887Sdim//
3218887Sdim//                     The LLVM Compiler Infrastructure
4218887Sdim//
5218887Sdim// This file is distributed under the University of Illinois Open Source
6218887Sdim// License. See LICENSE.TXT for details.
7218887Sdim//
8218887Sdim//===----------------------------------------------------------------------===//
9218887Sdim//
10218887Sdim//  This file defines the PlistDiagnostics object.
11218887Sdim//
12218887Sdim//===----------------------------------------------------------------------===//
13218887Sdim
14243830Sdim#include "clang/StaticAnalyzer/Core/PathDiagnosticClients.h"
15276479Sdim#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h"
16218887Sdim#include "clang/Basic/SourceManager.h"
17243830Sdim#include "clang/Basic/FileManager.h"
18218887Sdim#include "clang/Lex/Preprocessor.h"
19249423Sdim#include "llvm/Support/raw_ostream.h"
20296417Sdim#include "llvm/Support/Casting.h"
21249423Sdim#include "llvm/ADT/DenseMap.h"
22218887Sdim#include "llvm/ADT/SmallVector.h"
23249423Sdimusing namespace clang;
24218887Sdimusing namespace ento;
25218887Sdimusing llvm::cast;
26276479Sdim
27218887Sdimtypedef llvm::DenseMap<FileID, unsigned> FIDMap;
28218887Sdim
29226633Sdimnamespace clang {
30218887Sdim  class Preprocessor;
31218887Sdim}
32234353Sdim
33218887Sdimnamespace {
34249423Sdimstruct CompareDiagnostics {
35249423Sdim  // Compare if 'X' is "<" than 'Y'.
36249423Sdim  bool operator()(const PathDiagnostic *X, const PathDiagnostic *Y) const {
37239462Sdim    // First compare by location
38218887Sdim    const FullSourceLoc &XLoc = X->getLocation().asLocation();
39288943Sdim    const FullSourceLoc &YLoc = Y->getLocation().asLocation();
40218887Sdim    if (XLoc < YLoc)
41234353Sdim      return true;
42276479Sdim    if (XLoc != YLoc)
43276479Sdim      return false;
44280031Sdim
45218887Sdim    // Next, compare by bug type.
46218887Sdim    llvm::StringRef XBugType = X->getBugType();
47218887Sdim    llvm::StringRef YBugType = Y->getBugType();
48276479Sdim    if (XBugType < YBugType)
49276479Sdim      return true;
50276479Sdim    if (XBugType != YBugType)
51276479Sdim      return false;
52276479Sdim
53234353Sdim    // Next, compare by bug description.
54234353Sdim    llvm::StringRef XDesc = X->getDescription();
55218887Sdim    llvm::StringRef YDesc = Y->getDescription();
56218887Sdim    if (XDesc < YDesc)
57218887Sdim      return true;
58249423Sdim    if (XDesc != YDesc)
59249423Sdim      return false;
60218887Sdim
61239462Sdim    // FIXME: Further refine by comparing PathDiagnosticPieces?
62249423Sdim    return false;
63249423Sdim  }
64234353Sdim};
65218887Sdim}
66249423Sdim
67249423Sdimnamespace {
68239462Sdim  class PlistDiagnostics : public PathDiagnosticClient {
69239462Sdim    std::vector<const PathDiagnostic*> BatchedDiags;
70249423Sdim    const std::string OutputFile;
71249423Sdim    const LangOptions &LangOpts;
72218887Sdim    llvm::OwningPtr<PathDiagnosticClient> SubPD;
73218887Sdim    bool flushed;
74249423Sdim  public:
75249423Sdim    PlistDiagnostics(const std::string& prefix, const LangOptions &LangOpts,
76239462Sdim                     PathDiagnosticClient *subPD);
77239462Sdim
78249423Sdim    ~PlistDiagnostics() { FlushDiagnostics(NULL); }
79249423Sdim
80234353Sdim    void FlushDiagnostics(llvm::SmallVectorImpl<std::string> *FilesMade);
81234353Sdim
82226633Sdim    void HandlePathDiagnostic(const PathDiagnostic* D);
83218887Sdim
84218887Sdim    virtual llvm::StringRef getName() const {
85218887Sdim      return "PlistDiagnostics";
86218887Sdim    }
87218887Sdim
88218887Sdim    PathGenerationScheme getGenerationScheme() const;
89218887Sdim    bool supportsLogicalOpControlFlow() const { return true; }
90218887Sdim    bool supportsAllBlockEdges() const { return true; }
91218887Sdim    virtual bool useVerboseDescription() const { return false; }
92218887Sdim  };
93218887Sdim} // end anonymous namespace
94218887Sdim
95218887SdimPlistDiagnostics::PlistDiagnostics(const std::string& output,
96218887Sdim                                   const LangOptions &LO,
97218887Sdim                                   PathDiagnosticClient *subPD)
98218887Sdim  : OutputFile(output), LangOpts(LO), SubPD(subPD), flushed(false) {}
99218887Sdim
100218887SdimPathDiagnosticClient*
101218887Sdimento::createPlistDiagnosticClient(const std::string& s, const Preprocessor &PP,
102218887Sdim                                  PathDiagnosticClient *subPD) {
103239462Sdim  return new PlistDiagnostics(s, PP.getLangOptions(), subPD);
104239462Sdim}
105239462Sdim
106239462SdimPathDiagnosticClient::PathGenerationScheme
107218887SdimPlistDiagnostics::getGenerationScheme() const {
108288943Sdim  if (const PathDiagnosticClient *PD = SubPD.get())
109288943Sdim    return PD->getGenerationScheme();
110288943Sdim
111276479Sdim  return Extensive;
112239462Sdim}
113218887Sdim
114288943Sdimstatic void AddFID(FIDMap &FIDs, llvm::SmallVectorImpl<FileID> &V,
115288943Sdim                   const SourceManager* SM, SourceLocation L) {
116276479Sdim
117239462Sdim  FileID FID = SM->getFileID(SM->getInstantiationLoc(L));
118218887Sdim  FIDMap::iterator I = FIDs.find(FID);
119218887Sdim  if (I != FIDs.end()) return;
120218887Sdim  FIDs[FID] = V.size();
121218887Sdim  V.push_back(FID);
122218887Sdim}
123218887Sdim
124218887Sdimstatic unsigned GetFID(const FIDMap& FIDs, const SourceManager &SM,
125218887Sdim                       SourceLocation L) {
126309124Sdim  FileID FID = SM.getFileID(SM.getInstantiationLoc(L));
127218887Sdim  FIDMap::const_iterator I = FIDs.find(FID);
128218887Sdim  assert(I != FIDs.end());
129218887Sdim  return I->second;
130218887Sdim}
131218887Sdim
132218887Sdimstatic llvm::raw_ostream& Indent(llvm::raw_ostream& o, const unsigned indent) {
133218887Sdim  for (unsigned i = 0; i < indent; ++i) o << ' ';
134218887Sdim  return o;
135218887Sdim}
136226633Sdim
137218887Sdimstatic void EmitLocation(llvm::raw_ostream& o, const SourceManager &SM,
138218887Sdim                         const LangOptions &LangOpts,
139218887Sdim                         SourceLocation L, const FIDMap &FM,
140234353Sdim                         unsigned indent, bool extend = false) {
141261991Sdim
142261991Sdim  FullSourceLoc Loc(SM.getInstantiationLoc(L), const_cast<SourceManager&>(SM));
143218887Sdim
144218887Sdim  // Add in the length of the token, so that we cover multi-char tokens.
145218887Sdim  unsigned offset =
146218887Sdim    extend ? Lexer::MeasureTokenLength(Loc, SM, LangOpts) - 1 : 0;
147218887Sdim
148218887Sdim  Indent(o, indent) << "<dict>\n";
149261991Sdim  Indent(o, indent) << " <key>line</key><integer>"
150261991Sdim                    << Loc.getInstantiationLineNumber() << "</integer>\n";
151261991Sdim  Indent(o, indent) << " <key>col</key><integer>"
152261991Sdim                    << Loc.getInstantiationColumnNumber() + offset << "</integer>\n";
153218887Sdim  Indent(o, indent) << " <key>file</key><integer>"
154218887Sdim                    << GetFID(FM, SM, Loc) << "</integer>\n";
155218887Sdim  Indent(o, indent) << "</dict>\n";
156218887Sdim}
157288943Sdim
158218887Sdimstatic void EmitLocation(llvm::raw_ostream& o, const SourceManager &SM,
159218887Sdim                         const LangOptions &LangOpts,
160239462Sdim                         const PathDiagnosticLocation &L, const FIDMap& FM,
161218887Sdim                         unsigned indent, bool extend = false) {
162239462Sdim  EmitLocation(o, SM, LangOpts, L.asLocation(), FM, indent, extend);
163218887Sdim}
164218887Sdim
165218887Sdimstatic void EmitRange(llvm::raw_ostream& o, const SourceManager &SM,
166288943Sdim                      const LangOptions &LangOpts,
167288943Sdim                      PathDiagnosticRange R, const FIDMap &FM,
168288943Sdim                      unsigned indent) {
169288943Sdim  Indent(o, indent) << "<array>\n";
170218887Sdim  EmitLocation(o, SM, LangOpts, R.getBegin(), FM, indent+1);
171218887Sdim  EmitLocation(o, SM, LangOpts, R.getEnd(), FM, indent+1, !R.isPoint);
172218887Sdim  Indent(o, indent) << "</array>\n";
173296417Sdim}
174234353Sdim
175276479Sdimstatic llvm::raw_ostream& EmitString(llvm::raw_ostream& o,
176276479Sdim                                     const std::string& s) {
177218887Sdim  o << "<string>";
178218887Sdim  for (std::string::const_iterator I=s.begin(), E=s.end(); I!=E; ++I) {
179218887Sdim    char c = *I;
180218887Sdim    switch (c) {
181218887Sdim    default:   o << c; break;
182218887Sdim    case '&':  o << "&amp;"; break;
183218887Sdim    case '<':  o << "&lt;"; break;
184218887Sdim    case '>':  o << "&gt;"; break;
185218887Sdim    case '\'': o << "&apos;"; break;
186218887Sdim    case '\"': o << "&quot;"; break;
187243830Sdim    }
188218887Sdim  }
189296417Sdim  o << "</string>";
190218887Sdim  return o;
191218887Sdim}
192218887Sdim
193218887Sdimstatic void ReportControlFlow(llvm::raw_ostream& o,
194218887Sdim                              const PathDiagnosticControlFlowPiece& P,
195234353Sdim                              const FIDMap& FM,
196234353Sdim                              const SourceManager &SM,
197234353Sdim                              const LangOptions &LangOpts,
198234353Sdim                              unsigned indent) {
199234353Sdim
200234353Sdim  Indent(o, indent) << "<dict>\n";
201261991Sdim  ++indent;
202261991Sdim
203234353Sdim  Indent(o, indent) << "<key>kind</key><string>control</string>\n";
204234353Sdim
205234353Sdim  // Emit edges.
206234353Sdim  Indent(o, indent) << "<key>edges</key>\n";
207234353Sdim  ++indent;
208234353Sdim  Indent(o, indent) << "<array>\n";
209234353Sdim  ++indent;
210296417Sdim  for (PathDiagnosticControlFlowPiece::const_iterator I=P.begin(), E=P.end();
211314564Sdim       I!=E; ++I) {
212261991Sdim    Indent(o, indent) << "<dict>\n";
213261991Sdim    ++indent;
214234353Sdim    Indent(o, indent) << "<key>start</key>\n";
215296417Sdim    EmitRange(o, SM, LangOpts, I->getStart().asRange(), FM, indent+1);
216234353Sdim    Indent(o, indent) << "<key>end</key>\n";
217296417Sdim    EmitRange(o, SM, LangOpts, I->getEnd().asRange(), FM, indent+1);
218314564Sdim    --indent;
219234353Sdim    Indent(o, indent) << "</dict>\n";
220234353Sdim  }
221296417Sdim  --indent;
222234353Sdim  Indent(o, indent) << "</array>\n";
223234353Sdim  --indent;
224251662Sdim
225251662Sdim  // Output any helper text.
226296417Sdim  const std::string& s = P.getString();
227314564Sdim  if (!s.empty()) {
228234353Sdim    Indent(o, indent) << "<key>alternate</key>";
229234353Sdim    EmitString(o, s) << '\n';
230234353Sdim  }
231226633Sdim
232218887Sdim  --indent;
233218887Sdim  Indent(o, indent) << "</dict>\n";
234218887Sdim}
235234353Sdim
236234353Sdimstatic void ReportEvent(llvm::raw_ostream& o, const PathDiagnosticPiece& P,
237218887Sdim                        const FIDMap& FM,
238234353Sdim                        const SourceManager &SM,
239218887Sdim                        const LangOptions &LangOpts,
240234353Sdim                        unsigned indent) {
241218887Sdim
242218887Sdim  Indent(o, indent) << "<dict>\n";
243218887Sdim  ++indent;
244226633Sdim
245218887Sdim  Indent(o, indent) << "<key>kind</key><string>event</string>\n";
246218887Sdim
247234353Sdim  // Output the location.
248234353Sdim  FullSourceLoc L = P.getLocation().asLocation();
249218887Sdim
250234353Sdim  Indent(o, indent) << "<key>location</key>\n";
251234353Sdim  EmitLocation(o, SM, LangOpts, L, FM, indent);
252234353Sdim
253234353Sdim  // Output the ranges (if any).
254234353Sdim  PathDiagnosticPiece::range_iterator RI = P.ranges_begin(),
255234353Sdim  RE = P.ranges_end();
256261991Sdim
257261991Sdim  if (RI != RE) {
258218887Sdim    Indent(o, indent) << "<key>ranges</key>\n";
259234353Sdim    Indent(o, indent) << "<array>\n";
260234353Sdim    ++indent;
261234353Sdim    for (; RI != RE; ++RI)
262234353Sdim      EmitRange(o, SM, LangOpts, *RI, FM, indent+1);
263234353Sdim    --indent;
264234353Sdim    Indent(o, indent) << "</array>\n";
265234353Sdim  }
266234353Sdim
267234353Sdim  // Output the text.
268234353Sdim  assert(!P.getString().empty());
269234353Sdim  Indent(o, indent) << "<key>extended_message</key>\n";
270261991Sdim  Indent(o, indent);
271234353Sdim  EmitString(o, P.getString()) << '\n';
272234353Sdim
273234353Sdim  // Output the short text.
274234353Sdim  // FIXME: Really use a short string.
275234353Sdim  Indent(o, indent) << "<key>message</key>\n";
276314564Sdim  EmitString(o, P.getString()) << '\n';
277314564Sdim
278314564Sdim  // Finish up.
279218887Sdim  --indent;
280218887Sdim  Indent(o, indent); o << "</dict>\n";
281218887Sdim}
282234353Sdim
283234353Sdimstatic void ReportMacro(llvm::raw_ostream& o,
284239462Sdim                        const PathDiagnosticMacroPiece& P,
285218887Sdim                        const FIDMap& FM, const SourceManager &SM,
286218887Sdim                        const LangOptions &LangOpts,
287218887Sdim                        unsigned indent) {
288226633Sdim
289276479Sdim  for (PathDiagnosticMacroPiece::const_iterator I=P.begin(), E=P.end();
290218887Sdim       I!=E; ++I) {
291234353Sdim
292296417Sdim    switch ((*I)->getKind()) {
293218887Sdim    default:
294314564Sdim      break;
295314564Sdim    case PathDiagnosticPiece::Event:
296314564Sdim      ReportEvent(o, cast<PathDiagnosticEventPiece>(**I), FM, SM, LangOpts,
297314564Sdim                  indent);
298314564Sdim      break;
299314564Sdim    case PathDiagnosticPiece::Macro:
300314564Sdim      ReportMacro(o, cast<PathDiagnosticMacroPiece>(**I), FM, SM, LangOpts,
301314564Sdim                  indent);
302296417Sdim      break;
303314564Sdim    }
304218887Sdim  }
305249423Sdim}
306234353Sdim
307218887Sdimstatic void ReportDiag(llvm::raw_ostream& o, const PathDiagnosticPiece& P,
308234353Sdim                       const FIDMap& FM, const SourceManager &SM,
309314564Sdim                       const LangOptions &LangOpts) {
310261991Sdim
311314564Sdim  unsigned indent = 4;
312314564Sdim
313314564Sdim  switch (P.getKind()) {
314234353Sdim  case PathDiagnosticPiece::ControlFlow:
315314564Sdim    ReportControlFlow(o, cast<PathDiagnosticControlFlowPiece>(P), FM, SM,
316314564Sdim                      LangOpts, indent);
317314564Sdim    break;
318314564Sdim  case PathDiagnosticPiece::Event:
319239462Sdim    ReportEvent(o, cast<PathDiagnosticEventPiece>(P), FM, SM, LangOpts,
320314564Sdim                indent);
321314564Sdim    break;
322314564Sdim  case PathDiagnosticPiece::Macro:
323314564Sdim    ReportMacro(o, cast<PathDiagnosticMacroPiece>(P), FM, SM, LangOpts,
324314564Sdim                indent);
325314564Sdim    break;
326314564Sdim  }
327234353Sdim}
328218887Sdim
329218887Sdimvoid PlistDiagnostics::HandlePathDiagnostic(const PathDiagnostic* D) {
330218887Sdim  if (!D)
331218887Sdim    return;
332218887Sdim
333280031Sdim  if (D->empty()) {
334280031Sdim    delete D;
335280031Sdim    return;
336280031Sdim  }
337218887Sdim
338218887Sdim  // We need to flatten the locations (convert Stmt* to locations) because
339218887Sdim  // the referenced statements may be freed by the time the diagnostics
340276479Sdim  // are emitted.
341218887Sdim  const_cast<PathDiagnostic*>(D)->flattenLocations();
342218887Sdim  BatchedDiags.push_back(D);
343243830Sdim}
344218887Sdim
345218887Sdimvoid PlistDiagnostics::FlushDiagnostics(llvm::SmallVectorImpl<std::string>
346243830Sdim                                        *FilesMade) {
347243830Sdim
348243830Sdim  if (flushed)
349243830Sdim    return;
350218887Sdim
351218887Sdim  flushed = true;
352276479Sdim
353276479Sdim  // Sort the diagnostics so that they are always emitted in a deterministic
354218887Sdim  // order.
355218887Sdim  if (!BatchedDiags.empty())
356218887Sdim    std::sort(BatchedDiags.begin(), BatchedDiags.end(), CompareDiagnostics());
357218887Sdim
358218887Sdim  // Build up a set of FIDs that we use by scanning the locations and
359234353Sdim  // ranges of the diagnostics.
360234353Sdim  FIDMap FM;
361218887Sdim  llvm::SmallVector<FileID, 10> Fids;
362218887Sdim  const SourceManager* SM = 0;
363218887Sdim
364218887Sdim  if (!BatchedDiags.empty())
365218887Sdim    SM = &(*BatchedDiags.begin())->begin()->getLocation().getManager();
366218887Sdim
367218887Sdim  for (std::vector<const PathDiagnostic*>::iterator DI = BatchedDiags.begin(),
368218887Sdim       DE = BatchedDiags.end(); DI != DE; ++DI) {
369296417Sdim
370234353Sdim    const PathDiagnostic *D = *DI;
371234353Sdim
372218887Sdim    for (PathDiagnostic::const_iterator I=D->begin(), E=D->end(); I!=E; ++I) {
373218887Sdim      AddFID(FM, Fids, SM, I->getLocation().asLocation());
374218887Sdim
375218887Sdim      for (PathDiagnosticPiece::range_iterator RI=I->ranges_begin(),
376218887Sdim           RE=I->ranges_end(); RI!=RE; ++RI) {
377243830Sdim        AddFID(FM, Fids, SM, RI->getBegin());
378218887Sdim        AddFID(FM, Fids, SM, RI->getEnd());
379218887Sdim      }
380218887Sdim    }
381218887Sdim  }
382288943Sdim
383288943Sdim  // Open the file.
384296417Sdim  std::string ErrMsg;
385296417Sdim  llvm::raw_fd_ostream o(OutputFile.c_str(), ErrMsg);
386296417Sdim  if (!ErrMsg.empty()) {
387296417Sdim    llvm::errs() << "warning: could not creat file: " << OutputFile << '\n';
388296417Sdim    return;
389296417Sdim  }
390296417Sdim
391296417Sdim  // Write the plist header.
392296417Sdim  o << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
393296417Sdim  "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" "
394296417Sdim  "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
395296417Sdim  "<plist version=\"1.0\">\n";
396296417Sdim
397234353Sdim  // Write the root object: a <dict> containing...
398234353Sdim  //  - "files", an <array> mapping from FIDs to file names
399234353Sdim  //  - "diagnostics", an <array> containing the path diagnostics
400234353Sdim  o << "<dict>\n"
401234353Sdim       " <key>files</key>\n"
402234353Sdim       " <array>\n";
403234353Sdim
404234353Sdim  for (llvm::SmallVectorImpl<FileID>::iterator I=Fids.begin(), E=Fids.end();
405234353Sdim       I!=E; ++I) {
406234353Sdim    o << "  ";
407234353Sdim    EmitString(o, SM->getFileEntryForID(*I)->getName()) << '\n';
408234353Sdim  }
409234353Sdim
410234353Sdim  o << " </array>\n"
411234353Sdim       " <key>diagnostics</key>\n"
412234353Sdim       " <array>\n";
413234353Sdim
414234353Sdim  for (std::vector<const PathDiagnostic*>::iterator DI=BatchedDiags.begin(),
415234353Sdim       DE = BatchedDiags.end(); DI!=DE; ++DI) {
416234353Sdim
417234353Sdim    o << "  <dict>\n"
418234353Sdim         "   <key>path</key>\n";
419234353Sdim
420234353Sdim    const PathDiagnostic *D = *DI;
421234353Sdim    // Create an owning smart pointer for 'D' just so that we auto-free it
422234353Sdim    // when we exit this method.
423234353Sdim    llvm::OwningPtr<PathDiagnostic> OwnedD(const_cast<PathDiagnostic*>(D));
424234353Sdim
425234353Sdim    o << "   <array>\n";
426239462Sdim
427239462Sdim    for (PathDiagnostic::const_iterator I=D->begin(), E=D->end(); I != E; ++I)
428239462Sdim      ReportDiag(o, *I, FM, *SM, LangOpts);
429239462Sdim
430296417Sdim    o << "   </array>\n";
431249423Sdim
432249423Sdim    // Output the bug type and bug category.
433249423Sdim    o << "   <key>description</key>";
434249423Sdim    EmitString(o, D->getDescription()) << '\n';
435249423Sdim    o << "   <key>category</key>";
436249423Sdim    EmitString(o, D->getCategory()) << '\n';
437249423Sdim    o << "   <key>type</key>";
438249423Sdim    EmitString(o, D->getBugType()) << '\n';
439296417Sdim
440296417Sdim    // Output the location of the bug.
441249423Sdim    o << "  <key>location</key>\n";
442249423Sdim    EmitLocation(o, *SM, LangOpts, D->getLocation(), FM, 2);
443249423Sdim
444249423Sdim    // Output the diagnostic to the sub-diagnostic client, if any.
445249423Sdim    if (SubPD) {
446296417Sdim      SubPD->HandlePathDiagnostic(OwnedD.take());
447249423Sdim      llvm::SmallVector<std::string, 1> SubFilesMade;
448249423Sdim      SubPD->FlushDiagnostics(SubFilesMade);
449249423Sdim
450249423Sdim      if (!SubFilesMade.empty()) {
451239462Sdim        o << "  <key>" << SubPD->getName() << "_files</key>\n";
452234353Sdim        o << "  <array>\n";
453234353Sdim        for (size_t i = 0, n = SubFilesMade.size(); i < n ; ++i)
454218887Sdim          o << "   <string>" << SubFilesMade[i] << "</string>\n";
455218887Sdim        o << "  </array>\n";
456218887Sdim      }
457288943Sdim    }
458218887Sdim
459218887Sdim    // Close up the entry.
460239462Sdim    o << "  </dict>\n";
461239462Sdim  }
462243830Sdim
463243830Sdim  o << " </array>\n";
464243830Sdim
465243830Sdim  // Finish.
466243830Sdim  o << "</dict>\n</plist>";
467243830Sdim
468243830Sdim  if (FilesMade)
469243830Sdim    FilesMade->push_back(OutputFile);
470243830Sdim
471243830Sdim  BatchedDiags.clear();
472243830Sdim}
473243830Sdim