1 /*
2  * Copyright (c) 2001, 2016, 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 
26 #include <setjmp.h>
27 
28 #include "util.h"
29 #include "SDE.h"
30 
31 #ifdef __APPLE__
32 /* use setjmp/longjmp versions that do not save/restore the signal mask */
33 #define setjmp _setjmp
34 #define longjmp _longjmp
35 #endif
36 
37 /**
38  * This SourceDebugExtension code does not
39  * allow concurrent translation - due to caching method.
40  * A separate thread setting the default stratum ID
41  * is, however, fine.
42  */
43 
44 #define INIT_SIZE_FILE 10
45 #define INIT_SIZE_LINE 100
46 #define INIT_SIZE_STRATUM 3
47 
48 #define BASE_STRATUM_NAME "Java"
49 
50 #define null NULL
51 #define String char *
52 #define private static
53 
54 typedef struct {
55   int fileId;
56   String sourceName;
57   String sourcePath; // do not read - use accessor
58   int isConverted;
59 } FileTableRecord;
60 
61 typedef struct {
62     int jplsStart;
63     int jplsEnd;
64     int jplsLineInc;
65     int njplsStart;
66     int njplsEnd;
67     int fileId;
68 } LineTableRecord;
69 
70 typedef struct {
71     String id;
72     int fileIndex;
73     int lineIndex;
74 } StratumTableRecord;
75 
76 /* back-end wide value for default stratum */
77 private String globalDefaultStratumId = null;
78 
79 /* reference type default */
80 private String defaultStratumId = null;
81 
82 private jclass cachedClass = NULL;
83 
84 private FileTableRecord* fileTable;
85 private LineTableRecord* lineTable;
86 private StratumTableRecord* stratumTable;
87 
88 private int fileTableSize;
89 private int lineTableSize;
90 private int stratumTableSize;
91 
92 private int fileIndex;
93 private int lineIndex;
94 private int stratumIndex = 0;
95 private int currentFileId;
96 
97 private int defaultStratumIndex;
98 private int baseStratumIndex;
99 private char* sdePos;
100 
101 private char* jplsFilename = null;
102 private char* NullString = null;
103 
104 /* mangled in parse, cannot be parsed.  Must be kept. */
105 private String sourceDebugExtension;
106 
107 private jboolean sourceMapIsValid;
108 
109 private jmp_buf jmp_buf_env;
110 
111 private int stratumTableIndex(String stratumId);
112 private int stiLineTableIndex(int sti, int jplsLine);
113 private int stiLineNumber(int sti, int lti, int jplsLine);
114 private void decode(void);
115 private void ignoreWhite(void);
116 private jboolean isValid(void);
117 
118     private void
loadDebugInfo(JNIEnv * env,jclass clazz)119     loadDebugInfo(JNIEnv *env, jclass clazz) {
120 
121         if (!isSameObject(env, clazz, cachedClass)) {
122             /* Not the same - swap out the info */
123 
124             /* Delete existing info */
125             if ( cachedClass != null ) {
126                 tossGlobalRef(env, &cachedClass);
127                 cachedClass = null;
128             }
129             if ( sourceDebugExtension!=null ) {
130                 jvmtiDeallocate(sourceDebugExtension);
131             }
132             sourceDebugExtension = null;
133 
134             /* Init info */
135             lineTable = null;
136             fileTable = null;
137             stratumTable = null;
138             lineTableSize = 0;
139             fileTableSize = 0;
140             stratumTableSize = 0;
141             fileIndex = 0;
142             lineIndex = 0;
143             stratumIndex = 0;
144             currentFileId = 0;
145             defaultStratumId = null;
146             defaultStratumIndex = -1;
147             baseStratumIndex = -2; /* so as not to match -1 above */
148             sourceMapIsValid = JNI_FALSE;
149 
150             if (getSourceDebugExtension(clazz, &sourceDebugExtension) ==
151                 JVMTI_ERROR_NONE) {
152                 sdePos = sourceDebugExtension;
153                 if (setjmp(jmp_buf_env) == 0) {
154                     /* this is the initial (non-error) case, do parse */
155                     decode();
156                 }
157             }
158 
159             cachedClass = null;
160             saveGlobalRef(env, clazz, &cachedClass);
161         }
162     }
163 
164     /* Return 1 if match, 0 if no match */
165     private int
patternMatch(char * classname,const char * pattern)166     patternMatch(char *classname, const char *pattern) {
167         int pattLen;
168         int compLen;
169         char *start;
170         int offset;
171 
172         if (pattern == NULL || classname == NULL) {
173             return 0;
174         }
175         pattLen = (int)strlen(pattern);
176 
177         if ((pattern[0] != '*') && (pattern[pattLen-1] != '*')) {
178             return strcmp(pattern, classname) == 0;
179         }
180 
181         compLen = pattLen - 1;
182         offset = (int)strlen(classname) - compLen;
183         if (offset < 0) {
184             return 0;
185         }
186         if (pattern[0] == '*') {
187             pattern++;
188             start = classname + offset;
189         }  else {
190             start = classname;
191         }
192         return strncmp(pattern, start, compLen) == 0;
193     }
194 
195     /**
196      * Return 1 if p1 is a SourceName for stratum sti,
197      * else, return 0.
198      */
199     private int
searchOneSourceName(int sti,char * p1)200     searchOneSourceName(int sti, char *p1) {
201         int fileIndexStart = stratumTable[sti].fileIndex;
202         /* one past end */
203         int fileIndexEnd = stratumTable[sti+1].fileIndex;
204         int ii;
205         for (ii = fileIndexStart; ii < fileIndexEnd; ++ii) {
206             if (patternMatch(fileTable[ii].sourceName, p1)) {
207               return 1;
208             }
209         }
210         return 0;
211     }
212 
213     /**
214      * Return 1 if p1 is a SourceName for any stratum
215      * else, return 0.
216      */
searchAllSourceNames(JNIEnv * env,jclass clazz,char * p1)217     int searchAllSourceNames(JNIEnv *env,
218                              jclass clazz,
219                              char *p1) {
220         int ii;
221         loadDebugInfo(env, clazz);
222         if (!isValid()) {
223           return 0; /* no SDE or not SourceMap */
224         }
225 
226         for (ii = 0; ii < stratumIndex - 1; ++ii) {
227             if (searchOneSourceName(ii, p1) == 1) {
228                 return 1;
229             }
230         }
231         return 0;
232     }
233 
234     /**
235      * Convert a line number table, as returned by the JVMTI
236      * function GetLineNumberTable, to one for another stratum.
237      * Conversion is by overwrite.
238      * Actual line numbers are not returned - just a unique
239      * number (file ID in top 16 bits, line number in
240      * bottom 16 bits) - this is all stepping needs.
241      */
242     void
convertLineNumberTable(JNIEnv * env,jclass clazz,jint * entryCountPtr,jvmtiLineNumberEntry ** tablePtr)243     convertLineNumberTable(JNIEnv *env, jclass clazz,
244                            jint *entryCountPtr,
245                            jvmtiLineNumberEntry **tablePtr) {
246         jvmtiLineNumberEntry *fromEntry = *tablePtr;
247         jvmtiLineNumberEntry *toEntry = *tablePtr;
248         int cnt = *entryCountPtr;
249         int lastLn = 0;
250         int sti;
251 
252         if (cnt < 0) {
253             return;
254         }
255         loadDebugInfo(env, clazz);
256         if (!isValid()) {
257             return; /* no SDE or not SourceMap - return unchanged */
258         }
259         sti = stratumTableIndex(globalDefaultStratumId);
260         if (sti == baseStratumIndex || sti < 0) {
261             return; /* Java stratum - return unchanged */
262         }
263         LOG_MISC(("SDE is re-ordering the line table"));
264         for (; cnt-- > 0; ++fromEntry) {
265             int jplsLine = fromEntry->line_number;
266             int lti = stiLineTableIndex(sti, jplsLine);
267             if (lti >= 0) {
268                 int fileId = lineTable[lti].fileId;
269                 int ln = stiLineNumber(sti, lti, jplsLine);
270                 ln += (fileId << 16); /* create line hash */
271                 if (ln != lastLn) {
272                     lastLn = ln;
273                     toEntry->start_location = fromEntry->start_location;
274                     toEntry->line_number = ln;
275                     ++toEntry;
276                 }
277             }
278         }
279         /*LINTED*/
280         *entryCountPtr = (int)(toEntry - *tablePtr);
281     }
282 
283     /**
284      * Set back-end wide default stratum ID .
285      */
286     void
setGlobalStratumId(char * id)287     setGlobalStratumId(char *id) {
288         globalDefaultStratumId = id;
289     }
290 
291 
syntax(String msg)292     private void syntax(String msg) {
293         char buf[200];
294         (void)snprintf(buf, sizeof(buf),
295                 "bad SourceDebugExtension syntax - position %d - %s\n",
296                 /*LINTED*/
297                 (int)(sdePos-sourceDebugExtension),
298                 msg);
299         JDI_ASSERT_FAILED(buf);
300 
301         longjmp(jmp_buf_env, 1);  /* abort parse */
302     }
303 
sdePeek(void)304     private char sdePeek(void) {
305         if (*sdePos == 0) {
306             syntax("unexpected EOF");
307         }
308         return *sdePos;
309     }
310 
sdeRead(void)311     private char sdeRead(void) {
312         if (*sdePos == 0) {
313             syntax("unexpected EOF");
314         }
315         return *sdePos++;
316     }
317 
sdeAdvance(void)318     private void sdeAdvance(void) {
319         sdePos++;
320     }
321 
assureLineTableSize(void)322     private void assureLineTableSize(void) {
323         if (lineIndex >= lineTableSize) {
324             size_t allocSize;
325             LineTableRecord* new_lineTable;
326             int new_lineTableSize;
327 
328             new_lineTableSize = lineTableSize == 0?
329                                   INIT_SIZE_LINE :
330                                   lineTableSize * 2;
331             allocSize = new_lineTableSize * (int)sizeof(LineTableRecord);
332             new_lineTable = jvmtiAllocate((jint)allocSize);
333             if ( new_lineTable == NULL ) {
334                 EXIT_ERROR(AGENT_ERROR_OUT_OF_MEMORY, "SDE line table");
335             }
336             if ( lineTable!=NULL ) {
337                 (void)memcpy(new_lineTable, lineTable,
338                         lineTableSize * (int)sizeof(LineTableRecord));
339                 jvmtiDeallocate(lineTable);
340             }
341             lineTable     = new_lineTable;
342             lineTableSize = new_lineTableSize;
343         }
344     }
345 
assureFileTableSize(void)346     private void assureFileTableSize(void) {
347         if (fileIndex >= fileTableSize) {
348             size_t allocSize;
349             FileTableRecord* new_fileTable;
350             int new_fileTableSize;
351 
352             new_fileTableSize = fileTableSize == 0?
353                                   INIT_SIZE_FILE :
354                                   fileTableSize * 2;
355             allocSize = new_fileTableSize * (int)sizeof(FileTableRecord);
356             new_fileTable = jvmtiAllocate((jint)allocSize);
357             if ( new_fileTable == NULL ) {
358                 EXIT_ERROR(AGENT_ERROR_OUT_OF_MEMORY, "SDE file table");
359             }
360             if ( fileTable!=NULL ) {
361                 (void)memcpy(new_fileTable, fileTable,
362                         fileTableSize * (int)sizeof(FileTableRecord));
363                 jvmtiDeallocate(fileTable);
364             }
365             fileTable     = new_fileTable;
366             fileTableSize = new_fileTableSize;
367         }
368     }
369 
assureStratumTableSize(void)370     private void assureStratumTableSize(void) {
371         if (stratumIndex >= stratumTableSize) {
372             size_t allocSize;
373             StratumTableRecord* new_stratumTable;
374             int new_stratumTableSize;
375 
376             new_stratumTableSize = stratumTableSize == 0?
377                                   INIT_SIZE_STRATUM :
378                                   stratumTableSize * 2;
379             allocSize = new_stratumTableSize * (int)sizeof(StratumTableRecord);
380             new_stratumTable = jvmtiAllocate((jint)allocSize);
381             if ( new_stratumTable == NULL ) {
382                 EXIT_ERROR(AGENT_ERROR_OUT_OF_MEMORY, "SDE stratum table");
383             }
384             if ( stratumTable!=NULL ) {
385                 (void)memcpy(new_stratumTable, stratumTable,
386                         stratumTableSize * (int)sizeof(StratumTableRecord));
387                 jvmtiDeallocate(stratumTable);
388             }
389             stratumTable     = new_stratumTable;
390             stratumTableSize = new_stratumTableSize;
391         }
392     }
393 
readLine(void)394     private String readLine(void) {
395         char *initialPos;
396         char ch;
397 
398         ignoreWhite();
399         initialPos = sdePos;
400         while (((ch = *sdePos) != '\n') && (ch != '\r')) {
401             if (ch == 0) {
402                 syntax("unexpected EOF");
403             }
404             ++sdePos;
405         }
406         *sdePos++ = 0; /* null terminate string - mangles SDE */
407 
408         /* check for CR LF */
409         if ((ch == '\r') && (*sdePos == '\n')) {
410             ++sdePos;
411         }
412         ignoreWhite(); /* leading white */
413         return initialPos;
414     }
415 
defaultStratumTableIndex(void)416     private int defaultStratumTableIndex(void) {
417         if ((defaultStratumIndex == -1) && (defaultStratumId != null)) {
418             defaultStratumIndex =
419                 stratumTableIndex(defaultStratumId);
420         }
421         return defaultStratumIndex;
422     }
423 
stratumTableIndex(String stratumId)424     private int stratumTableIndex(String stratumId) {
425         int i;
426 
427         if (stratumId == null) {
428             return defaultStratumTableIndex();
429         }
430         for (i = 0; i < (stratumIndex-1); ++i) {
431             if (strcmp(stratumTable[i].id, stratumId) == 0) {
432                 return i;
433             }
434         }
435         return defaultStratumTableIndex();
436     }
437 
438 
439 /*****************************
440  * below functions/methods are written to compile under either Java or C
441  *
442  * Needed support functions:
443  *   sdePeek()
444  *   sdeRead()
445  *   sdeAdvance()
446  *   readLine()
447  *   assureLineTableSize()
448  *   assureFileTableSize()
449  *   assureStratumTableSize()
450  *   syntax(String)
451  *
452  *   stratumTableIndex(String)
453  *
454  * Needed support variables:
455  *   lineTable
456  *   lineIndex
457  *   fileTable
458  *   fileIndex
459  *   currentFileId
460  *
461  * Needed types:
462  *   String
463  *
464  * Needed constants:
465  *   NullString
466  */
467 
ignoreWhite(void)468     private void ignoreWhite(void) {
469         char ch;
470 
471         while (((ch = sdePeek()) == ' ') || (ch == '\t')) {
472             sdeAdvance();
473         }
474     }
475 
ignoreLine(void)476     private void ignoreLine(void) {
477         char ch;
478 
479         do {
480            ch = sdeRead();
481         } while ((ch != '\n') && (ch != '\r'));
482 
483         /* check for CR LF */
484         if ((ch == '\r') && (sdePeek() == '\n')) {
485             sdeAdvance();
486         }
487         ignoreWhite(); /* leading white */
488     }
489 
readNumber(void)490     private int readNumber(void) {
491         int value = 0;
492         char ch;
493 
494         ignoreWhite();
495         while (((ch = sdePeek()) >= '0') && (ch <= '9')) {
496             sdeAdvance();
497             value = (value * 10) + ch - '0';
498         }
499         ignoreWhite();
500         return value;
501     }
502 
storeFile(int fileId,String sourceName,String sourcePath)503     private void storeFile(int fileId, String sourceName, String sourcePath) {
504         assureFileTableSize();
505         fileTable[fileIndex].fileId = fileId;
506         fileTable[fileIndex].sourceName = sourceName;
507         fileTable[fileIndex].sourcePath = sourcePath;
508         ++fileIndex;
509     }
510 
fileLine(void)511     private void fileLine(void) {
512         int hasAbsolute = 0; /* acts as boolean */
513         int fileId;
514         String sourceName;
515         String sourcePath = null;
516 
517         /* is there an absolute filename? */
518         if (sdePeek() == '+') {
519             sdeAdvance();
520             hasAbsolute = 1;
521         }
522         fileId = readNumber();
523         sourceName = readLine();
524         if (hasAbsolute == 1) {
525             sourcePath = readLine();
526         }
527         storeFile(fileId, sourceName, sourcePath);
528     }
529 
storeLine(int jplsStart,int jplsEnd,int jplsLineInc,int njplsStart,int njplsEnd,int fileId)530     private void storeLine(int jplsStart, int jplsEnd, int jplsLineInc,
531                   int njplsStart, int njplsEnd, int fileId) {
532         assureLineTableSize();
533         lineTable[lineIndex].jplsStart = jplsStart;
534         lineTable[lineIndex].jplsEnd = jplsEnd;
535         lineTable[lineIndex].jplsLineInc = jplsLineInc;
536         lineTable[lineIndex].njplsStart = njplsStart;
537         lineTable[lineIndex].njplsEnd = njplsEnd;
538         lineTable[lineIndex].fileId = fileId;
539         ++lineIndex;
540     }
541 
542     /**
543      * Parse line translation info.  Syntax is
544      *     <NJ-start-line> [ # <file-id> ] [ , <line-count> ] :
545      *                 <J-start-line> [ , <line-increment> ] CR
546      */
lineLine(void)547     private void lineLine(void) {
548         int lineCount = 1;
549         int lineIncrement = 1;
550         int njplsStart;
551         int jplsStart;
552 
553         njplsStart = readNumber();
554 
555         /* is there a fileID? */
556         if (sdePeek() == '#') {
557             sdeAdvance();
558             currentFileId = readNumber();
559         }
560 
561         /* is there a line count? */
562         if (sdePeek() == ',') {
563             sdeAdvance();
564             lineCount = readNumber();
565         }
566 
567         if (sdeRead() != ':') {
568             syntax("expected ':'");
569         }
570         jplsStart = readNumber();
571         if (sdePeek() == ',') {
572             sdeAdvance();
573             lineIncrement = readNumber();
574         }
575         ignoreLine(); /* flush the rest */
576 
577         storeLine(jplsStart,
578                   jplsStart + (lineCount * lineIncrement) -1,
579                   lineIncrement,
580                   njplsStart,
581                   njplsStart + lineCount -1,
582                   currentFileId);
583     }
584 
585     /**
586      * Until the next stratum section, everything after this
587      * is in stratumId - so, store the current indicies.
588      */
storeStratum(String stratumId)589     private void storeStratum(String stratumId) {
590         /* remove redundant strata */
591         if (stratumIndex > 0) {
592             if ((stratumTable[stratumIndex-1].fileIndex
593                                             == fileIndex) &&
594                 (stratumTable[stratumIndex-1].lineIndex
595                                             == lineIndex)) {
596                 /* nothing changed overwrite it */
597                 --stratumIndex;
598             }
599         }
600         /* store the results */
601         assureStratumTableSize();
602         stratumTable[stratumIndex].id = stratumId;
603         stratumTable[stratumIndex].fileIndex = fileIndex;
604         stratumTable[stratumIndex].lineIndex = lineIndex;
605         ++stratumIndex;
606         currentFileId = 0;
607     }
608 
609     /**
610      * The beginning of a stratum's info
611      */
stratumSection(void)612     private void stratumSection(void) {
613         storeStratum(readLine());
614     }
615 
fileSection(void)616     private void fileSection(void) {
617         ignoreLine();
618         while (sdePeek() != '*') {
619             fileLine();
620         }
621     }
622 
lineSection(void)623     private void lineSection(void) {
624         ignoreLine();
625         while (sdePeek() != '*') {
626             lineLine();
627         }
628     }
629 
630     /**
631      * Ignore a section we don't know about.
632      */
ignoreSection(void)633     private void ignoreSection(void) {
634         ignoreLine();
635         while (sdePeek() != '*') {
636             ignoreLine();
637         }
638     }
639 
640     /**
641      * A base "Java" stratum is always available, though
642      * it is not in the SourceDebugExtension.
643      * Create the base stratum.
644      */
createJavaStratum(void)645     private void createJavaStratum(void) {
646         baseStratumIndex = stratumIndex;
647         storeStratum(BASE_STRATUM_NAME);
648         storeFile(1, jplsFilename, NullString);
649         /* JPL line numbers cannot exceed 65535 */
650         storeLine(1, 65536, 1, 1, 65536, 1);
651         storeStratum("Aux"); /* in case they don't declare */
652     }
653 
654     /**
655      * Decode a SourceDebugExtension which is in SourceMap format.
656      * This is the entry point into the recursive descent parser.
657      */
decode(void)658     private void decode(void) {
659         /* check for "SMAP" - allow EOF if not ours */
660         if (strlen(sourceDebugExtension) <= 4 ||
661             (sdeRead() != 'S') ||
662             (sdeRead() != 'M') ||
663             (sdeRead() != 'A') ||
664             (sdeRead() != 'P')) {
665             return; /* not our info */
666         }
667         ignoreLine(); /* flush the rest */
668         jplsFilename = readLine();
669         defaultStratumId = readLine();
670         createJavaStratum();
671         while (1) {
672             if (sdeRead() != '*') {
673                 syntax("expected '*'");
674             }
675             switch (sdeRead()) {
676                 case 'S':
677                     stratumSection();
678                     break;
679                 case 'F':
680                     fileSection();
681                     break;
682                 case 'L':
683                     lineSection();
684                     break;
685                 case 'E':
686                     /* set end points */
687                     storeStratum("*terminator*");
688                     sourceMapIsValid = JNI_TRUE;
689                     return;
690                 default:
691                     ignoreSection();
692             }
693         }
694     }
695 
696     /***************** query functions ***********************/
697 
stiLineTableIndex(int sti,int jplsLine)698     private int stiLineTableIndex(int sti, int jplsLine) {
699         int i;
700         int lineIndexStart;
701         int lineIndexEnd;
702 
703         lineIndexStart = stratumTable[sti].lineIndex;
704         /* one past end */
705         lineIndexEnd = stratumTable[sti+1].lineIndex;
706         for (i = lineIndexStart; i < lineIndexEnd; ++i) {
707             if ((jplsLine >= lineTable[i].jplsStart) &&
708                             (jplsLine <= lineTable[i].jplsEnd)) {
709                 return i;
710             }
711         }
712         return -1;
713     }
714 
stiLineNumber(int sti,int lti,int jplsLine)715     private int stiLineNumber(int sti, int lti, int jplsLine) {
716         return lineTable[lti].njplsStart +
717                 (((jplsLine - lineTable[lti].jplsStart) /
718                                    lineTable[lti].jplsLineInc));
719     }
720 
fileTableIndex(int sti,int fileId)721     private int fileTableIndex(int sti, int fileId) {
722         int i;
723         int fileIndexStart = stratumTable[sti].fileIndex;
724         /* one past end */
725         int fileIndexEnd = stratumTable[sti+1].fileIndex;
726         for (i = fileIndexStart; i < fileIndexEnd; ++i) {
727             if (fileTable[i].fileId == fileId) {
728                 return i;
729             }
730         }
731         return -1;
732     }
733 
stiFileTableIndex(int sti,int lti)734     private int stiFileTableIndex(int sti, int lti) {
735         return fileTableIndex(sti, lineTable[lti].fileId);
736     }
737 
isValid(void)738     private jboolean isValid(void) {
739         return sourceMapIsValid;
740     }
741