blob: df9b62433a02f6074e31f69a0dc4d0243be74fc5 [file] [log] [blame]
Chris Lattner4b009652007-07-25 00:24:17 +00001//===--- TextDiagnosticPrinter.cpp - Diagnostic Printer -------------------===//
2//
3// The LLVM Compiler Infrastructure
4//
Chris Lattner959e5be2007-12-29 19:59:25 +00005// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
Chris Lattner4b009652007-07-25 00:24:17 +00007//
8//===----------------------------------------------------------------------===//
9//
10// This diagnostic client prints out their diagnostic messages.
11//
12//===----------------------------------------------------------------------===//
13
Daniel Dunbar68952de2009-03-02 06:16:29 +000014#include "clang/Frontend/TextDiagnosticPrinter.h"
Chris Lattner4b009652007-07-25 00:24:17 +000015#include "clang/Basic/SourceManager.h"
Chris Lattner4b009652007-07-25 00:24:17 +000016#include "clang/Lex/Lexer.h"
Chris Lattner92a33532008-11-19 06:56:25 +000017#include "llvm/Support/raw_ostream.h"
Chris Lattnerbe8e5a42008-11-19 06:51:40 +000018#include "llvm/ADT/SmallString.h"
Douglas Gregor3bb30002009-02-26 21:00:50 +000019#include <algorithm>
Chris Lattner4b009652007-07-25 00:24:17 +000020using namespace clang;
21
Chris Lattner4b009652007-07-25 00:24:17 +000022void TextDiagnosticPrinter::
Chris Lattner836774b2009-01-27 07:57:44 +000023PrintIncludeStack(SourceLocation Loc, const SourceManager &SM) {
24 if (Loc.isInvalid()) return;
Chris Lattner4b009652007-07-25 00:24:17 +000025
Chris Lattner836774b2009-01-27 07:57:44 +000026 PresumedLoc PLoc = SM.getPresumedLoc(Loc);
Chris Lattner4b009652007-07-25 00:24:17 +000027
28 // Print out the other include frames first.
Chris Lattner836774b2009-01-27 07:57:44 +000029 PrintIncludeStack(PLoc.getIncludeLoc(), SM);
Chris Lattnerfd0739e2009-04-21 03:57:54 +000030
31 if (ShowLocation)
32 OS << "In file included from " << PLoc.getFilename()
33 << ':' << PLoc.getLine() << ":\n";
34 else
35 OS << "In included file:\n";
Chris Lattner4b009652007-07-25 00:24:17 +000036}
37
38/// HighlightRange - Given a SourceRange and a line number, highlight (with ~'s)
39/// any characters in LineNo that intersect the SourceRange.
Ted Kremenekb3ee1932007-12-11 21:27:55 +000040void TextDiagnosticPrinter::HighlightRange(const SourceRange &R,
Chris Lattner836774b2009-01-27 07:57:44 +000041 const SourceManager &SM,
Chris Lattner10aaf532009-01-17 08:45:21 +000042 unsigned LineNo, FileID FID,
Gordon Henriksenf0a835c2008-08-09 19:58:22 +000043 std::string &CaretLine,
Nuno Lopesd0e162c2008-08-05 19:40:20 +000044 const std::string &SourceLine) {
Gordon Henriksenf0a835c2008-08-09 19:58:22 +000045 assert(CaretLine.size() == SourceLine.size() &&
46 "Expect a correspondence between source and caret line!");
Chris Lattner4b009652007-07-25 00:24:17 +000047 if (!R.isValid()) return;
48
Chris Lattner836774b2009-01-27 07:57:44 +000049 SourceLocation Begin = SM.getInstantiationLoc(R.getBegin());
50 SourceLocation End = SM.getInstantiationLoc(R.getEnd());
51
Chris Lattnere357b112009-02-17 05:19:10 +000052 // If the End location and the start location are the same and are a macro
53 // location, then the range was something that came from a macro expansion
54 // or _Pragma. If this is an object-like macro, the best we can do is to
55 // highlight the range. If this is a function-like macro, we'd also like to
56 // highlight the arguments.
57 if (Begin == End && R.getEnd().isMacroID())
58 End = SM.getInstantiationRange(R.getEnd()).second;
59
Chris Lattner2d89c562009-02-04 01:06:56 +000060 unsigned StartLineNo = SM.getInstantiationLineNumber(Begin);
Chris Lattner836774b2009-01-27 07:57:44 +000061 if (StartLineNo > LineNo || SM.getFileID(Begin) != FID)
Chris Lattnera0030d22008-01-12 06:43:35 +000062 return; // No intersection.
Chris Lattner4b009652007-07-25 00:24:17 +000063
Chris Lattner2d89c562009-02-04 01:06:56 +000064 unsigned EndLineNo = SM.getInstantiationLineNumber(End);
Chris Lattner836774b2009-01-27 07:57:44 +000065 if (EndLineNo < LineNo || SM.getFileID(End) != FID)
Chris Lattnera0030d22008-01-12 06:43:35 +000066 return; // No intersection.
Chris Lattner4b009652007-07-25 00:24:17 +000067
68 // Compute the column number of the start.
69 unsigned StartColNo = 0;
70 if (StartLineNo == LineNo) {
Chris Lattnere79fc852009-02-04 00:55:58 +000071 StartColNo = SM.getInstantiationColumnNumber(Begin);
Chris Lattner4b009652007-07-25 00:24:17 +000072 if (StartColNo) --StartColNo; // Zero base the col #.
73 }
74
75 // Pick the first non-whitespace column.
76 while (StartColNo < SourceLine.size() &&
77 (SourceLine[StartColNo] == ' ' || SourceLine[StartColNo] == '\t'))
78 ++StartColNo;
79
80 // Compute the column number of the end.
Gordon Henriksenf0a835c2008-08-09 19:58:22 +000081 unsigned EndColNo = CaretLine.size();
Chris Lattner4b009652007-07-25 00:24:17 +000082 if (EndLineNo == LineNo) {
Chris Lattnere79fc852009-02-04 00:55:58 +000083 EndColNo = SM.getInstantiationColumnNumber(End);
Chris Lattner4b009652007-07-25 00:24:17 +000084 if (EndColNo) {
85 --EndColNo; // Zero base the col #.
86
87 // Add in the length of the token, so that we cover multi-char tokens.
Chris Lattnere1be6022009-04-14 23:22:57 +000088 EndColNo += Lexer::MeasureTokenLength(End, SM, *LangOpts);
Chris Lattner4b009652007-07-25 00:24:17 +000089 } else {
Gordon Henriksenf0a835c2008-08-09 19:58:22 +000090 EndColNo = CaretLine.size();
Chris Lattner4b009652007-07-25 00:24:17 +000091 }
92 }
93
94 // Pick the last non-whitespace column.
Nuno Lopesd0e162c2008-08-05 19:40:20 +000095 if (EndColNo <= SourceLine.size())
96 while (EndColNo-1 &&
97 (SourceLine[EndColNo-1] == ' ' || SourceLine[EndColNo-1] == '\t'))
98 --EndColNo;
99 else
100 EndColNo = SourceLine.size();
Chris Lattner4b009652007-07-25 00:24:17 +0000101
102 // Fill the range with ~'s.
103 assert(StartColNo <= EndColNo && "Invalid range!");
Nuno Lopesd0e162c2008-08-05 19:40:20 +0000104 for (unsigned i = StartColNo; i < EndColNo; ++i)
Gordon Henriksenf0a835c2008-08-09 19:58:22 +0000105 CaretLine[i] = '~';
Chris Lattner4b009652007-07-25 00:24:17 +0000106}
107
Chris Lattnerec52b7d2009-02-20 00:18:51 +0000108void TextDiagnosticPrinter::EmitCaretDiagnostic(SourceLocation Loc,
Chris Lattner3272e922009-02-20 00:25:28 +0000109 SourceRange *Ranges,
Chris Lattnerec52b7d2009-02-20 00:18:51 +0000110 unsigned NumRanges,
Douglas Gregor3bb30002009-02-26 21:00:50 +0000111 SourceManager &SM,
112 const CodeModificationHint *Hints,
113 unsigned NumHints) {
Chris Lattner37f9ad22009-02-17 08:44:50 +0000114 assert(!Loc.isInvalid() && "must have a valid source location here");
115
Chris Lattner3b29a182009-02-17 07:54:55 +0000116 // We always emit diagnostics about the instantiation points, not the spelling
117 // points. This more closely correlates to what the user writes.
Chris Lattner37f9ad22009-02-17 08:44:50 +0000118 if (!Loc.isFileID()) {
Chris Lattner459da5d2009-02-18 18:50:45 +0000119 SourceLocation OneLevelUp = SM.getImmediateInstantiationRange(Loc).first;
Chris Lattnerec52b7d2009-02-20 00:18:51 +0000120 EmitCaretDiagnostic(OneLevelUp, Ranges, NumRanges, SM);
Chris Lattner37f9ad22009-02-17 08:44:50 +0000121
Chris Lattner3272e922009-02-20 00:25:28 +0000122 // Map the location through the macro.
Chris Lattner37f9ad22009-02-17 08:44:50 +0000123 Loc = SM.getInstantiationLoc(SM.getImmediateSpellingLoc(Loc));
Chris Lattner3272e922009-02-20 00:25:28 +0000124
125 // Map the ranges.
126 for (unsigned i = 0; i != NumRanges; ++i) {
127 SourceLocation S = Ranges[i].getBegin(), E = Ranges[i].getEnd();
128 if (S.isMacroID())
129 S = SM.getInstantiationLoc(SM.getImmediateSpellingLoc(S));
130 if (E.isMacroID())
131 E = SM.getInstantiationLoc(SM.getImmediateSpellingLoc(E));
132 Ranges[i] = SourceRange(S, E);
133 }
Chris Lattner37f9ad22009-02-17 08:44:50 +0000134
Chris Lattnerfd0739e2009-04-21 03:57:54 +0000135 if (ShowLocation) {
136 // Emit the file/line/column that this expansion came from.
137 OS << SM.getBufferName(Loc) << ':' << SM.getInstantiationLineNumber(Loc)
138 << ':';
139 if (ShowColumn)
140 OS << SM.getInstantiationColumnNumber(Loc) << ':';
141 OS << ' ';
142 }
143 OS << "note: instantiated from:\n";
Chris Lattner37f9ad22009-02-17 08:44:50 +0000144 }
Chris Lattner34e6c262009-02-17 07:51:53 +0000145
146 // Decompose the location into a FID/Offset pair.
147 std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(Loc);
148 FileID FID = LocInfo.first;
149 unsigned FileOffset = LocInfo.second;
150
151 // Get information about the buffer it points into.
152 std::pair<const char*, const char*> BufferInfo = SM.getBufferData(FID);
153 const char *BufStart = BufferInfo.first;
Chris Lattner34e6c262009-02-17 07:51:53 +0000154
155 unsigned ColNo = SM.getColumnNumber(FID, FileOffset);
Chris Lattnerc1303fb2009-02-17 07:38:37 +0000156
157 // Rewind from the current position to the start of the line.
Chris Lattner34e6c262009-02-17 07:51:53 +0000158 const char *TokPtr = BufStart+FileOffset;
159 const char *LineStart = TokPtr-ColNo+1; // Column # is 1-based.
160
Chris Lattnerc1303fb2009-02-17 07:38:37 +0000161
162 // Compute the line end. Scan forward from the error position to the end of
163 // the line.
Chris Lattner34e6c262009-02-17 07:51:53 +0000164 const char *LineEnd = TokPtr;
Chris Lattnerd9e72412009-03-08 08:11:22 +0000165 while (*LineEnd != '\n' && *LineEnd != '\r' && *LineEnd != '\0')
Chris Lattnerc1303fb2009-02-17 07:38:37 +0000166 ++LineEnd;
167
168 // Copy the line of code into an std::string for ease of manipulation.
169 std::string SourceLine(LineStart, LineEnd);
170
171 // Create a line for the caret that is filled with spaces that is the same
172 // length as the line of source code.
173 std::string CaretLine(LineEnd-LineStart, ' ');
174
175 // Highlight all of the characters covered by Ranges with ~ characters.
Chris Lattnerec52b7d2009-02-20 00:18:51 +0000176 if (NumRanges) {
Chris Lattner34e6c262009-02-17 07:51:53 +0000177 unsigned LineNo = SM.getLineNumber(FID, FileOffset);
178
Chris Lattnerec52b7d2009-02-20 00:18:51 +0000179 for (unsigned i = 0, e = NumRanges; i != e; ++i)
180 HighlightRange(Ranges[i], SM, LineNo, FID, CaretLine, SourceLine);
Chris Lattner34e6c262009-02-17 07:51:53 +0000181 }
Chris Lattnerc1303fb2009-02-17 07:38:37 +0000182
183 // Next, insert the caret itself.
184 if (ColNo-1 < CaretLine.size())
185 CaretLine[ColNo-1] = '^';
186 else
187 CaretLine.push_back('^');
188
189 // Scan the source line, looking for tabs. If we find any, manually expand
190 // them to 8 characters and update the CaretLine to match.
191 for (unsigned i = 0; i != SourceLine.size(); ++i) {
192 if (SourceLine[i] != '\t') continue;
193
194 // Replace this tab with at least one space.
195 SourceLine[i] = ' ';
196
197 // Compute the number of spaces we need to insert.
198 unsigned NumSpaces = ((i+8)&~7) - (i+1);
199 assert(NumSpaces < 8 && "Invalid computation of space amt");
200
201 // Insert spaces into the SourceLine.
202 SourceLine.insert(i+1, NumSpaces, ' ');
203
204 // Insert spaces or ~'s into CaretLine.
205 CaretLine.insert(i+1, NumSpaces, CaretLine[i] == '~' ? '~' : ' ');
206 }
207
208 // Finally, remove any blank spaces from the end of CaretLine.
209 while (CaretLine[CaretLine.size()-1] == ' ')
210 CaretLine.erase(CaretLine.end()-1);
211
Chris Lattner404ba8e2009-04-28 22:33:16 +0000212 // If we are in -fdiagnostics-print-source-range-info mode, we are trying to
213 // produce easily machine parsable output. Add a space before the source line
214 // and the caret to make it trivial to tell the main diagnostic line from what
215 // the user is intended to see.
216 if (PrintRangeInfo) {
217 SourceLine = ' ' + SourceLine;
218 CaretLine = ' ' + CaretLine;
219 }
220
221
Chris Lattnerc1303fb2009-02-17 07:38:37 +0000222 // Emit what we have computed.
223 OS << SourceLine << '\n';
224 OS << CaretLine << '\n';
Douglas Gregor3bb30002009-02-26 21:00:50 +0000225
Chris Lattner041bd732009-04-19 07:44:08 +0000226 if (NumHints && PrintFixItInfo) {
Douglas Gregor3bb30002009-02-26 21:00:50 +0000227 std::string InsertionLine;
Chris Lattner041bd732009-04-19 07:44:08 +0000228 for (const CodeModificationHint *Hint = Hints, *LastHint = Hints + NumHints;
Douglas Gregor3bb30002009-02-26 21:00:50 +0000229 Hint != LastHint; ++Hint) {
230 if (Hint->InsertionLoc.isValid()) {
231 // We have an insertion hint. Determine whether the inserted
232 // code is on the same line as the caret.
233 std::pair<FileID, unsigned> HintLocInfo
Chris Lattner9ccffa72009-03-02 20:58:48 +0000234 = SM.getDecomposedInstantiationLoc(Hint->InsertionLoc);
Douglas Gregor3bb30002009-02-26 21:00:50 +0000235 if (SM.getLineNumber(HintLocInfo.first, HintLocInfo.second) ==
236 SM.getLineNumber(FID, FileOffset)) {
237 // Insert the new code into the line just below the code
238 // that the user wrote.
239 unsigned HintColNo
240 = SM.getColumnNumber(HintLocInfo.first, HintLocInfo.second);
241 unsigned LastColumnModified
242 = HintColNo - 1 + Hint->CodeToInsert.size();
243 if (LastColumnModified > InsertionLine.size())
244 InsertionLine.resize(LastColumnModified, ' ');
245 std::copy(Hint->CodeToInsert.begin(), Hint->CodeToInsert.end(),
246 InsertionLine.begin() + HintColNo - 1);
247 }
248 }
249 }
250
Chris Lattner404ba8e2009-04-28 22:33:16 +0000251 if (!InsertionLine.empty()) {
252 if (PrintRangeInfo)
253 OS << ' ';
Douglas Gregor3bb30002009-02-26 21:00:50 +0000254 OS << InsertionLine << '\n';
Chris Lattner404ba8e2009-04-28 22:33:16 +0000255 }
Douglas Gregor3bb30002009-02-26 21:00:50 +0000256 }
Chris Lattnerc1303fb2009-02-17 07:38:37 +0000257}
258
259
Chris Lattner6948ae62008-11-18 07:04:44 +0000260void TextDiagnosticPrinter::HandleDiagnostic(Diagnostic::Level Level,
261 const DiagnosticInfo &Info) {
Chris Lattner836774b2009-01-27 07:57:44 +0000262 // If the location is specified, print out a file/line/col and include trace
263 // if enabled.
264 if (Info.getLocation().isValid()) {
Ted Kremenekdd62ea62009-01-28 20:47:47 +0000265 const SourceManager &SM = Info.getLocation().getManager();
Chris Lattner836774b2009-01-27 07:57:44 +0000266 PresumedLoc PLoc = SM.getPresumedLoc(Info.getLocation());
267 unsigned LineNo = PLoc.getLine();
Chris Lattner4b009652007-07-25 00:24:17 +0000268
269 // First, if this diagnostic is not in the main file, print out the
270 // "included from" lines.
Chris Lattner836774b2009-01-27 07:57:44 +0000271 if (LastWarningLoc != PLoc.getIncludeLoc()) {
272 LastWarningLoc = PLoc.getIncludeLoc();
273 PrintIncludeStack(LastWarningLoc, SM);
Chris Lattner4b009652007-07-25 00:24:17 +0000274 }
275
Chris Lattner836774b2009-01-27 07:57:44 +0000276 // Compute the column number.
Chris Lattner68c1e192009-01-30 17:41:53 +0000277 if (ShowLocation) {
278 OS << PLoc.getFilename() << ':' << LineNo << ':';
Chris Lattnerf0b28562009-02-17 07:34:34 +0000279 if (ShowColumn)
280 if (unsigned ColNo = PLoc.getColumn())
281 OS << ColNo << ':';
Chris Lattner695a4f52009-03-13 01:08:23 +0000282
283 if (PrintRangeInfo && Info.getNumRanges()) {
284 FileID CaretFileID =
285 SM.getFileID(SM.getInstantiationLoc(Info.getLocation()));
286 bool PrintedRange = false;
287
288 for (unsigned i = 0, e = Info.getNumRanges(); i != e; ++i) {
Chris Lattner94780632009-04-19 22:24:10 +0000289 // Ignore invalid ranges.
290 if (!Info.getRange(i).isValid()) continue;
291
Chris Lattner695a4f52009-03-13 01:08:23 +0000292 SourceLocation B = Info.getRange(i).getBegin();
293 SourceLocation E = Info.getRange(i).getEnd();
294 std::pair<FileID, unsigned> BInfo=SM.getDecomposedInstantiationLoc(B);
295
296 E = SM.getInstantiationLoc(E);
297 std::pair<FileID, unsigned> EInfo = SM.getDecomposedLoc(E);
298
299 // If the start or end of the range is in another file, just discard
300 // it.
301 if (BInfo.first != CaretFileID || EInfo.first != CaretFileID)
302 continue;
303
304 // Add in the length of the token, so that we cover multi-char tokens.
Chris Lattnere1be6022009-04-14 23:22:57 +0000305 unsigned TokSize = Lexer::MeasureTokenLength(E, SM, *LangOpts);
Chris Lattner695a4f52009-03-13 01:08:23 +0000306
307 OS << '{' << SM.getLineNumber(BInfo.first, BInfo.second) << ':'
308 << SM.getColumnNumber(BInfo.first, BInfo.second) << '-'
309 << SM.getLineNumber(EInfo.first, EInfo.second) << ':'
310 << (SM.getColumnNumber(EInfo.first, EInfo.second)+TokSize) << '}';
311 PrintedRange = true;
312 }
313
314 if (PrintedRange)
315 OS << ':';
316 }
Chris Lattner68c1e192009-01-30 17:41:53 +0000317 OS << ' ';
318 }
Chris Lattner4b009652007-07-25 00:24:17 +0000319 }
320
321 switch (Level) {
Chris Lattner95cb5502009-02-06 03:57:44 +0000322 case Diagnostic::Ignored: assert(0 && "Invalid diagnostic type");
Nate Begeman01d74272008-04-17 18:06:57 +0000323 case Diagnostic::Note: OS << "note: "; break;
324 case Diagnostic::Warning: OS << "warning: "; break;
325 case Diagnostic::Error: OS << "error: "; break;
Chris Lattner95cb5502009-02-06 03:57:44 +0000326 case Diagnostic::Fatal: OS << "fatal error: "; break;
Chris Lattner4b009652007-07-25 00:24:17 +0000327 }
328
Chris Lattnerbe8e5a42008-11-19 06:51:40 +0000329 llvm::SmallString<100> OutStr;
330 Info.FormatDiagnostic(OutStr);
331 OS.write(OutStr.begin(), OutStr.size());
Chris Lattnera96ec3b2009-04-16 05:44:38 +0000332
333 if (PrintDiagnosticOption)
334 if (const char *Option = Diagnostic::getWarningOptionForDiag(Info.getID()))
Chris Lattner8ee040e2009-04-16 05:52:14 +0000335 OS << " [-W" << Option << ']';
Chris Lattnera96ec3b2009-04-16 05:44:38 +0000336
Chris Lattnerbe8e5a42008-11-19 06:51:40 +0000337 OS << '\n';
Chris Lattner4b009652007-07-25 00:24:17 +0000338
Douglas Gregor56d25a72009-03-10 20:44:00 +0000339 // If caret diagnostics are enabled and we have location, we want to
340 // emit the caret. However, we only do this if the location moved
341 // from the last diagnostic, if the last diagnostic was a note that
342 // was part of a different warning or error diagnostic, or if the
343 // diagnostic has ranges. We don't want to emit the same caret
344 // multiple times if one loc has multiple diagnostics.
Chris Lattner836774b2009-01-27 07:57:44 +0000345 if (CaretDiagnostics && Info.getLocation().isValid() &&
Douglas Gregor3bb30002009-02-26 21:00:50 +0000346 ((LastLoc != Info.getLocation()) || Info.getNumRanges() ||
Douglas Gregor56d25a72009-03-10 20:44:00 +0000347 (LastCaretDiagnosticWasNote && Level != Diagnostic::Note) ||
Douglas Gregor3bb30002009-02-26 21:00:50 +0000348 Info.getNumCodeModificationHints())) {
Steve Naroffb268d2a2008-02-08 22:06:17 +0000349 // Cache the LastLoc, it allows us to omit duplicate source/caret spewage.
Chris Lattner836774b2009-01-27 07:57:44 +0000350 LastLoc = Info.getLocation();
Douglas Gregor56d25a72009-03-10 20:44:00 +0000351 LastCaretDiagnosticWasNote = (Level == Diagnostic::Note);
Chris Lattner836774b2009-01-27 07:57:44 +0000352
Chris Lattnerec52b7d2009-02-20 00:18:51 +0000353 // Get the ranges into a local array we can hack on.
Douglas Gregor3bb30002009-02-26 21:00:50 +0000354 SourceRange Ranges[20];
Chris Lattnerec52b7d2009-02-20 00:18:51 +0000355 unsigned NumRanges = Info.getNumRanges();
Douglas Gregor3bb30002009-02-26 21:00:50 +0000356 assert(NumRanges < 20 && "Out of space");
Chris Lattnerec52b7d2009-02-20 00:18:51 +0000357 for (unsigned i = 0; i != NumRanges; ++i)
358 Ranges[i] = Info.getRange(i);
359
Douglas Gregor3bb30002009-02-26 21:00:50 +0000360 unsigned NumHints = Info.getNumCodeModificationHints();
361 for (unsigned idx = 0; idx < NumHints; ++idx) {
362 const CodeModificationHint &Hint = Info.getCodeModificationHint(idx);
363 if (Hint.RemoveRange.isValid()) {
364 assert(NumRanges < 20 && "Out of space");
365 Ranges[NumRanges++] = Hint.RemoveRange;
366 }
367 }
368
369 EmitCaretDiagnostic(LastLoc, Ranges, NumRanges, LastLoc.getManager(),
370 Info.getCodeModificationHints(),
371 Info.getNumCodeModificationHints());
Chris Lattner4b009652007-07-25 00:24:17 +0000372 }
Chris Lattner92a33532008-11-19 06:56:25 +0000373
374 OS.flush();
Chris Lattner4b009652007-07-25 00:24:17 +0000375}