1 /*
  2  * Copyright (c) 2009, 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.jcoder;
 24 
 25 import java.io.*;
 26 import java.util.ArrayList;
 27 import java.util.Arrays;
 28 import java.util.HashMap;
 29 import java.util.Stack;
 30 
 31 import static org.openjdk.asmtools.jcoder.JcodTokens.ConstType;
 32 import static org.openjdk.asmtools.jcoder.JcodTokens.Token;
 33 
 34 /**
 35  * Compiles just 1 source file
 36  */
 37 class Jcoder {
 38 
 39     /*-------------------------------------------------------- */
 40     /* Jcoder Fields */
 41     private ArrayList<ByteBuffer> Classes = new ArrayList<>();
 42     private ByteBuffer buf;
 43     private DataOutputStream bufstream;
 44     private int depth = 0;
 45     private String tabStr = "";
 46     private Context context = null;
 47     protected SourceFile env;
 48     protected Scanner scanner;
 49 
 50     /*-------------------------------------------------------- */
 51     /* Jcoder inner classes */
 52 
 53     /*-------------------------------------------------------- */
 54     /* ContextTag (marker) - describes the type of token */
 55     /*    this is rather cosmetic, no function currently. */
 56     private enum ContextTag {
 57         NULL                ( ""),
 58         CLASS               ( "Class"),
 59         CONSTANTPOOL        ( "Constant-Pool"),
 60         INTERFACES          ( "Interfaces"),
 61         INTERFACE           ( "Interface"),
 62         METHODS             ( "Methods"),
 63         METHOD              ( "Method"),
 64         FIELDS              ( "Fields"),
 65         FIELD               ( "Field"),
 66         ATTRIBUTE           ( "Attribute");
 67 
 68         private final String  printValue;
 69 
 70         ContextTag(String value) {
 71             printValue = value;
 72         }
 73 
 74         public String printval() {
 75             return printValue;
 76         }
 77     }
 78 
 79     /*-------------------------------------------------------- */
 80     /* ContextVal (marker) - Specific value on a context stack */
 81     private class ContextVal {
 82 
 83         public ContextTag tag;
 84         int compCount;
 85         ContextVal owner;
 86 
 87         ContextVal(ContextTag tg) {
 88             tag = tg;
 89             compCount = 0;
 90             owner = null;
 91         }
 92 
 93         ContextVal(ContextTag tg, ContextVal ownr) {
 94             tag = tg;
 95             compCount = 0;
 96             owner = ownr;
 97         }
 98     }
 99 
100 
101     /*-------------------------------------------------------- */
102     /* Context - Context stack */
103     public class Context {
104 
105         Stack<ContextVal> stack;
106 
107         private boolean hasCP;
108         private boolean hasMethods;
109         private boolean hasInterfaces;
110         private boolean hasFields;
111 
112         Context() {
113             stack = new Stack<>();
114             init();
115         }
116 
117         boolean isConstantPool() {
118           return !stack.empty() && (stack.peek().tag == ContextTag.CONSTANTPOOL);
119         }
120 
121         public void init() {
122             stack.removeAllElements();
123             hasCP = false;
124             hasMethods = false;
125             hasInterfaces = false;
126             hasFields = false;
127         }
128 
129         void update() {
130             if (stack.empty()) {
131                 stack.push(new ContextVal(ContextTag.CLASS));
132                 return;
133             }
134 
135             ContextVal currentCtx = stack.peek();
136             switch (currentCtx.tag) {
137                 case CLASS:
138                     if (!hasCP) {
139                         stack.push(new ContextVal(ContextTag.CONSTANTPOOL));
140                         hasCP = true;
141                     } else if (!hasInterfaces) {
142                         stack.push(new ContextVal(ContextTag.INTERFACES));
143                         hasInterfaces = true;
144                     } else if (!hasFields) {
145                         stack.push(new ContextVal(ContextTag.FIELDS));
146                         hasFields = true;
147                     } else if (!hasMethods) {
148                         stack.push(new ContextVal(ContextTag.METHODS));
149                         hasMethods = true;
150                     } else {
151                         // must be class attributes
152                         currentCtx.compCount += 1;
153                         stack.push(new ContextVal(ContextTag.ATTRIBUTE, currentCtx));
154                     }
155                     break;
156                 case INTERFACES:
157                     currentCtx.compCount += 1;
158                     stack.push(new ContextVal(ContextTag.INTERFACE, currentCtx));
159                     break;
160                 case FIELDS:
161                     currentCtx.compCount += 1;
162                     stack.push(new ContextVal(ContextTag.FIELD, currentCtx));
163                     break;
164                 case METHODS:
165                     currentCtx.compCount += 1;
166                     stack.push(new ContextVal(ContextTag.METHOD, currentCtx));
167                     break;
168                 case FIELD:
169                 case METHOD:
170                 case ATTRIBUTE:
171                     currentCtx.compCount += 1;
172                     stack.push(new ContextVal(ContextTag.ATTRIBUTE, currentCtx));
173                     break;
174                 default:
175                     break;
176             }
177         }
178 
179         void exit() {
180             if (!stack.isEmpty()) {
181                 stack.pop();
182             }
183         }
184 
185         public String toString() {
186             if (stack.isEmpty()) {
187                 return "";
188             }
189             ContextVal currentCtx = stack.peek();
190             String retval = currentCtx.tag.printval();
191             switch (currentCtx.tag) {
192                 case INTERFACE:
193                 case METHOD:
194                 case FIELD:
195                 case ATTRIBUTE:
196                     if (currentCtx.owner != null) {
197                         retval += "[" + currentCtx.owner.compCount + "]";
198                     }
199             }
200 
201             return retval;
202         }
203     }
204 
205 
206     /*-------------------------------------------------------- */
207     /* Jcoder */
208     /**
209      * Create a parser
210      */
211     Jcoder(SourceFile sf, HashMap<String, String> macros) throws IOException {
212         scanner = new Scanner(sf, macros);
213         env = sf;
214         context = new Context();
215 
216     }
217     /*-------------------------------------------------------- */
218 
219     /**
220      * Expect a token, return its value, scan the next token or throw an exception.
221      */
222     private void expect(Token t) throws SyntaxError, IOException {
223         if (scanner.token != t) {
224             env.traceln("expect:" + t + " instead of " + scanner.token);
225             switch (t) {
226                 case IDENT:
227                     env.error(scanner.pos, "identifier.expected");
228                     break;
229                 default:
230                     env.error(scanner.pos, "token.expected", t.toString());
231                     break;
232             }
233             throw new SyntaxError();
234         }
235         scanner.scan();
236     }
237 
238     private void recoverField() throws SyntaxError, IOException {
239         while (true) {
240             switch (scanner.token) {
241                 case LBRACE:
242                     scanner.match(Token.LBRACE, Token.RBRACE);
243                     scanner.scan();
244                     break;
245 
246                 case LPAREN:
247                     scanner.match(Token.LPAREN, Token.RPAREN);
248                     scanner.scan();
249                     break;
250 
251                 case LSQBRACKET:
252                     scanner.match(Token.LSQBRACKET, Token.RSQBRACKET);
253                     scanner.scan();
254                     break;
255 
256                 case RBRACE:
257                 case EOF:
258                 case INTERFACE:
259                 case CLASS:
260                     // begin of something outside a class, panic more
261                     throw new SyntaxError();
262 
263                 default:
264                     // don't know what to do, skip
265                     scanner.scan();
266                     break;
267             }
268         }
269     }
270 
271     /**
272      * Parse an array of struct.
273      */
274     private void parseArray() throws IOException {
275         scanner.scan();
276         int length0 = buf.length, pos0 = scanner.pos;
277         int num_expected;
278         if (scanner.token == Token.INTVAL) {
279             num_expected = scanner.intValue;
280             scanner.scan();
281         } else {
282             num_expected = -1;
283         }
284         expect(Token.RSQBRACKET);
285         int numSize;
286         switch (scanner.token) {
287             case BYTEINDEX:
288                 scanner.scan();
289                 numSize = 1;
290                 break;
291             case SHORTINDEX:
292                 scanner.scan();
293                 numSize = 2;
294                 break;
295             case ZEROINDEX:
296                 scanner.scan();
297                 numSize = 0;
298                 break;
299             default:
300                 numSize = 2;
301         }
302 
303         // skip array size
304         if (numSize > 0) {
305             buf.append(num_expected, numSize);
306         }
307 
308         int num_present = parseStruct();
309         if (num_expected == -1) {
310             env.trace(" buf.writeAt(" + length0 + ", " + num_present + ", " + numSize + ");  ");
311             // skip array size
312             if (numSize > 0) {
313                 buf.writeAt(length0, num_present, numSize);
314             }
315         } else if ( num_expected != num_present) {
316             if (context.isConstantPool() && num_expected == num_present +1) return;
317             env.error(pos0, "warn.array.wronglength", num_expected, num_present);
318         }
319     }
320 
321     /**
322      * Parse a byte array.
323      */
324     private void parseByteArray() throws IOException {
325         scanner.scan();
326         expect(Token.LSQBRACKET);
327         int length0 = buf.length, pos0 = scanner.pos;
328         int len_expected;
329         if (scanner.token == Token.INTVAL) {
330             len_expected = scanner.intValue;
331             scanner.scan();
332         } else {
333             len_expected = -1;
334         }
335         expect(Token.RSQBRACKET);
336         int lenSize;
337         switch (scanner.token) {
338             case BYTEINDEX:
339                 scanner.scan();
340                 lenSize = 1;
341                 break;
342             case SHORTINDEX:
343                 scanner.scan();
344                 lenSize = 2;
345                 break;
346             case ZEROINDEX:
347                 scanner.scan();
348                 lenSize = 0;
349                 break;
350             default:
351                 lenSize = 4;
352         }
353 
354         // skip array size
355         if (lenSize > 0) {
356             buf.append(len_expected, lenSize);
357         }
358         int length1 = buf.length;
359         parseStruct();
360         int len_present = buf.length - length1;
361         if (len_expected == -1) {
362             env.trace(" buf.writeAt(" + length0 + ", " + len_present + ", " + lenSize + ");  ");
363             // skip array size
364             if (lenSize > 0) {
365                 buf.writeAt(length0, len_present, lenSize);
366             }
367         } else if (len_expected != len_present) {
368             env.error(pos0, "warn.array.wronglength", len_expected, len_present);
369         }
370     }
371 
372     /**
373      * Parse an Attribute.
374      */
375     private void parseAttr() throws IOException {
376         scanner.scan();
377         expect(Token.LPAREN);
378         int cpx; // index int const. pool
379         if (scanner.token == Token.INTVAL) {
380             cpx = scanner.intValue;
381             scanner.scan();
382 
383             /*  } else if (token==STRINGVAL) {
384              Integer Val=(Integer)(CP_Strings.get(stringValue));
385              if (Val == null) {
386              env.error(pos, "attrname.notfound", stringValue);
387              throw new SyntaxError();
388              }
389              cpx=Val.intValue();
390              */        } else {
391             env.error(scanner.pos, "attrname.expected");
392             throw new SyntaxError();
393         }
394         buf.append(cpx, 2);
395         int pos0 = scanner.pos, length0 = buf.length;
396         int len_expected;
397         if (scanner.token == Token.COMMA) {
398             scanner.scan();
399             len_expected = scanner.intValue;
400             expect(Token.INTVAL);
401         } else {
402             len_expected = -1;
403         }
404         buf.append(len_expected, 4);
405         expect(Token.RPAREN);
406         parseStruct();
407         int len_present = buf.length - (length0 + 4);
408         if (len_expected == -1) {
409             buf.writeAt(length0, len_present, 4);
410         } else if (len_expected != len_present) {
411             env.error(pos0, "warn.attr.wronglength", len_expected, len_present);
412         }
413     } // end parseAttr
414 
415     /**
416      * Parse a Component of JavaCard .cap file.
417      */
418     private void parseComp() throws IOException {
419         scanner.scan();
420         expect(Token.LPAREN);
421         int tag = scanner.intValue; // index int const. pool
422         expect(Token.INTVAL);
423         buf.append(tag, 1);
424         int pos0 = scanner.pos, length0 = buf.length;
425         int len_expected;
426         if (scanner.token == Token.COMMA) {
427             scanner.scan();
428             len_expected = scanner.intValue;
429             expect(Token.INTVAL);
430         } else {
431             len_expected = -1;
432         }
433         buf.append(len_expected, 2);
434         expect(Token.RPAREN);
435         parseStruct();
436         int len_present = buf.length - (length0 + 2);
437         if (len_expected == -1) {
438             buf.writeAt(length0, len_present, 2);
439         } else if (len_expected != len_present) {
440             env.error(pos0, "warn.attr.wronglength", len_expected, len_present);
441         }
442     } // end parseComp
443 
444     private void adjustDepth(boolean up) {
445         if (up) {
446             depth += 1;
447             context.update();
448             scanner.setDebugCP(context.isConstantPool());
449         } else {
450             depth -= 1;
451             context.exit();
452         }
453         StringBuilder bldr = new StringBuilder();
454         int tabAmt = 4;
455         int len = depth * tabAmt;
456         for (int i = 0; i < len; i++) {
457             bldr.append(" ");
458         }
459         tabStr = bldr.toString();
460     }
461 
462     /**
463      * Parse a structure.
464      */
465     private int parseStruct() throws IOException {
466         adjustDepth(true);
467         env.traceln(" ");
468         env.traceln(tabStr + "MapStruct { <" + context + "> ");
469         expect(Token.LBRACE);
470         int num = 0;
471         int addElem = 0;
472         while (true) {
473             try {
474                 switch (scanner.token) {
475                     case COMMA: // ignored
476                         scanner.scan();
477                         break;
478                     case SEMICOLON:
479                         num++;
480                         addElem = 0;
481                         scanner.scan();
482                         break;
483                     case CLASS:
484                         scanner.addConstDebug(ConstType.CONSTANT_CLASS);
485                         env.trace("class ");
486                         scanner.longValue = ConstType.CONSTANT_CLASS.value();
487                         scanner.intSize = 1;
488                     case INTVAL:
489                         env.trace("int [" + scanner.longValue + "] ");
490                         buf.append(scanner.longValue, scanner.intSize);
491                         scanner.scan();
492                         addElem = 1;
493                         break;
494                     case STRINGVAL:
495                         scanner.scan();
496                         scanner.addConstDebug(ConstType.CONSTANT_UTF8);
497                         env.trace("UTF8 [\"" + scanner.stringValue + "\"] ");
498                         bufstream.writeUTF(scanner.stringValue);
499                         addElem = 1;
500                         break;
501                     case LONGSTRINGVAL:
502                         scanner.scan();
503                         env.traceln("LongString [\"" + Arrays.toString(scanner.longStringValue.data) + "\"] ");
504                         buf.write(scanner.longStringValue.data, 0, scanner.longStringValue.length);
505                         addElem = 1;
506                         break;
507                     case LBRACE:
508                         parseStruct();
509                         addElem = 1;
510                         break;
511                     case LSQBRACKET:
512                         parseArray();
513                         addElem = 1;
514                         break;
515                     case BYTES:
516                         env.trace("bytes ");
517                         parseByteArray();
518                         addElem = 1;
519                         break;
520                     case ATTR:
521                         env.trace("attr ");
522                         parseAttr();
523                         addElem = 1;
524                         break;
525                     case COMP:
526                         env.trace("comp ");
527                         parseComp();
528                         addElem = 1;
529                         break;
530                     case RBRACE:
531                         scanner.scan();
532                         env.traceln(" ");
533                         env.traceln(tabStr + "} // MapStruct  <" + context + "> [");
534                         adjustDepth(false);
535                         return num + addElem;
536                     default:
537                         env.traceln("unexp token=" + scanner.token);
538                         env.traceln("   scanner.stringval = \"" + scanner.stringValue + "\"");
539                         env.error(scanner.pos, "element.expected");
540                         throw new SyntaxError();
541                 }
542             } catch (SyntaxError e) {
543                 recoverField();
544             }
545         }
546     } // end parseStruct
547 
548     /**
549      * Recover after a syntax error in the file. This involves discarding tokens until an
550      * EOF or a possible legal continuation is encountered.
551      */
552     private void recoverFile() throws IOException {
553         while (true) {
554             switch (scanner.token) {
555                 case CLASS:
556                 case INTERFACE:
557                     // Start of a new source file statement, continue
558                     return;
559 
560                 case LBRACE:
561                     scanner.match(Token.LBRACE, Token.RBRACE);
562                     scanner.scan();
563                     break;
564 
565                 case LPAREN:
566                     scanner.match(Token.LPAREN, Token.RPAREN);
567                     scanner.scan();
568                     break;
569 
570                 case LSQBRACKET:
571                     scanner.match(Token.LSQBRACKET, Token.RSQBRACKET);
572                     scanner.scan();
573                     break;
574 
575                 case EOF:
576                     return;
577 
578                 default:
579                     // Don't know what to do, skip
580                     scanner.scan();
581                     break;
582             }
583         }
584     }
585 
586     /**
587      * Parse module declaration
588      */
589     private void parseModule() throws IOException {
590         // skip module name as a redundant element
591         scanner.skipTill(Scanner.LBRACE);
592         buf = new ByteBuffer();
593         bufstream = new DataOutputStream(buf);
594         buf.myname = "module-info.class";
595         scanner.scan();
596         env.traceln("starting " + buf.myname);
597         // Parse the clause
598         parseClause();
599         env.traceln("ending " + buf.myname);
600     }
601 
602     /**
603      * Parse a class or interface declaration.
604      */
605     private void parseClass(Token prev) throws IOException {
606         scanner.scan();
607         buf = new ByteBuffer();
608         bufstream = new DataOutputStream(buf);
609         // Parse the class name
610         switch (scanner.token) {
611             case STRINGVAL:
612                 buf.myname = scanner.stringValue;
613                 break;
614             case BYTEINDEX:
615             case SHORTINDEX:
616             case ATTR:
617             case BYTES:
618             case MACRO:
619             case COMP:
620             case FILE:
621             case IDENT:
622                 if (prev == Token.FILE) {
623                     buf.myname = scanner.stringValue;
624                 } else {
625                     buf.myname = scanner.stringValue + ".class";
626                 }
627                 break;
628             default:
629                 env.error(scanner.prevPos, "name.expected");
630                 throw new SyntaxError();
631         }
632         scanner.scan();
633         env.traceln("starting class " + buf.myname);
634         // Parse the clause
635         parseClause();
636         env.traceln("ending class " + buf.myname);
637 
638     } // end parseClass
639 
640     private void parseClause() throws IOException {
641         switch (scanner.token) {
642             case LBRACE:
643                 parseStruct();
644                 break;
645             case LSQBRACKET:
646                 parseArray();
647                 break;
648             case BYTES:
649                 parseByteArray();
650                 break;
651             case ATTR:
652                 parseAttr();
653                 break;
654             case COMP:
655                 parseComp();
656                 break;
657             default:
658                 env.error(scanner.pos, "struct.expected");
659         }
660     }
661 
662     /**
663      * Parse an Jcoder file.
664      */
665     void parseFile() {
666         env.traceln("PARSER");
667         context.init();
668         try {
669             while (scanner.token != Token.EOF) {
670                 try {
671                     switch (scanner.token) {
672                         case CLASS:
673                         case MODULE:
674                         case INTERFACE:
675                         case FILE:
676                             Token t = scanner.token;
677                             if ( t == Token.MODULE) {
678                                 parseModule();
679                             } else {
680                                 parseClass(t);
681                             }
682                             // End of the class,interface or module
683                             env.flushErrors();
684                             Classes.add(buf);
685                             break;
686                         case SEMICOLON:
687                             // Bogus semi colon
688                             scanner.scan();
689                             break;
690 
691                         case EOF:
692                             // The end
693                             return;
694 
695                         default:
696                             env.traceln("unexpected token=" + scanner.token.toString());
697                             env.error(scanner.pos, "toplevel.expected");
698                             throw new SyntaxError();
699                     }
700                 } catch (SyntaxError e) {
701                     String msg = e.getMessage();
702                     env.traceln("SyntaxError " + (msg == null ? "" : msg));
703                     if( env.debugInfoFlag ) {
704                         e.printStackTrace();
705                     }
706                     recoverFile();
707                 }
708             }
709         } catch (IOException e) {
710             env.error(scanner.pos, "io.exception", env.getInputFileName());
711         }
712     } //end parseFile
713 
714     /*---------------------------------------------*/
715     private static char fileSeparator; //=System.getProperty("file.separator");
716 
717     /**
718      * write to the directory passed with -d option
719      */
720     public void write(ByteBuffer cls, File destdir) throws IOException {
721         String myname = cls.myname;
722         if (myname == null) {
723             env.error("cannot.write", null);
724             return;
725         }
726 
727         env.traceln("writing " + myname);
728         File outfile;
729         if (destdir == null) {
730             int startofname = myname.lastIndexOf('/');
731             if (startofname != -1) {
732                 myname = myname.substring(startofname + 1);
733             }
734             outfile = new File(myname);
735         } else {
736             env.traceln("writing -d " + destdir.getPath());
737             if (fileSeparator == 0) {
738                 fileSeparator = System.getProperty("file.separator").charAt(0);
739             }
740             if (fileSeparator != '/') {
741                 myname = myname.replace('/', fileSeparator);
742             }
743             outfile = new File(destdir, myname);
744             File outdir = new File(outfile.getParent());
745             if (!outdir.exists() && !outdir.mkdirs()) {
746                 env.error("cannot.write", outdir.getPath());
747                 return;
748             }
749         }
750 
751         BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(outfile));
752         out.write(cls.data, 0, cls.length);
753         try {
754             out.close();
755         } catch (IOException ignored) { }
756     }
757 
758     /**
759      * Writes the classes
760      */
761     public void write(File destdir) throws IOException {
762         for (ByteBuffer cls : Classes) {
763             write(cls, destdir);
764         }
765     }  // end write()
766 } // end Jcoder