1 /*
2  * Copyright (c) 2000, 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 
26 package com.sun.tools.jdi;
27 
28 import java.lang.ref.SoftReference;
29 import java.util.ArrayList;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.Iterator;
33 import java.util.List;
34 import java.util.Map;
35 
36 import com.sun.jdi.AbsentInformationException;
37 import com.sun.jdi.LocalVariable;
38 import com.sun.jdi.Location;
39 import com.sun.jdi.VirtualMachine;
40 import com.sun.tools.jdi.JDWP.Method.VariableTable;
41 import com.sun.tools.jdi.JDWP.Method.VariableTableWithGeneric;
42 
43 /**
44  * Represents methods with method bodies.
45  * That is, non-native non-abstract methods.
46  * Private to MethodImpl.
47  */
48 public class ConcreteMethodImpl extends MethodImpl {
49 
50     /*
51      * A subset of the line number info that is softly cached
52      */
53     static private class SoftLocationXRefs {
54         final String stratumID;                        // The stratum of this information
55         final Map<Integer, List<Location>> lineMapper; // Maps line number to location(s)
56         final List<Location> lineLocations;            // List of locations ordered by code index
57 
58         /*
59          * Note: these do not necessarily correspond to
60          * the line numbers of the first and last elements
61          * in the lineLocations list. Use these only for bounds
62          * checking and with lineMapper.
63          */
64         @SuppressWarnings("unused")
65         final int lowestLine;
66         @SuppressWarnings("unused")
67         final int highestLine;
68 
SoftLocationXRefs(String stratumID, Map<Integer, List<Location>> lineMapper, List<Location> lineLocations, int lowestLine, int highestLine)69         SoftLocationXRefs(String stratumID, Map<Integer, List<Location>> lineMapper,
70                           List<Location> lineLocations, int lowestLine, int highestLine) {
71             this.stratumID = stratumID;
72             this.lineMapper = Collections.unmodifiableMap(lineMapper);
73             this.lineLocations = Collections.unmodifiableList(lineLocations);
74             this.lowestLine = lowestLine;
75             this.highestLine = highestLine;
76         }
77     }
78 
79     private Location location = null;
80     private SoftReference<SoftLocationXRefs> softBaseLocationXRefsRef;
81     private SoftReference<SoftLocationXRefs> softOtherLocationXRefsRef;
82     private SoftReference<List<LocalVariable>> variablesRef = null;
83     private boolean absentVariableInformation = false;
84     private long firstIndex = -1;
85     private long lastIndex = -1;
86     private SoftReference<byte[]> bytecodesRef = null;
87     private int argSlotCount = -1;
88 
ConcreteMethodImpl(VirtualMachine vm, ReferenceTypeImpl declaringType, long ref, String name, String signature, String genericSignature, int modifiers)89     ConcreteMethodImpl(VirtualMachine vm, ReferenceTypeImpl declaringType,
90                        long ref, String name, String signature,
91                        String genericSignature, int modifiers)
92     {
93         // The generic signature is set when this is created
94         super(vm, declaringType, ref, name, signature, genericSignature, modifiers);
95     }
96 
location()97     public Location location() {
98         if (location == null) {
99             getBaseLocations();
100         }
101         return location;
102     }
103 
sourceNameFilter(List<Location> list, SDE.Stratum stratum, String sourceName)104     List<Location> sourceNameFilter(List<Location> list,
105                                     SDE.Stratum stratum,
106                                     String sourceName)
107                             throws AbsentInformationException {
108         if (sourceName == null) {
109             return list;
110         } else {
111             /* needs sourceName filteration */
112             List<Location> locs = new ArrayList<>();
113             for (Location loc : list) {
114                 if (((LocationImpl)loc).sourceName(stratum).equals(sourceName)) {
115                     locs.add(loc);
116                 }
117             }
118             return locs;
119         }
120     }
121 
allLineLocations(SDE.Stratum stratum, String sourceName)122     List<Location> allLineLocations(SDE.Stratum stratum,
123                                     String sourceName)
124                             throws AbsentInformationException {
125         List<Location> lineLocations = getLocations(stratum).lineLocations;
126 
127         if (lineLocations.size() == 0) {
128             throw new AbsentInformationException();
129         }
130 
131         return Collections.unmodifiableList(
132             sourceNameFilter(lineLocations, stratum, sourceName));
133     }
134 
locationsOfLine(SDE.Stratum stratum, String sourceName, int lineNumber)135     List<Location> locationsOfLine(SDE.Stratum stratum,
136                                    String sourceName,
137                                    int lineNumber)
138                             throws AbsentInformationException {
139         SoftLocationXRefs info = getLocations(stratum);
140 
141         if (info.lineLocations.size() == 0) {
142             throw new AbsentInformationException();
143         }
144 
145         /*
146          * Find the locations which match the line number
147          * passed in.
148          */
149         List<Location> list = info.lineMapper.get(lineNumber);
150 
151         if (list == null) {
152             list = new ArrayList<>(0);
153         }
154         return Collections.unmodifiableList(
155             sourceNameFilter(list, stratum, sourceName));
156     }
157 
locationOfCodeIndex(long codeIndex)158     public Location locationOfCodeIndex(long codeIndex) {
159         if (firstIndex == -1) {
160             getBaseLocations();
161         }
162 
163         /*
164          * Check for invalid code index.
165          */
166         if (codeIndex < firstIndex || codeIndex > lastIndex) {
167             return null;
168         }
169 
170         return new LocationImpl(virtualMachine(), this, codeIndex);
171     }
172 
codeIndexToLineInfo(SDE.Stratum stratum, long codeIndex)173     LineInfo codeIndexToLineInfo(SDE.Stratum stratum,
174                                  long codeIndex) {
175         if (firstIndex == -1) {
176             getBaseLocations();
177         }
178 
179         /*
180          * Check for invalid code index.
181          */
182         if (codeIndex < firstIndex || codeIndex > lastIndex) {
183             throw new InternalError("Location with invalid code index");
184         }
185 
186         List<Location> lineLocations = getLocations(stratum).lineLocations;
187 
188         /*
189          * Check for absent line numbers.
190          */
191         if (lineLocations.size() == 0) {
192             return super.codeIndexToLineInfo(stratum, codeIndex);
193         }
194 
195         Iterator<Location> iter = lineLocations.iterator();
196         /*
197          * Treat code before the beginning of the first line table
198          * entry as part of the first line.  javac will generate
199          * code like this for some local classes. This "prolog"
200          * code contains assignments from locals in the enclosing
201          * scope to synthetic fields in the local class.  Same for
202          * other language prolog code.
203          */
204         LocationImpl bestMatch = (LocationImpl)iter.next();
205         while (iter.hasNext()) {
206             LocationImpl current = (LocationImpl)iter.next();
207             if (current.codeIndex() > codeIndex) {
208                 break;
209             }
210             bestMatch = current;
211         }
212         return bestMatch.getLineInfo(stratum);
213     }
214 
variables()215     public List<LocalVariable> variables() throws AbsentInformationException {
216         return getVariables();
217     }
218 
variablesByName(String name)219     public List<LocalVariable> variablesByName(String name) throws AbsentInformationException {
220         List<LocalVariable> variables = getVariables();
221 
222         List<LocalVariable> retList = new ArrayList<>(2);
223         Iterator<LocalVariable> iter = variables.iterator();
224         while(iter.hasNext()) {
225             LocalVariable variable = iter.next();
226             if (variable.name().equals(name)) {
227                 retList.add(variable);
228             }
229         }
230         return retList;
231     }
232 
arguments()233     public List<LocalVariable> arguments() throws AbsentInformationException {
234         List<LocalVariable> variables = getVariables();
235 
236         List<LocalVariable> retList = new ArrayList<>(variables.size());
237         Iterator<LocalVariable> iter = variables.iterator();
238         while(iter.hasNext()) {
239             LocalVariable variable = iter.next();
240             if (variable.isArgument()) {
241                 retList.add(variable);
242             }
243         }
244         return retList;
245     }
246 
bytecodes()247     public byte[] bytecodes() {
248         byte[] bytecodes = (bytecodesRef == null) ? null :
249                                      bytecodesRef.get();
250         if (bytecodes == null) {
251             try {
252                 bytecodes = JDWP.Method.Bytecodes.
253                                  process(vm, declaringType, ref).bytes;
254             } catch (JDWPException exc) {
255                 throw exc.toJDIException();
256             }
257             bytecodesRef = new SoftReference<>(bytecodes);
258         }
259         /*
260          * Arrays are always modifiable, so it is a little unsafe
261          * to return the cached bytecodes directly; instead, we
262          * make a clone at the cost of using more memory.
263          */
264         return bytecodes.clone();
265     }
266 
argSlotCount()267     int argSlotCount() throws AbsentInformationException {
268         if (argSlotCount == -1) {
269             getVariables();
270         }
271         return argSlotCount;
272     }
273 
getLocations(SDE.Stratum stratum)274     private SoftLocationXRefs getLocations(SDE.Stratum stratum) {
275         if (stratum.isJava()) {
276             return getBaseLocations();
277         }
278         String stratumID = stratum.id();
279         SoftLocationXRefs info =
280             (softOtherLocationXRefsRef == null) ? null :
281                softOtherLocationXRefsRef.get();
282         if (info != null && info.stratumID.equals(stratumID)) {
283             return info;
284         }
285 
286         List<Location> lineLocations = new ArrayList<Location>();
287         Map<Integer, List<Location>> lineMapper = new HashMap<>();
288         int lowestLine = -1;
289         int highestLine = -1;
290         SDE.LineStratum lastLineStratum = null;
291         SDE.Stratum baseStratum = declaringType.stratum(SDE.BASE_STRATUM_NAME);
292         Iterator<Location> it = getBaseLocations().lineLocations.iterator();
293         while(it.hasNext()) {
294             LocationImpl loc = (LocationImpl)it.next();
295             int baseLineNumber = loc.lineNumber(baseStratum);
296             SDE.LineStratum lineStratum =
297                   stratum.lineStratum(declaringType, baseLineNumber);
298 
299             if (lineStratum == null) {
300                 // location not mapped in this stratum
301                 continue;
302             }
303 
304             int lineNumber = lineStratum.lineNumber();
305 
306             // remove unmapped and dup lines
307             if ((lineNumber != -1) &&
308                           (!lineStratum.equals(lastLineStratum))) {
309                 lastLineStratum = lineStratum;
310 
311                 // Remember the largest/smallest line number
312                 if (lineNumber > highestLine) {
313                     highestLine = lineNumber;
314                 }
315                 if ((lineNumber < lowestLine) || (lowestLine == -1)) {
316                     lowestLine = lineNumber;
317                 }
318 
319                 loc.addStratumLineInfo(
320                     new StratumLineInfo(stratumID,
321                                         lineNumber,
322                                         lineStratum.sourceName(),
323                                         lineStratum.sourcePath()));
324 
325                 // Add to the location list
326                 lineLocations.add(loc);
327 
328                 // Add to the line -> locations map
329                 Integer key = lineNumber;
330                 List<Location> mappedLocs = lineMapper.get(key);
331                 if (mappedLocs == null) {
332                     mappedLocs = new ArrayList<Location>(1);
333                     lineMapper.put(key, mappedLocs);
334                 }
335                 mappedLocs.add(loc);
336             }
337         }
338 
339         info = new SoftLocationXRefs(stratumID, lineMapper, lineLocations,
340                                      lowestLine, highestLine);
341         softOtherLocationXRefsRef = new SoftReference<>(info);
342         return info;
343     }
344 
getBaseLocations()345     private SoftLocationXRefs getBaseLocations() {
346         SoftLocationXRefs info = (softBaseLocationXRefsRef == null) ? null :
347                                      softBaseLocationXRefsRef.get();
348         if (info != null) {
349             return info;
350         }
351 
352         JDWP.Method.LineTable lntab = null;
353         try {
354             lntab = JDWP.Method.LineTable.process(vm, declaringType, ref);
355         } catch (JDWPException exc) {
356             /*
357              * Note: the absent info error shouldn't happen here
358              * because the first and last index are always available.
359              */
360             throw exc.toJDIException();
361         }
362 
363         int count  = lntab.lines.length;
364 
365         List<Location> lineLocations = new ArrayList<>(count);
366         Map<Integer, List<Location>>lineMapper = new HashMap<>();
367         int lowestLine = -1;
368         int highestLine = -1;
369         for (int i = 0; i < count; i++) {
370             long bci = lntab.lines[i].lineCodeIndex;
371             int lineNumber = lntab.lines[i].lineNumber;
372 
373             /*
374              * Some compilers will point multiple consecutive
375              * lines at the same location. We need to choose
376              * one of them so that we can consistently map back
377              * and forth between line and location. So we choose
378              * to record only the last line entry at a particular
379              * location.
380              */
381             if ((i + 1 == count) || (bci != lntab.lines[i+1].lineCodeIndex)) {
382                 // Remember the largest/smallest line number
383                 if (lineNumber > highestLine) {
384                     highestLine = lineNumber;
385                 }
386                 if ((lineNumber < lowestLine) || (lowestLine == -1)) {
387                     lowestLine = lineNumber;
388                 }
389                 LocationImpl loc =
390                     new LocationImpl(virtualMachine(), this, bci);
391                 loc.addBaseLineInfo(
392                     new BaseLineInfo(lineNumber, declaringType));
393 
394                 // Add to the location list
395                 lineLocations.add(loc);
396 
397                 // Add to the line -> locations map
398                 Integer key = lineNumber;
399                 List<Location> mappedLocs = lineMapper.get(key);
400                 if (mappedLocs == null) {
401                     mappedLocs = new ArrayList<>(1);
402                     lineMapper.put(key, mappedLocs);
403                 }
404                 mappedLocs.add(loc);
405             }
406         }
407 
408         /*
409          * firstIndex, lastIndex, and startLocation need to be
410          * retrieved only once since they are strongly referenced.
411          */
412         if (location == null) {
413             firstIndex = lntab.start;
414             lastIndex = lntab.end;
415             /*
416              * The startLocation is the first one in the
417              * location list if we have one;
418              * otherwise, we construct a location for a
419              * method start with no line info
420              */
421             if (count > 0) {
422                 location = lineLocations.get(0);
423             } else {
424                 location = new LocationImpl(virtualMachine(), this,
425                                             firstIndex);
426             }
427         }
428 
429         info = new SoftLocationXRefs(SDE.BASE_STRATUM_NAME,
430                                      lineMapper, lineLocations,
431                                      lowestLine, highestLine);
432         softBaseLocationXRefsRef = new SoftReference<SoftLocationXRefs>(info);
433         return info;
434     }
435 
getVariables1_4()436     private List<LocalVariable> getVariables1_4() throws AbsentInformationException {
437         JDWP.Method.VariableTable vartab = null;
438         try {
439             vartab = JDWP.Method.VariableTable.
440                                      process(vm, declaringType, ref);
441         } catch (JDWPException exc) {
442             if (exc.errorCode() == JDWP.Error.ABSENT_INFORMATION) {
443                 absentVariableInformation = true;
444                 throw new AbsentInformationException();
445             } else {
446                 throw exc.toJDIException();
447             }
448         }
449 
450         // Get the number of slots used by argument variables
451         argSlotCount = vartab.argCnt;
452         int count = vartab.slots.length;
453         List<LocalVariable> variables = new ArrayList<>(count);
454         for (int i=0; i<count; i++) {
455             JDWP.Method.VariableTable.SlotInfo si = vartab.slots[i];
456 
457             /*
458              * Skip "this*" entries because they are never real
459              * variables from the JLS perspective.
460              */
461             if (!si.name.startsWith("this$") && !si.name.equals("this")) {
462                 Location scopeStart = new LocationImpl(virtualMachine(),
463                                                        this, si.codeIndex);
464                 Location scopeEnd =
465                     new LocationImpl(virtualMachine(), this,
466                                      si.codeIndex + si.length - 1);
467                 LocalVariable variable =
468                     new LocalVariableImpl(virtualMachine(), this,
469                                           si.slot, scopeStart, scopeEnd,
470                                           si.name, si.signature, null);
471                 // Add to the variable list
472                 variables.add(variable);
473             }
474         }
475         return variables;
476     }
477 
getVariables1()478     private List<LocalVariable> getVariables1() throws AbsentInformationException {
479 
480         if (!vm.canGet1_5LanguageFeatures()) {
481             return getVariables1_4();
482         }
483 
484         JDWP.Method.VariableTableWithGeneric vartab = null;
485         try {
486             vartab = JDWP.Method.VariableTableWithGeneric.
487                                      process(vm, declaringType, ref);
488         } catch (JDWPException exc) {
489             if (exc.errorCode() == JDWP.Error.ABSENT_INFORMATION) {
490                 absentVariableInformation = true;
491                 throw new AbsentInformationException();
492             } else {
493                 throw exc.toJDIException();
494             }
495         }
496 
497         // Get the number of slots used by argument variables
498         argSlotCount = vartab.argCnt;
499         int count = vartab.slots.length;
500         List<LocalVariable> variables = new ArrayList<LocalVariable>(count);
501         for (int i=0; i<count; i++) {
502             JDWP.Method.VariableTableWithGeneric.SlotInfo si = vartab.slots[i];
503 
504             /*
505              * Skip "this*" entries because they are never real
506              * variables from the JLS perspective.
507              */
508             if (!si.name.startsWith("this$") && !si.name.equals("this")) {
509                 Location scopeStart = new LocationImpl(virtualMachine(),
510                                                        this, si.codeIndex);
511                 Location scopeEnd =
512                     new LocationImpl(virtualMachine(), this,
513                                      si.codeIndex + si.length - 1);
514                 LocalVariable variable =
515                     new LocalVariableImpl(virtualMachine(), this,
516                                           si.slot, scopeStart, scopeEnd,
517                                           si.name, si.signature,
518                                           si.genericSignature);
519                 // Add to the variable list
520                 variables.add(variable);
521             }
522         }
523         return variables;
524     }
525 
getVariables()526     private List<LocalVariable> getVariables() throws AbsentInformationException {
527         if (absentVariableInformation) {
528             throw new AbsentInformationException();
529         }
530 
531         List<LocalVariable> variables = (variablesRef == null) ? null :
532                                          variablesRef.get();
533         if (variables != null) {
534             return variables;
535         }
536         variables = getVariables1();
537         variables = Collections.unmodifiableList(variables);
538         variablesRef = new SoftReference<>(variables);
539         return variables;
540     }
541 }
542