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