1 /*
  2  * Copyright (c) 1996, 2020, 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.jasm;
 24 
 25 import static org.openjdk.asmtools.jasm.Constants.EOF;
 26 import static org.openjdk.asmtools.jasm.Constants.OFFSETBITS;
 27 import org.openjdk.asmtools.util.I18NResourceBundle;
 28 
 29 import java.io.*;
 30 import java.nio.file.Paths;
 31 import java.text.MessageFormat;
 32 
 33 /**
 34  * An input stream for java programs. The stream treats either "\n", "\r" or "\r\n" as the
 35  * end of a line, it always returns \n. It also parses UNICODE characters expressed as
 36  * \uffff. However, if it sees "\\", the second slash cannot begin a unicode sequence. It
 37  * keeps track of the current position in the input stream.
 38  *
 39  * An position consists of: ((linenr << OFFSETBITS) | offset) this means that both
 40  * the line number and the exact offset into the file are encoded in each position
 41  * value.<p>
 42  */
 43 public class Environment {
 44 
 45     /*-------------------------------------------------------- */
 46     /* Environment Inner Classes */
 47     /**
 48      * A sorted list of error messages
 49      */
 50     final class ErrorMessage {
 51 
 52         int where;
 53         String message;
 54         ErrorMessage next;
 55 
 56         /**
 57          * Constructor
 58          */
 59         ErrorMessage(int where, String message) {
 60             this.where = where;
 61             this.message = message;
 62         }
 63     }
 64 
 65     /*-------------------------------------------------------- */
 66     /* Environment Fields */
 67     static boolean traceFlag = false;
 68     boolean debugInfoFlag = false;
 69 
 70     private String inputFileName;
 71     private String simpleInputFileName;
 72     public PrintWriter out;
 73     private boolean nowarn;
 74     private byte[] data;
 75     private int bytepos;
 76     private int linepos;
 77     public int pos;
 78     /*-------------------------------------------------------- */
 79 
 80     public Environment(DataInputStream dis, String inputFileName, PrintWriter out, boolean nowarn) throws IOException {
 81         this.out = out;
 82         this.inputFileName = inputFileName;
 83         this.nowarn = nowarn;
 84         // Read the file
 85         data = new byte[dis.available()];
 86         dis.read(data);
 87         dis.close();
 88         bytepos = 0;
 89         linepos = 1;
 90     }
 91 
 92     public String getInputFileName() {
 93         return inputFileName;
 94     }
 95 
 96     public String getSimpleInputFileName() {
 97         if( simpleInputFileName == null ) {
 98             simpleInputFileName = Paths.get(inputFileName).getFileName().toString();
 99         }
100         return simpleInputFileName;
101     }
102 
103     int lookForward() {
104         try {
105             return data[bytepos];
106         } catch (ArrayIndexOutOfBoundsException e) {
107             return EOF;
108         }
109     }
110 
111     int convertUnicode() {
112         int c;
113         try {
114             while ((c = data[bytepos]) == 'u') {
115                 bytepos++;
116             }
117             int d = 0;
118             for (int i = 0; i < 4; i++) {
119                 switch (c) {
120                     case '0':
121                     case '1':
122                     case '2':
123                     case '3':
124                     case '4':
125                     case '5':
126                     case '6':
127                     case '7':
128                     case '8':
129                     case '9':
130                         d = (d << 4) + c - '0';
131                         break;
132                     case 'a':
133                     case 'b':
134                     case 'c':
135                     case 'd':
136                     case 'e':
137                     case 'f':
138                         d = (d << 4) + 10 + c - 'a';
139                         break;
140                     case 'A':
141                     case 'B':
142                     case 'C':
143                     case 'D':
144                     case 'E':
145                     case 'F':
146                         d = (d << 4) + 10 + c - 'A';
147                         break;
148                     default:
149                         error(pos, "invalid.escape.char");
150                         return d;
151                 }
152                 ++bytepos;
153                 c = data[bytepos];
154             }
155             return d;
156         } catch (ArrayIndexOutOfBoundsException e) {
157             error(pos, "invalid.escape.char");
158             return EOF;
159         }
160     }
161 
162     public int read() {
163         int c;
164         pos = (linepos << OFFSETBITS) | bytepos;
165         try {
166             c = data[bytepos];
167         } catch (ArrayIndexOutOfBoundsException e) {
168             return EOF;
169         }
170         bytepos++;
171 
172         // parse special characters
173         switch (c) {
174             /*            case '\\':
175              if (lookForward() != 'u') {
176              return '\\';
177              }
178              // we have a unicode sequence
179              return convertUnicode();*/
180             case '\n':
181                 linepos++;
182                 return '\n';
183 
184             case '\r':
185                 if (lookForward() == '\n') {
186                     bytepos++;
187                 }
188                 linepos++;
189                 return '\n';
190 
191             default:
192                 return c;
193         }
194     }
195 
196     int lineNumber(int lcpos) {
197         return lcpos >>> OFFSETBITS;
198     }
199 
200     int lineNumber() {
201         return lineNumber(pos);
202     }
203 
204     int lineOffset(int lcpos) {
205         return lcpos & ((1 << OFFSETBITS) - 1);
206     }
207 
208     int lineOffset() {
209         return lineOffset(pos);
210     }
211 
212     /*==============================================================  Environment */
213     /**
214      * The number of errors and warnings
215      */
216     public int nerrors;
217     public int nwarnings;
218     public static final I18NResourceBundle i18n
219             = I18NResourceBundle.getBundleForClass(Main.class);
220 
221     /**
222      * Error String
223      */
224     String errorString(String err, Object arg1, Object arg2, Object arg3) {
225         String str = null;
226 
227         //str = getProperty(err);
228         str = i18n.getString(err);
229         if (str == null) {
230             return "error message '" + err + "' not found";
231         }
232 
233         StringBuffer buf = new StringBuffer();
234         for (int i = 0; i < str.length(); i++) {
235             char c = str.charAt(i);
236             if ((c == '%') && (i + 1 < str.length())) {
237                 switch (str.charAt(++i)) {
238                     case 's':
239                         String arg = arg1.toString();
240                         for (int j = 0; j < arg.length(); j++) {
241                             switch (c = arg.charAt(j)) {
242                                 case ' ':
243                                 case '\t':
244                                 case '\n':
245                                 case '\r':
246                                     buf.append((char) c);
247                                     break;
248 
249                                 default:
250                                     if ((c > ' ') && (c <= 255)) {
251                                         buf.append((char) c);
252                                     } else {
253                                         buf.append('\\');
254                                         buf.append('u');
255                                         buf.append(Integer.toString(c, 16));
256                                     }
257                             }
258                         }
259                         arg1 = arg2;
260                         arg2 = arg3;
261                         break;
262 
263                     case '%':
264                         buf.append('%');
265                         break;
266 
267                     default:
268                         buf.append('?');
269                         break;
270                 }
271             } else {
272                 buf.append((char) c);
273             }
274         }
275         // KTL
276         // Need to do message format to substitute args
277         String msg = buf.toString();
278         MessageFormat form = new MessageFormat(msg);
279         Object args[] = {arg1, arg2, arg3};
280         msg = form.format(args);
281 
282         return msg;
283 
284     }
285 
286     /**
287      * List of outstanding error messages
288      */
289     ErrorMessage errors;
290 
291     /**
292      * Insert an error message in the list of outstanding error messages. The list is
293      * sorted on input position.
294      */
295     void insertError(int where, String message) {
296         //output("ERR = " + message);
297         ErrorMessage msg = new ErrorMessage(where, message);
298         if (errors == null) {
299             errors = msg;
300         } else if (errors.where > where) {
301             msg.next = errors;
302             errors = msg;
303         } else {
304             ErrorMessage m = errors;
305             for (; (m.next != null) && (m.next.where <= where); m = m.next);
306             msg.next = m.next;
307             m.next = msg;
308         }
309     }
310 
311     /**
312      * Flush outstanding errors
313      */
314     public void flushErrors() {
315         if (errors == null) {
316             traceln("flushErrors: errors == null");
317             return;
318         }
319 
320         // Report the errors
321         for (ErrorMessage msg = errors; msg != null; msg = msg.next) {
322             int off = lineOffset(msg.where);
323 
324             int i, j;
325             for (i = off; (i > 0) && (data[i - 1] != '\n') && (data[i - 1] != '\r'); i--);
326             for (j = off; (j < data.length) && (data[j] != '\n') && (data[j] != '\r'); j++);
327 
328             outputln( String.format( "%s (%d:%d) %s", getSimpleInputFileName(), lineNumber(msg.where), off - i, msg.message));
329             outputln(new String(data, i, j - i));
330 
331             char strdata[] = new char[(off - i) + 1];
332             for (j = i; j < off; j++) {
333                 strdata[j - i] = (data[j] == '\t') ? '\t' : ' ';
334             }
335             strdata[off - i] = '^';
336             outputln(new String(strdata));
337         }
338         errors = null;
339     }
340 
341     /**
342      * Output a string. This can either be an error message or something for debugging.
343      * This should be used instead of print.
344      */
345     public void output(String msg) {
346         int len = msg.length();
347         for (int i = 0; i < len; i++) {
348             out.write(msg.charAt(i));
349         }
350         out.flush();
351     }
352 
353     /**
354      * Output a string. This can either be an error message or something for debugging.
355      * This should be used instead of println.
356      */
357     public void outputln(String msg) {
358         output((msg == null ? "" : msg) + "\n");
359     }
360 
361     /**
362      * Issue an error. source - the input source, usually a file name string offset - the
363      * offset in the source of the error err - the error number (as defined in this
364      * interface) arg1 - an optional argument to the error (null if not applicable) arg2 -
365      * a second optional argument to the error (null if not applicable) arg3 - a third
366      * optional argument to the error (null if not applicable)
367      */
368     /**
369      * Issue an error
370      */
371     public void error(int where, String err, Object arg1, Object arg2, Object arg3) {
372         String msg;
373         if (err.startsWith("warn.")) {
374             if (nowarn) {
375                 return;
376             }
377             nwarnings++;
378             msg = "Warning: ";
379         } else {
380             err = "err." + err;
381             nerrors++;
382             msg = "Error: ";
383         }
384         msg = msg + errorString(err, arg1, arg2, arg3);
385         traceln(msg);
386         insertError(where, msg);
387     }
388 
389     public final void error(int where, String err, Object arg1, Object arg2) {
390         error(where, err, arg1, arg2, null);
391     }
392 
393     public final void error(int where, String err, Object arg1) {
394         error(where, err, arg1, null, null);
395     }
396 
397     public final void error(int where, String err) {
398         error(where, err, null, null, null);
399     }
400 
401     public final void error(String err, Object arg1, Object arg2, Object arg3) {
402         error(pos, err, arg1, arg2, arg3);
403     }
404 
405     public final void error(String err, Object arg1, Object arg2) {
406         error(pos, err, arg1, arg2, null);
407     }
408 
409     public final void error(String err, Object arg1) {
410         error(pos, err, arg1, null, null);
411     }
412 
413     public final void error(String err) {
414         error(pos, err, null, null, null);
415     }
416 
417     public final String errorStr(String err, Object arg1, Object arg2, Object arg3) {
418         return errorString(err, arg1, arg2, arg3);
419     }
420 
421     public final String errorStr(String err, Object arg1, Object arg2) {
422         return errorStr(err, arg1, arg2, null);
423     }
424 
425     public final String errorStr(String err, Object arg1) {
426         return errorStr(err, arg1, null, null);
427     }
428 
429     public final String errorStr(String err) {
430         return errorStr(err, null, null, null);
431     }
432 
433     /*==============================================================  trace */
434     public boolean isTraceEnabled() {
435         return traceFlag;
436     }
437 
438     public boolean isDebugEnabled() {
439         return debugInfoFlag;
440     }
441 
442     void trace(String message) {
443         if (traceFlag) {
444             output(message);
445         }
446     }
447 
448     void traceln(String message) {
449         if (traceFlag) {
450             outputln(message);
451         }
452     }
453 
454 } // end Environment