Thu, 06 Mar 2008 10:25:04 -0800
6668802: javac handles diagnostics for last line badly, if line not terminated by newline
Summary: use CharBuffer.limit(), not the length of the backing array
Reviewed-by: mcimadamore
1 /*
2 * Copyright 1999-2006 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
26 package com.sun.tools.javac.util;
28 import java.io.*;
29 import java.nio.CharBuffer;
30 import java.util.HashMap;
31 import java.util.HashSet;
32 import java.util.Map;
33 import java.util.Set;
34 import javax.tools.DiagnosticListener;
35 import javax.tools.JavaFileObject;
36 import com.sun.tools.javac.code.Source;
37 import com.sun.tools.javac.tree.JCTree;
38 import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
39 import com.sun.tools.javac.util.JCDiagnostic.DiagnosticType;
40 import com.sun.tools.javac.util.JCDiagnostic.SimpleDiagnosticPosition;
41 import static com.sun.tools.javac.util.LayoutCharacters.*;
43 /** A class for error logs. Reports errors and warnings, and
44 * keeps track of error numbers and positions.
45 *
46 * <p><b>This is NOT part of any API supported by Sun Microsystems. If
47 * you write code that depends on this, you do so at your own risk.
48 * This code and its internal interfaces are subject to change or
49 * deletion without notice.</b>
50 */
51 public class Log {
52 /** The context key for the log. */
53 public static final Context.Key<Log> logKey
54 = new Context.Key<Log>();
56 /** The context key for the output PrintWriter. */
57 public static final Context.Key<PrintWriter> outKey =
58 new Context.Key<PrintWriter>();
60 //@Deprecated
61 public final PrintWriter errWriter;
63 //@Deprecated
64 public final PrintWriter warnWriter;
66 //@Deprecated
67 public final PrintWriter noticeWriter;
69 /** The maximum number of errors/warnings that are reported.
70 */
71 public final int MaxErrors;
72 public final int MaxWarnings;
74 /** Whether or not to display the line of source containing a diagnostic.
75 */
76 private final boolean showSourceLine;
78 /** Switch: prompt user on each error.
79 */
80 public boolean promptOnError;
82 /** Switch: emit warning messages.
83 */
84 public boolean emitWarnings;
86 /** Enforce mandatory warnings.
87 */
88 private boolean enforceMandatoryWarnings;
90 /** Print stack trace on errors?
91 */
92 public boolean dumpOnError;
94 /** Print multiple errors for same source locations.
95 */
96 public boolean multipleErrors;
98 /**
99 * Diagnostic listener, if provided through programmatic
100 * interface to javac (JSR 199).
101 */
102 protected DiagnosticListener<? super JavaFileObject> diagListener;
103 /**
104 * Formatter for diagnostics
105 */
106 private DiagnosticFormatter diagFormatter;
108 /**
109 * Factory for diagnostics
110 */
111 private JCDiagnostic.Factory diags;
114 /** Construct a log with given I/O redirections.
115 */
116 @Deprecated
117 protected Log(Context context, PrintWriter errWriter, PrintWriter warnWriter, PrintWriter noticeWriter) {
118 context.put(logKey, this);
119 this.errWriter = errWriter;
120 this.warnWriter = warnWriter;
121 this.noticeWriter = noticeWriter;
123 this.diags = JCDiagnostic.Factory.instance(context);
125 Options options = Options.instance(context);
126 this.dumpOnError = options.get("-doe") != null;
127 this.promptOnError = options.get("-prompt") != null;
128 this.emitWarnings = options.get("-Xlint:none") == null;
129 this.MaxErrors = getIntOption(options, "-Xmaxerrs", 100);
130 this.MaxWarnings = getIntOption(options, "-Xmaxwarns", 100);
131 this.showSourceLine = options.get("rawDiagnostics") == null;
133 this.diagFormatter = DiagnosticFormatter.instance(context);
134 @SuppressWarnings("unchecked") // FIXME
135 DiagnosticListener<? super JavaFileObject> diagListener =
136 context.get(DiagnosticListener.class);
137 this.diagListener = diagListener;
139 Source source = Source.instance(context);
140 this.enforceMandatoryWarnings = source.enforceMandatoryWarnings();
141 }
142 // where
143 private int getIntOption(Options options, String optionName, int defaultValue) {
144 String s = options.get(optionName);
145 try {
146 if (s != null) return Integer.parseInt(s);
147 } catch (NumberFormatException e) {
148 // silently ignore ill-formed numbers
149 }
150 return defaultValue;
151 }
153 /** The default writer for diagnostics
154 */
155 static final PrintWriter defaultWriter(Context context) {
156 PrintWriter result = context.get(outKey);
157 if (result == null)
158 context.put(outKey, result = new PrintWriter(System.err));
159 return result;
160 }
162 /** Construct a log with default settings.
163 */
164 protected Log(Context context) {
165 this(context, defaultWriter(context));
166 }
168 /** Construct a log with all output redirected.
169 */
170 protected Log(Context context, PrintWriter defaultWriter) {
171 this(context, defaultWriter, defaultWriter, defaultWriter);
172 }
174 /** Get the Log instance for this context. */
175 public static Log instance(Context context) {
176 Log instance = context.get(logKey);
177 if (instance == null)
178 instance = new Log(context);
179 return instance;
180 }
182 /** The file that's currently translated.
183 */
184 protected JCDiagnostic.DiagnosticSource source;
186 /** The number of errors encountered so far.
187 */
188 public int nerrors = 0;
190 /** The number of warnings encountered so far.
191 */
192 public int nwarnings = 0;
194 /** A set of all errors generated so far. This is used to avoid printing an
195 * error message more than once. For each error, a pair consisting of the
196 * source file name and source code position of the error is added to the set.
197 */
198 private Set<Pair<JavaFileObject, Integer>> recorded = new HashSet<Pair<JavaFileObject,Integer>>();
200 private Map<JavaFileObject, Map<JCTree, Integer>> endPosTables;
202 /** The buffer containing the file that's currently translated.
203 */
204 private char[] buf = null;
206 /** The length of useful data in buf
207 */
208 private int bufLen = 0;
210 /** The position in the buffer at which last error was reported
211 */
212 private int bp;
214 /** number of the current source line; first line is 1
215 */
216 private int line;
218 /** buffer index of the first character of the current source line
219 */
220 private int lineStart;
222 public boolean hasDiagnosticListener() {
223 return diagListener != null;
224 }
226 public void setEndPosTable(JavaFileObject name, Map<JCTree, Integer> table) {
227 if (endPosTables == null)
228 endPosTables = new HashMap<JavaFileObject, Map<JCTree, Integer>>();
229 endPosTables.put(name, table);
230 }
232 /** Re-assign source, returning previous setting.
233 */
234 public JavaFileObject useSource(final JavaFileObject name) {
235 JavaFileObject prev = currentSource();
236 if (name != prev) {
237 source = new JCDiagnostic.DiagnosticSource() {
238 public JavaFileObject getFile() {
239 return name;
240 }
241 public CharSequence getName() {
242 return JavacFileManager.getJavacBaseFileName(getFile());
243 }
244 public int getLineNumber(int pos) {
245 return Log.this.getLineNumber(pos);
246 }
247 public int getColumnNumber(int pos) {
248 return Log.this.getColumnNumber(pos);
249 }
250 public Map<JCTree, Integer> getEndPosTable() {
251 return (endPosTables == null ? null : endPosTables.get(name));
252 }
253 };
254 buf = null;
255 }
256 return prev;
257 }
259 /** Re-assign source buffer for existing source name.
260 */
261 protected void setBuf(char[] newBuf) {
262 buf = newBuf;
263 bufLen = buf.length;
264 bp = 0;
265 lineStart = 0;
266 line = 1;
267 }
269 protected char[] getBuf() {
270 return buf;
271 }
273 /** Return current source name.
274 */
275 public JavaFileObject currentSource() {
276 return source == null ? null : source.getFile();
277 }
279 /** Flush the logs
280 */
281 public void flush() {
282 errWriter.flush();
283 warnWriter.flush();
284 noticeWriter.flush();
285 }
287 /** Returns true if an error needs to be reported for a given
288 * source name and pos.
289 */
290 protected boolean shouldReport(JavaFileObject file, int pos) {
291 if (multipleErrors || file == null)
292 return true;
294 Pair<JavaFileObject,Integer> coords = new Pair<JavaFileObject,Integer>(file, pos);
295 boolean shouldReport = !recorded.contains(coords);
296 if (shouldReport)
297 recorded.add(coords);
298 return shouldReport;
299 }
301 /** Prompt user after an error.
302 */
303 public void prompt() {
304 if (promptOnError) {
305 System.err.println(getLocalizedString("resume.abort"));
306 char ch;
307 try {
308 while (true) {
309 switch (System.in.read()) {
310 case 'a': case 'A':
311 System.exit(-1);
312 return;
313 case 'r': case 'R':
314 return;
315 case 'x': case 'X':
316 throw new AssertionError("user abort");
317 default:
318 }
319 }
320 } catch (IOException e) {}
321 }
322 }
324 /** Print the faulty source code line and point to the error.
325 * @param pos Buffer index of the error position, must be on current line
326 */
327 private void printErrLine(int pos, PrintWriter writer) {
328 if (!findLine(pos))
329 return;
331 int lineEnd = lineStart;
332 while (lineEnd < bufLen && buf[lineEnd] != CR && buf[lineEnd] != LF)
333 lineEnd++;
334 if (lineEnd - lineStart == 0)
335 return;
336 printLines(writer, new String(buf, lineStart, lineEnd - lineStart));
337 for (bp = lineStart; bp < pos; bp++) {
338 writer.print((buf[bp] == '\t') ? "\t" : " ");
339 }
340 writer.println("^");
341 writer.flush();
342 }
344 protected void initBuf(JavaFileObject fileObject) throws IOException {
345 CharSequence cs = fileObject.getCharContent(true);
346 if (cs instanceof CharBuffer) {
347 CharBuffer cb = (CharBuffer) cs;
348 buf = JavacFileManager.toArray(cb);
349 bufLen = cb.limit();
350 } else {
351 buf = cs.toString().toCharArray();
352 bufLen = buf.length;
353 }
354 }
356 /** Find the line in the buffer that contains the current position
357 * @param pos Character offset into the buffer
358 */
359 private boolean findLine(int pos) {
360 if (pos == Position.NOPOS || currentSource() == null)
361 return false;
362 try {
363 if (buf == null) {
364 initBuf(currentSource());
365 lineStart = 0;
366 line = 1;
367 } else if (lineStart > pos) { // messages don't come in order
368 lineStart = 0;
369 line = 1;
370 }
371 bp = lineStart;
372 while (bp < bufLen && bp < pos) {
373 switch (buf[bp++]) {
374 case CR:
375 if (bp < bufLen && buf[bp] == LF) bp++;
376 line++;
377 lineStart = bp;
378 break;
379 case LF:
380 line++;
381 lineStart = bp;
382 break;
383 }
384 }
385 return bp <= bufLen;
386 } catch (IOException e) {
387 //e.printStackTrace();
388 // FIXME: include e.getLocalizedMessage() in error message
389 printLines(errWriter, getLocalizedString("source.unavailable"));
390 errWriter.flush();
391 buf = new char[0];
392 }
393 return false;
394 }
396 /** Print the text of a message, translating newlines appropriately
397 * for the platform.
398 */
399 public static void printLines(PrintWriter writer, String msg) {
400 int nl;
401 while ((nl = msg.indexOf('\n')) != -1) {
402 writer.println(msg.substring(0, nl));
403 msg = msg.substring(nl+1);
404 }
405 if (msg.length() != 0) writer.println(msg);
406 }
408 /** Report an error, unless another error was already reported at same
409 * source position.
410 * @param key The key for the localized error message.
411 * @param args Fields of the error message.
412 */
413 public void error(String key, Object ... args) {
414 report(diags.error(source, null, key, args));
415 }
417 /** Report an error, unless another error was already reported at same
418 * source position.
419 * @param pos The source position at which to report the error.
420 * @param key The key for the localized error message.
421 * @param args Fields of the error message.
422 */
423 public void error(DiagnosticPosition pos, String key, Object ... args) {
424 report(diags.error(source, pos, key, args));
425 }
427 /** Report an error, unless another error was already reported at same
428 * source position.
429 * @param pos The source position at which to report the error.
430 * @param key The key for the localized error message.
431 * @param args Fields of the error message.
432 */
433 public void error(int pos, String key, Object ... args) {
434 report(diags.error(source, wrap(pos), key, args));
435 }
437 /** Report a warning, unless suppressed by the -nowarn option or the
438 * maximum number of warnings has been reached.
439 * @param pos The source position at which to report the warning.
440 * @param key The key for the localized warning message.
441 * @param args Fields of the warning message.
442 */
443 public void warning(String key, Object ... args) {
444 report(diags.warning(source, null, key, args));
445 }
447 /** Report a warning, unless suppressed by the -nowarn option or the
448 * maximum number of warnings has been reached.
449 * @param pos The source position at which to report the warning.
450 * @param key The key for the localized warning message.
451 * @param args Fields of the warning message.
452 */
453 public void warning(DiagnosticPosition pos, String key, Object ... args) {
454 report(diags.warning(source, pos, key, args));
455 }
457 /** Report a warning, unless suppressed by the -nowarn option or the
458 * maximum number of warnings has been reached.
459 * @param pos The source position at which to report the warning.
460 * @param key The key for the localized warning message.
461 * @param args Fields of the warning message.
462 */
463 public void warning(int pos, String key, Object ... args) {
464 report(diags.warning(source, wrap(pos), key, args));
465 }
467 /** Report a warning.
468 * @param pos The source position at which to report the warning.
469 * @param key The key for the localized warning message.
470 * @param args Fields of the warning message.
471 */
472 public void mandatoryWarning(DiagnosticPosition pos, String key, Object ... args) {
473 if (enforceMandatoryWarnings)
474 report(diags.mandatoryWarning(source, pos, key, args));
475 else
476 report(diags.warning(source, pos, key, args));
477 }
479 /** Report a warning that cannot be suppressed.
480 * @param pos The source position at which to report the warning.
481 * @param key The key for the localized warning message.
482 * @param args Fields of the warning message.
483 */
484 public void strictWarning(DiagnosticPosition pos, String key, Object ... args) {
485 writeDiagnostic(diags.warning(source, pos, key, args));
486 nwarnings++;
487 }
489 /** Provide a non-fatal notification, unless suppressed by the -nowarn option.
490 * @param key The key for the localized notification message.
491 * @param args Fields of the notification message.
492 */
493 public void note(String key, Object ... args) {
494 report(diags.note(source, null, key, args));
495 }
497 /** Provide a non-fatal notification, unless suppressed by the -nowarn option.
498 * @param key The key for the localized notification message.
499 * @param args Fields of the notification message.
500 */
501 public void note(DiagnosticPosition pos, String key, Object ... args) {
502 report(diags.note(source, pos, key, args));
503 }
505 /** Provide a non-fatal notification, unless suppressed by the -nowarn option.
506 * @param key The key for the localized notification message.
507 * @param args Fields of the notification message.
508 */
509 public void note(int pos, String key, Object ... args) {
510 report(diags.note(source, wrap(pos), key, args));
511 }
513 /** Provide a non-fatal notification, unless suppressed by the -nowarn option.
514 * @param key The key for the localized notification message.
515 * @param args Fields of the notification message.
516 */
517 public void mandatoryNote(final JavaFileObject file, String key, Object ... args) {
518 JCDiagnostic.DiagnosticSource wrapper = null;
519 if (file != null) {
520 wrapper = new JCDiagnostic.DiagnosticSource() {
521 public JavaFileObject getFile() {
522 return file;
523 }
524 public CharSequence getName() {
525 return JavacFileManager.getJavacBaseFileName(getFile());
526 }
527 public int getLineNumber(int pos) {
528 return Log.this.getLineNumber(pos);
529 }
530 public int getColumnNumber(int pos) {
531 return Log.this.getColumnNumber(pos);
532 }
533 public Map<JCTree, Integer> getEndPosTable() {
534 return (endPosTables == null ? null : endPosTables.get(file));
535 }
536 };
537 }
538 if (enforceMandatoryWarnings)
539 report(diags.mandatoryNote(wrapper, key, args));
540 else
541 report(diags.note(wrapper, null, key, args));
542 }
544 private DiagnosticPosition wrap(int pos) {
545 return (pos == Position.NOPOS ? null : new SimpleDiagnosticPosition(pos));
546 }
548 /**
549 * Common diagnostic handling.
550 * The diagnostic is counted, and depending on the options and how many diagnostics have been
551 * reported so far, the diagnostic may be handed off to writeDiagnostic.
552 */
553 public void report(JCDiagnostic diagnostic) {
554 switch (diagnostic.getType()) {
555 case FRAGMENT:
556 throw new IllegalArgumentException();
558 case NOTE:
559 // Print out notes only when we are permitted to report warnings
560 // Notes are only generated at the end of a compilation, so should be small
561 // in number.
562 if (emitWarnings || diagnostic.isMandatory()) {
563 writeDiagnostic(diagnostic);
564 }
565 break;
567 case WARNING:
568 if (emitWarnings || diagnostic.isMandatory()) {
569 if (nwarnings < MaxWarnings) {
570 writeDiagnostic(diagnostic);
571 nwarnings++;
572 }
573 }
574 break;
576 case ERROR:
577 if (nerrors < MaxErrors
578 && shouldReport(diagnostic.getSource(), diagnostic.getIntPosition())) {
579 writeDiagnostic(diagnostic);
580 nerrors++;
581 }
582 break;
583 }
584 }
586 /**
587 * Write out a diagnostic.
588 */
589 protected void writeDiagnostic(JCDiagnostic diag) {
590 if (diagListener != null) {
591 try {
592 diagListener.report(diag);
593 return;
594 }
595 catch (Throwable t) {
596 throw new ClientCodeException(t);
597 }
598 }
600 PrintWriter writer = getWriterForDiagnosticType(diag.getType());
602 printLines(writer, diagFormatter.format(diag));
603 if (showSourceLine) {
604 int pos = diag.getIntPosition();
605 if (pos != Position.NOPOS) {
606 JavaFileObject prev = useSource(diag.getSource());
607 printErrLine(pos, writer);
608 useSource(prev);
609 }
610 }
612 if (promptOnError) {
613 switch (diag.getType()) {
614 case ERROR:
615 case WARNING:
616 prompt();
617 }
618 }
620 if (dumpOnError)
621 new RuntimeException().printStackTrace(writer);
623 writer.flush();
624 }
626 @Deprecated
627 protected PrintWriter getWriterForDiagnosticType(DiagnosticType dt) {
628 switch (dt) {
629 case FRAGMENT:
630 throw new IllegalArgumentException();
632 case NOTE:
633 return noticeWriter;
635 case WARNING:
636 return warnWriter;
638 case ERROR:
639 return errWriter;
641 default:
642 throw new Error();
643 }
644 }
646 /** Find a localized string in the resource bundle.
647 * @param key The key for the localized string.
648 * @param args Fields to substitute into the string.
649 */
650 public static String getLocalizedString(String key, Object ... args) {
651 return Messages.getDefaultLocalizedString("compiler.misc." + key, args);
652 }
654 /***************************************************************************
655 * raw error messages without internationalization; used for experimentation
656 * and quick prototyping
657 ***************************************************************************/
659 /** print an error or warning message:
660 */
661 private void printRawError(int pos, String msg) {
662 if (!findLine(pos)) {
663 printLines(errWriter, "error: " + msg);
664 } else {
665 JavaFileObject file = currentSource();
666 if (file != null)
667 printLines(errWriter,
668 JavacFileManager.getJavacFileName(file) + ":" +
669 line + ": " + msg);
670 printErrLine(pos, errWriter);
671 }
672 errWriter.flush();
673 }
675 /** report an error:
676 */
677 public void rawError(int pos, String msg) {
678 if (nerrors < MaxErrors && shouldReport(currentSource(), pos)) {
679 printRawError(pos, msg);
680 prompt();
681 nerrors++;
682 }
683 errWriter.flush();
684 }
686 /** report a warning:
687 */
688 public void rawWarning(int pos, String msg) {
689 if (nwarnings < MaxWarnings && emitWarnings) {
690 printRawError(pos, "warning: " + msg);
691 }
692 prompt();
693 nwarnings++;
694 errWriter.flush();
695 }
697 /** Return the one-based line number associated with a given pos
698 * for the current source file. Zero is returned if no line exists
699 * for the given position.
700 */
701 protected int getLineNumber(int pos) {
702 if (findLine(pos))
703 return line;
704 return 0;
705 }
707 /** Return the one-based column number associated with a given pos
708 * for the current source file. Zero is returned if no column exists
709 * for the given position.
710 */
711 protected int getColumnNumber(int pos) {
712 if (findLine(pos)) {
713 int column = 0;
714 for (bp = lineStart; bp < pos; bp++) {
715 if (bp >= bufLen)
716 return 0;
717 if (buf[bp] == '\t')
718 column = (column / TabInc * TabInc) + TabInc;
719 else
720 column++;
721 }
722 return column + 1; // positions are one-based
723 }
724 return 0;
725 }
727 public static String format(String fmt, Object... args) {
728 return String.format((java.util.Locale)null, fmt, args);
729 }
731 }