1 /*
  2  * Copyright (c) 2009, 2014, Oracle and/or its affiliates. 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.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 package org.openjdk.asmtools.jcoder;
 24 
 25 import java.io.*;
 26 import java.util.Hashtable;
 27 
 28 import org.openjdk.asmtools.common.Tool;
 29 import org.openjdk.asmtools.util.I18NResourceBundle;
 30 
 31 /**
 32  * An input stream for java programs. The stream treats either "\n", "\r" or "\r\n" as the
 33  * end of a line, it always returns \n. It also parses UNICODE characters expressed as
 34  * \uffff. However, if it sees "\\", the second slash cannot begin a unicode sequence. It
 35  * keeps track of the current position in the input stream.
 36  *
 37  * An position consists of: ((linenr << OFFSETBITS) | offset) this means that both
 38  * the line number and the exact offset into the file are encoded in each postion
 39  * value.<p>
 40  */
 41 public class SourceFile implements org.openjdk.asmtools.jasm.Constants {
 42 
 43     Tool tool;
 44 
 45     boolean traceFlag = false;
 46     boolean debugInfoFlag = false;
 47     /**
 48      * The increment for each character.
 49      */
 50     static final int OFFSETINC = 1;
 51     /**
 52      * The increment for each line.
 53      */
 54     static final int LINEINC = 1 << OFFSETBITS;
 55     String inputFileName;
 56     InputStream in;
 57     PrintWriter out;
 58     int pos;
 59     private int chpos;
 60     private int pushBack = -1;
 61 
 62     public SourceFile(Tool tool, DataInputStream dataInputStream, String inputFileName, PrintWriter out) {
 63         this.tool = tool;
 64         this.inputFileName = inputFileName;
 65         this.in = new BufferedInputStream(dataInputStream);
 66         chpos = LINEINC;
 67         this.out = out;
 68     }
 69 
 70     public String getInputFileName() {
 71         return inputFileName;
 72     }
 73 
 74     public void closeInp() {
 75         try {
 76             in.close();
 77         } catch (IOException e) {
 78         }
 79         flushErrors();
 80     }
 81 
 82     public int read() throws IOException {
 83         pos = chpos;
 84         chpos += OFFSETINC;
 85 
 86         int c = pushBack;
 87         if (c == -1) {
 88             c = in.read();
 89         } else {
 90             pushBack = -1;
 91         }
 92 
 93         // parse special characters
 94         switch (c) {
 95             case -2:
 96                 // -2 is a special code indicating a pushback of a backslash that
 97                 // definitely isn't the start of a unicode sequence.
 98                 return '\\';
 99 
100             case '\\':
101                 if ((c = in.read()) != 'u') {
102                     pushBack = (c == '\\' ? -2 : c);
103                     return '\\';
104                 }
105                 // we have a unicode sequence
106                 chpos += OFFSETINC;
107                 while ((c = in.read()) == 'u') {
108                     chpos += OFFSETINC;
109                 }
110 
111                 // unicode escape sequence
112                 int d = 0;
113                 for (int i = 0; i < 4; i++, chpos += OFFSETINC, c = in.read()) {
114                     switch (c) {
115                         case '0':
116                         case '1':
117                         case '2':
118                         case '3':
119                         case '4':
120                         case '5':
121                         case '6':
122                         case '7':
123                         case '8':
124                         case '9':
125                             d = (d << 4) + c - '0';
126                             break;
127 
128                         case 'a':
129                         case 'b':
130                         case 'c':
131                         case 'd':
132                         case 'e':
133                         case 'f':
134                             d = (d << 4) + 10 + c - 'a';
135                             break;
136 
137                         case 'A':
138                         case 'B':
139                         case 'C':
140                         case 'D':
141                         case 'E':
142                         case 'F':
143                             d = (d << 4) + 10 + c - 'A';
144                             break;
145 
146                         default:
147                             error(pos, "invalid.escape.char");
148                             pushBack = c;
149                             return d;
150                     }
151                 }
152                 pushBack = c;
153                 return d;
154 
155             case '\n':
156                 chpos += LINEINC;
157                 return '\n';
158 
159             case '\r':
160                 if ((c = in.read()) != '\n') {
161                     pushBack = c;
162                 } else {
163                     chpos += OFFSETINC;
164                 }
165                 chpos += LINEINC;
166                 return '\n';
167 
168             default:
169                 return c;
170         }
171     }
172 
173     public int lineNumber(int pos) {
174         return pos >>> OFFSETBITS;
175     }
176 
177     public int lineNumber() {
178         return pos >>> OFFSETBITS;
179     }
180 
181     /*==============================================================  Environment */
182     /**
183      * The number of errors and warnings
184      */
185     public int nerrors;
186     public int nwarnings;
187 
188     public static final I18NResourceBundle i18n
189             = I18NResourceBundle.getBundleForClass(Main.class);
190 
191     /*
192      *  Until place for jasm.properties is defind,
193      *  I have to keep them right here
194      *
195      static Hashtable properties = new Hashtable(40);
196 
197      static {
198      // Scanner:
199      properties.put("err.eof.in.comment", "Comment not terminated at end of input.");
200      properties.put("err.invalid.number", "Invalid character \'%s\' in number.");
201      properties.put("err.invalid.octal.number", "Invalid character in octal number.");
202      properties.put("err.overflow", "Numeric overflow.");
203      properties.put("err.float.format", "Invalid floating point format.");
204      properties.put("err.eof.in.string", "String not terminated at end of input.");
205      properties.put("err.newline.in.string", "String not terminated at end of line.");
206      properties.put("err.funny.char", "Invalid character in input.");
207      properties.put("err.unbalanced.paren", "Unbalanced parentheses.");
208      // Parser:
209      properties.put("err.toplevel.expected", "Class or interface declaration expected.");
210      properties.put("err.token.expected", "'%s' expected.");
211      properties.put("err.identifier.expected", "Identifier expected.");
212      properties.put("err.name.expected", "Name expected.");
213      properties.put("err.io.exception", "I/O error in %s.");
214      properties.put("err.cannot.write", "Cannot write to %s.");
215      properties.put("warn.array.wronglength", "expected array length %s do not match real length %s; expected length written");
216      properties.put("warn.attr.wronglength", "expected attribute length %s do not match real length %s; expected length written");
217      properties.put("attrname.notfound", "Cannot find \"%s\" in constant pool");
218      properties.put("err.attrname.expected", "Attribute's name or index expected.");
219      properties.put("err.element.expected", "Primary data item expected.");
220      properties.put("err.struct.expected", "Structured data item expected.");
221      properties.put("err.macro.undecl", "Macro %s undefined.");
222      }
223      static String getProperty(String nm) {
224      return (String) properties.get(nm);
225      }
226      */
227     /**
228      * Error String
229      */
230     String errorString(String err, Object arg1, Object arg2, Object arg3) {
231         String str = null;
232 
233         if (!err.startsWith("warn.")) {
234             err = "err." + err;
235         }
236         //str = getProperty(err);
237         str = i18n.getString(err);
238 
239         if (str == null) {
240             return "error message '" + err + "' not found";
241         }
242 
243         StringBuffer buf = new StringBuffer();
244         for (int i = 0; i < str.length(); i++) {
245             char c = str.charAt(i);
246             if ((c == '%') && (i + 1 < str.length())) {
247                 switch (str.charAt(++i)) {
248                     case 's':
249                         String arg = arg1.toString();
250                         for (int j = 0; j < arg.length(); j++) {
251                             switch (c = arg.charAt(j)) {
252                                 case ' ':
253                                 case '\t':
254                                 case '\n':
255                                 case '\r':
256                                     buf.append((char) c);
257                                     break;
258 
259                                 default:
260                                     if ((c > ' ') && (c <= 255)) {
261                                         buf.append((char) c);
262                                     } else {
263                                         buf.append('\\');
264                                         buf.append('u');
265                                         buf.append(Integer.toString(c, 16));
266                                     }
267                             }
268                         }
269                         arg1 = arg2;
270                         arg2 = arg3;
271                         break;
272 
273                     case '%':
274                         buf.append('%');
275                         break;
276 
277                     default:
278                         buf.append('?');
279                         break;
280                 }
281             } else {
282                 buf.append((char) c);
283             }
284         }
285         return buf.toString();
286     }
287 
288     /**
289      * List of outstanding error messages
290      */
291     ErrorMessage errors;
292 
293     /**
294      * Insert an error message in the list of outstanding error messages. The list is
295      * sorted on input position.
296      */
297     void insertError(int where, String message) {
298         //output("ERR = " + message);
299         ErrorMessage msg = new ErrorMessage(where, message);
300         if (errors == null) {
301             errors = msg;
302         } else if (errors.where > where) {
303             msg.next = errors;
304             errors = msg;
305         } else {
306             ErrorMessage m = errors;
307             for (; (m.next != null) && (m.next.where <= where); m = m.next) {
308                 ;
309             }
310             msg.next = m.next;
311             m.next = msg;
312         }
313     }
314 
315     /**
316      * Flush outstanding errors
317      */
318     public void flushErrors() {
319         if (errors == null) {
320             return;
321         }
322 
323         try {
324             // Read the file
325             DataInputStream dataInputStream = tool.getDataInputStream(inputFileName);
326             if (dataInputStream == null)
327                 return;
328 
329             byte data[] = new byte[dataInputStream.available()];
330             dataInputStream.read(data);
331             dataInputStream.close();
332 
333             // Report the errors
334             for (ErrorMessage msg = errors; msg != null; msg = msg.next) {
335                 int ln = msg.where >>> OFFSETBITS;
336                 int off = msg.where & ((1 << OFFSETBITS) - 1);
337 
338                 int i, j;
339                 for (i = off; (i > 0) && (data[i - 1] != '\n') && (data[i - 1] != '\r'); i--) {
340                     ;
341                 }
342                 for (j = off; (j < data.length) && (data[j] != '\n') && (data[j] != '\r'); j++) {
343                     ;
344                 }
345 
346                 String prefix = inputFileName + ":" + ln + ":";
347                 outputln(prefix + " " + msg.message);
348                 outputln(new String(data, i, j - i));
349 
350                 char strdata[] = new char[(off - i) + 1];
351                 for (j = i; j < off; j++) {
352                     strdata[j - i] = (data[j] == '\t') ? '\t' : ' ';
353                 }
354                 strdata[off - i] = '^';
355                 outputln(new String(strdata));
356             }
357         } catch (IOException e) {
358             outputln("I/O exception");
359         }
360         errors = null;
361     }
362 
363     /**
364      * Output a string. This can either be an error message or something for debugging.
365      * This should be used instead of print.
366      */
367     public void output(String msg) {
368         int len = msg.length();
369         for (int i = 0; i < len; i++) {
370             out.write(msg.charAt(i));
371         }
372         out.flush();
373     }
374 
375     /**
376      * Output a string. This can either be an error message or something for debugging.
377      * This should be used instead of println.
378      */
379     public void outputln(String msg) {
380         output(msg);
381         out.write('\n');
382         out.flush();
383     }
384 
385     /**
386      * Issue an error.
387      * @param where Offset in the source for the error
388      * @param err Error number (as defined in this interface)
389      * @param arg1 Optional argument to the error (null if not applicable)
390      * @param arg2 Optional argument to the error (null if not applicable)
391      * @param arg3 Optional argument to the error (null if not applicable)
392      */
393     /**
394      * Issue an error
395      */
396     public void error(int where, String err, Object arg1, Object arg2, Object arg3) {
397         String msg = errorString(err, arg1, arg2, arg3);
398         if (err.startsWith("warn.")) {
399             nwarnings++;
400         } else {
401             nerrors++;
402         }
403         traceln("error:" + msg);
404         insertError(where, msg);
405     }
406 
407     public final void error(int where, String err, Object arg1, Object arg2) {
408         error(where, err, arg1, arg2, null);
409     }
410 
411     public final void error(int where, String err, Object arg1) {
412         error(where, err, arg1, null, null);
413     }
414 
415     public final void error(int where, String err) {
416         error(where, err, null, null, null);
417     }
418 
419     public final void error(String err) {
420         error(pos, err, null, null, null);
421     }
422 
423     public final void error(String err, Object arg1) {
424         error(pos, err, arg1, null, null);
425     }
426 
427     /*==============================================================  trace */
428     public void trace(String message) {
429         if (traceFlag) {
430             output(message);
431         }
432     }
433 
434     public void traceln(String message) {
435         if (traceFlag) {
436             outputln(message);
437         }
438     }
439 } // end SourceFile
440