1/*
2 * Copyright (c) 2001, 2017, 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.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.tools.jdi;
27
28import java.io.File;
29import java.util.ArrayList;
30import java.util.List;
31
32class SDE {
33    private static final int INIT_SIZE_FILE = 3;
34    private static final int INIT_SIZE_LINE = 100;
35    private static final int INIT_SIZE_STRATUM = 3;
36
37    static final String BASE_STRATUM_NAME = "Java";
38
39    /* for C capatibility */
40    static final String NullString = null;
41
42    private class FileTableRecord {
43        int fileId;
44        String sourceName;
45        String sourcePath; // do not read - use accessor
46        boolean isConverted = false;
47
48        /**
49         * Return the sourcePath, computing it if not set.
50         * If set, convert '/' in the sourcePath to the
51         * local file separator.
52         */
53        String getSourcePath(ReferenceTypeImpl refType) {
54            if (!isConverted) {
55                if (sourcePath == null) {
56                    sourcePath = refType.baseSourceDir() + sourceName;
57                } else {
58                    StringBuilder sb = new StringBuilder();
59                    for (int i = 0; i < sourcePath.length(); ++i) {
60                        char ch = sourcePath.charAt(i);
61                        if (ch == '/') {
62                            sb.append(File.separatorChar);
63                        } else {
64                            sb.append(ch);
65                        }
66                    }
67                    sourcePath = sb.toString();
68                }
69                isConverted = true;
70            }
71            return sourcePath;
72        }
73    }
74
75    private class LineTableRecord {
76        int jplsStart;
77        int jplsEnd;
78        int jplsLineInc;
79        int njplsStart;
80        @SuppressWarnings("unused")
81        int njplsEnd;
82        int fileId;
83    }
84
85    private class StratumTableRecord {
86        String id;
87        int fileIndex;
88        int lineIndex;
89    }
90
91    class Stratum {
92        private final int sti; /* stratum index */
93
94        private Stratum(int sti) {
95            this.sti = sti;
96        }
97
98        String id() {
99            return stratumTable[sti].id;
100        }
101
102        boolean isJava() {
103            return sti == baseStratumIndex;
104        }
105
106        /**
107         * Return all the sourceNames for this stratum.
108         * Look from our starting fileIndex upto the starting
109         * fileIndex of next stratum - can do this since there
110         * is always a terminator stratum.
111         * Default sourceName (the first one) must be first.
112         */
113        List<String> sourceNames(ReferenceTypeImpl refType) {
114            int i;
115            int fileIndexStart = stratumTable[sti].fileIndex;
116            /* one past end */
117            int fileIndexEnd = stratumTable[sti+1].fileIndex;
118            List<String> result = new ArrayList<>(fileIndexEnd - fileIndexStart);
119            for (i = fileIndexStart; i < fileIndexEnd; ++i) {
120                result.add(fileTable[i].sourceName);
121            }
122            return result;
123        }
124
125        /**
126         * Return all the sourcePaths for this stratum.
127         * Look from our starting fileIndex upto the starting
128         * fileIndex of next stratum - can do this since there
129         * is always a terminator stratum.
130         * Default sourcePath (the first one) must be first.
131         */
132        List<String> sourcePaths(ReferenceTypeImpl refType) {
133            int i;
134            int fileIndexStart = stratumTable[sti].fileIndex;
135            /* one past end */
136            int fileIndexEnd = stratumTable[sti+1].fileIndex;
137            List<String> result = new ArrayList<>(fileIndexEnd - fileIndexStart);
138            for (i = fileIndexStart; i < fileIndexEnd; ++i) {
139                result.add(fileTable[i].getSourcePath(refType));
140            }
141            return result;
142        }
143
144        LineStratum lineStratum(ReferenceTypeImpl refType,
145                                int jplsLine) {
146            int lti = stiLineTableIndex(sti, jplsLine);
147            if (lti < 0) {
148                return null;
149            } else {
150                return new LineStratum(sti, lti, refType,
151                                       jplsLine);
152            }
153        }
154    }
155
156    class LineStratum {
157        private final int sti; /* stratum index */
158        private final int lti; /* line table index */
159        private final ReferenceTypeImpl refType;
160        private final int jplsLine;
161        private String sourceName = null;
162        private String sourcePath = null;
163
164        private LineStratum(int sti, int lti,
165                            ReferenceTypeImpl refType,
166                            int jplsLine) {
167            this.sti = sti;
168            this.lti = lti;
169            this.refType = refType;
170            this.jplsLine = jplsLine;
171        }
172
173        public boolean equals(Object obj) {
174            if (obj instanceof LineStratum) {
175                LineStratum other = (LineStratum)obj;
176                return (lti == other.lti) &&
177                       (sti == other.sti) &&
178                       (lineNumber() == other.lineNumber()) &&
179                       (refType.equals(other.refType));
180            } else {
181                return false;
182            }
183        }
184
185        @Override
186        public int hashCode() {
187            return (lineNumber() * 17) ^ refType.hashCode();
188        }
189
190        int lineNumber() {
191            return stiLineNumber(sti, lti, jplsLine);
192        }
193
194        /**
195         * Fetch the source name and source path for
196         * this line, converting or constructing
197         * the source path if needed.
198         */
199        void getSourceInfo() {
200            if (sourceName != null) {
201                // already done
202                return;
203            }
204            int fti = stiFileTableIndex(sti, lti);
205            if (fti == -1) {
206                throw new InternalError(
207              "Bad SourceDebugExtension, no matching source id " +
208              lineTable[lti].fileId + " jplsLine: " + jplsLine);
209            }
210            FileTableRecord ftr = fileTable[fti];
211            sourceName = ftr.sourceName;
212            sourcePath = ftr.getSourcePath(refType);
213        }
214
215        String sourceName() {
216            getSourceInfo();
217            return sourceName;
218        }
219
220        String sourcePath() {
221            getSourceInfo();
222            return sourcePath;
223        }
224    }
225
226    private FileTableRecord[] fileTable = null;
227    private LineTableRecord[] lineTable = null;
228    private StratumTableRecord[] stratumTable = null;
229
230    private int fileIndex = 0;
231    private int lineIndex = 0;
232    private int stratumIndex = 0;
233    private int currentFileId = 0;
234
235    private int defaultStratumIndex = -1;
236    private int baseStratumIndex = -2; /* so as not to match -1 above */
237    private int sdePos = 0;
238
239    final String sourceDebugExtension;
240    String jplsFilename = null;
241    String defaultStratumId = null;
242    boolean isValid = false;
243
244    SDE(String sourceDebugExtension) {
245        this.sourceDebugExtension = sourceDebugExtension;
246        decode();
247    }
248
249    SDE() {
250        this.sourceDebugExtension = null;
251        createProxyForAbsentSDE();
252    }
253
254    char sdePeek() {
255        if (sdePos >= sourceDebugExtension.length()) {
256            syntax();
257        }
258        return sourceDebugExtension.charAt(sdePos);
259    }
260
261    char sdeRead() {
262        if (sdePos >= sourceDebugExtension.length()) {
263            syntax();
264        }
265        return sourceDebugExtension.charAt(sdePos++);
266    }
267
268    void sdeAdvance() {
269        sdePos++;
270    }
271
272    void syntax() {
273        throw new InternalError("bad SourceDebugExtension syntax - position " +
274                                sdePos);
275    }
276
277    void syntax(String msg) {
278        throw new InternalError("bad SourceDebugExtension syntax: " + msg);
279    }
280
281    void assureLineTableSize() {
282        int len = lineTable == null? 0 : lineTable.length;
283        if (lineIndex >= len) {
284            int i;
285            int newLen = len == 0? INIT_SIZE_LINE : len * 2;
286            LineTableRecord[] newTable = new LineTableRecord[newLen];
287            for (i = 0; i < len; ++i) {
288                newTable[i] = lineTable[i];
289            }
290            for (; i < newLen; ++i) {
291                newTable[i] = new LineTableRecord();
292            }
293            lineTable = newTable;
294        }
295    }
296
297    void assureFileTableSize() {
298        int len = fileTable == null? 0 : fileTable.length;
299        if (fileIndex >= len) {
300            int i;
301            int newLen = len == 0? INIT_SIZE_FILE : len * 2;
302            FileTableRecord[] newTable = new FileTableRecord[newLen];
303            for (i = 0; i < len; ++i) {
304                newTable[i] = fileTable[i];
305            }
306            for (; i < newLen; ++i) {
307                newTable[i] = new FileTableRecord();
308            }
309            fileTable = newTable;
310        }
311    }
312
313    void assureStratumTableSize() {
314        int len = stratumTable == null? 0 : stratumTable.length;
315        if (stratumIndex >= len) {
316            int i;
317            int newLen = len == 0? INIT_SIZE_STRATUM : len * 2;
318            StratumTableRecord[] newTable = new StratumTableRecord[newLen];
319            for (i = 0; i < len; ++i) {
320                newTable[i] = stratumTable[i];
321            }
322            for (; i < newLen; ++i) {
323                newTable[i] = new StratumTableRecord();
324            }
325            stratumTable = newTable;
326        }
327    }
328
329    String readLine() {
330        StringBuilder sb = new StringBuilder();
331        char ch;
332
333        ignoreWhite();
334        while (((ch = sdeRead()) != '\n') && (ch != '\r')) {
335            sb.append(ch);
336        }
337        // check for CR LF
338        if ((ch == '\r') && (sdePeek() == '\n')) {
339            sdeRead();
340        }
341        ignoreWhite(); // leading white
342        return sb.toString();
343    }
344
345    private int defaultStratumTableIndex() {
346        if ((defaultStratumIndex == -1) && (defaultStratumId != null)) {
347            defaultStratumIndex =
348                stratumTableIndex(defaultStratumId);
349        }
350        return defaultStratumIndex;
351    }
352
353    int stratumTableIndex(String stratumId) {
354        int i;
355
356        if (stratumId == null) {
357            return defaultStratumTableIndex();
358        }
359        for (i = 0; i < (stratumIndex-1); ++i) {
360            if (stratumTable[i].id.equals(stratumId)) {
361                return i;
362            }
363        }
364        return defaultStratumTableIndex();
365    }
366
367    Stratum stratum(String stratumID) {
368        int sti = stratumTableIndex(stratumID);
369        return new Stratum(sti);
370    }
371
372    List<String> availableStrata() {
373        List<String> strata = new ArrayList<>();
374
375        for (int i = 0; i < (stratumIndex-1); ++i) {
376            StratumTableRecord rec = stratumTable[i];
377            strata.add(rec.id);
378        }
379        return strata;
380    }
381
382/*****************************
383 * below functions/methods are written to compile under either Java or C
384 *
385 * Needed support functions:
386 *   sdePeek()
387 *   sdeRead()
388 *   sdeAdvance()
389 *   readLine()
390 *   assureLineTableSize()
391 *   assureFileTableSize()
392 *   assureStratumTableSize()
393 *   syntax()
394 *
395 *   stratumTableIndex(String)
396 *
397 * Needed support variables:
398 *   lineTable
399 *   lineIndex
400 *   fileTable
401 *   fileIndex
402 *   currentFileId
403 *
404 * Needed types:
405 *   String
406 *
407 * Needed constants:
408 *   NullString
409 */
410
411    void ignoreWhite() {
412        char ch;
413
414        while (((ch = sdePeek()) == ' ') || (ch == '\t')) {
415            sdeAdvance();
416        }
417    }
418
419    void ignoreLine() {
420        char ch;
421
422        while (((ch = sdeRead()) != '\n') && (ch != '\r')) {
423        }
424        /* check for CR LF */
425        if ((ch == '\r') && (sdePeek() == '\n')) {
426            sdeAdvance();
427        }
428        ignoreWhite(); /* leading white */
429    }
430
431    int readNumber() {
432        int value = 0;
433        char ch;
434
435        ignoreWhite();
436        while (((ch = sdePeek()) >= '0') && (ch <= '9')) {
437            sdeAdvance();
438            value = (value * 10) + ch - '0';
439        }
440        ignoreWhite();
441        return value;
442    }
443
444    void storeFile(int fileId, String sourceName, String sourcePath) {
445        assureFileTableSize();
446        fileTable[fileIndex].fileId = fileId;
447        fileTable[fileIndex].sourceName = sourceName;
448        fileTable[fileIndex].sourcePath = sourcePath;
449        ++fileIndex;
450    }
451
452    void fileLine() {
453        int hasAbsolute = 0; /* acts as boolean */
454        int fileId;
455        String sourceName;
456        String sourcePath = null;
457
458        /* is there an absolute filename? */
459        if (sdePeek() == '+') {
460            sdeAdvance();
461            hasAbsolute = 1;
462        }
463        fileId = readNumber();
464        sourceName = readLine();
465        if (hasAbsolute == 1) {
466            sourcePath = readLine();
467        }
468
469        storeFile(fileId, sourceName, sourcePath);
470    }
471
472    void storeLine(int jplsStart, int jplsEnd, int jplsLineInc,
473                   int njplsStart, int njplsEnd, int fileId) {
474        assureLineTableSize();
475        lineTable[lineIndex].jplsStart = jplsStart;
476        lineTable[lineIndex].jplsEnd = jplsEnd;
477        lineTable[lineIndex].jplsLineInc = jplsLineInc;
478        lineTable[lineIndex].njplsStart = njplsStart;
479        lineTable[lineIndex].njplsEnd = njplsEnd;
480        lineTable[lineIndex].fileId = fileId;
481        ++lineIndex;
482    }
483
484    /**
485     * Parse line translation info.  Syntax is
486     *     <NJ-start-line> [ # <file-id> ] [ , <line-count> ] :
487     *                 <J-start-line> [ , <line-increment> ] CR
488     */
489    void lineLine() {
490        int lineCount = 1;
491        int lineIncrement = 1;
492        int njplsStart;
493        int jplsStart;
494
495        njplsStart = readNumber();
496
497        /* is there a fileID? */
498        if (sdePeek() == '#') {
499            sdeAdvance();
500            currentFileId = readNumber();
501        }
502
503        /* is there a line count? */
504        if (sdePeek() == ',') {
505            sdeAdvance();
506            lineCount = readNumber();
507        }
508
509        if (sdeRead() != ':') {
510            syntax();
511        }
512        jplsStart = readNumber();
513        if (sdePeek() == ',') {
514            sdeAdvance();
515            lineIncrement = readNumber();
516        }
517        ignoreLine(); /* flush the rest */
518
519        storeLine(jplsStart,
520                  jplsStart + (lineCount * lineIncrement) -1,
521                  lineIncrement,
522                  njplsStart,
523                  njplsStart + lineCount -1,
524                  currentFileId);
525    }
526
527    /**
528     * Until the next stratum section, everything after this
529     * is in stratumId - so, store the current indicies.
530     */
531    void storeStratum(String stratumId) {
532        /* remove redundant strata */
533        if (stratumIndex > 0) {
534            if ((stratumTable[stratumIndex-1].fileIndex
535                                            == fileIndex) &&
536                (stratumTable[stratumIndex-1].lineIndex
537                                            == lineIndex)) {
538                /* nothing changed overwrite it */
539                --stratumIndex;
540            }
541        }
542        /* store the results */
543        assureStratumTableSize();
544        stratumTable[stratumIndex].id = stratumId;
545        stratumTable[stratumIndex].fileIndex = fileIndex;
546        stratumTable[stratumIndex].lineIndex = lineIndex;
547        ++stratumIndex;
548        currentFileId = 0;
549    }
550
551    /**
552     * The beginning of a stratum's info
553     */
554    void stratumSection() {
555        storeStratum(readLine());
556    }
557
558    void fileSection() {
559        ignoreLine();
560        while (sdePeek() != '*') {
561            fileLine();
562        }
563    }
564
565    void lineSection() {
566        ignoreLine();
567        while (sdePeek() != '*') {
568            lineLine();
569        }
570    }
571
572    /**
573     * Ignore a section we don't know about.
574     */
575    void ignoreSection() {
576        ignoreLine();
577        while (sdePeek() != '*') {
578            ignoreLine();
579        }
580    }
581
582    /**
583     * A base "Java" stratum is always available, though
584     * it is not in the SourceDebugExtension.
585     * Create the base stratum.
586     */
587    void createJavaStratum() {
588        baseStratumIndex = stratumIndex;
589        storeStratum(BASE_STRATUM_NAME);
590        storeFile(1, jplsFilename, NullString);
591        /* JPL line numbers cannot exceed 65535 */
592        storeLine(1, 65536, 1, 1, 65536, 1);
593        storeStratum("Aux"); /* in case they don't declare */
594    }
595
596    /**
597     * Decode a SourceDebugExtension which is in SourceMap format.
598     * This is the entry point into the recursive descent parser.
599     */
600    void decode() {
601        /* check for "SMAP" - allow EOF if not ours */
602        if ((sourceDebugExtension.length() < 4) ||
603            (sdeRead() != 'S') ||
604            (sdeRead() != 'M') ||
605            (sdeRead() != 'A') ||
606            (sdeRead() != 'P')) {
607            return; /* not our info */
608        }
609        ignoreLine(); /* flush the rest */
610        jplsFilename = readLine();
611        defaultStratumId = readLine();
612        createJavaStratum();
613        while (true) {
614            if (sdeRead() != '*') {
615                syntax();
616            }
617            switch (sdeRead()) {
618                case 'S':
619                    stratumSection();
620                    break;
621                case 'F':
622                    fileSection();
623                    break;
624                case 'L':
625                    lineSection();
626                    break;
627                case 'E':
628                    /* set end points */
629                    storeStratum("*terminator*");
630                    isValid = true;
631                    return;
632                default:
633                    ignoreSection();
634            }
635        }
636    }
637
638    void createProxyForAbsentSDE() {
639        jplsFilename = null;
640        defaultStratumId = BASE_STRATUM_NAME;
641        defaultStratumIndex = stratumIndex;
642        createJavaStratum();
643        storeStratum("*terminator*");
644    }
645
646    /***************** query functions ***********************/
647
648    private int stiLineTableIndex(int sti, int jplsLine) {
649        int i;
650        int lineIndexStart;
651        int lineIndexEnd;
652
653        lineIndexStart = stratumTable[sti].lineIndex;
654        /* one past end */
655        lineIndexEnd = stratumTable[sti+1].lineIndex;
656        for (i = lineIndexStart; i < lineIndexEnd; ++i) {
657            if ((jplsLine >= lineTable[i].jplsStart) &&
658                            (jplsLine <= lineTable[i].jplsEnd)) {
659                return i;
660            }
661        }
662        return -1;
663    }
664
665    private int stiLineNumber(int sti, int lti, int jplsLine) {
666        return lineTable[lti].njplsStart +
667                (((jplsLine - lineTable[lti].jplsStart) /
668                                   lineTable[lti].jplsLineInc));
669    }
670
671    private int fileTableIndex(int sti, int fileId) {
672        int i;
673        int fileIndexStart = stratumTable[sti].fileIndex;
674        /* one past end */
675        int fileIndexEnd = stratumTable[sti+1].fileIndex;
676        for (i = fileIndexStart; i < fileIndexEnd; ++i) {
677            if (fileTable[i].fileId == fileId) {
678                return i;
679            }
680        }
681        return -1;
682    }
683
684    private int stiFileTableIndex(int sti, int lti) {
685        return fileTableIndex(sti, lineTable[lti].fileId);
686    }
687
688    boolean isValid() {
689        return isValid;
690    }
691}
692