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
26package com.sun.tools.jdi;
27
28import java.lang.ref.SoftReference;
29import java.util.ArrayList;
30import java.util.Collections;
31import java.util.HashMap;
32import java.util.Iterator;
33import java.util.List;
34import java.util.Map;
35
36import com.sun.jdi.AbsentInformationException;
37import com.sun.jdi.LocalVariable;
38import com.sun.jdi.Location;
39import com.sun.jdi.VirtualMachine;
40import com.sun.tools.jdi.JDWP.Method.VariableTable;
41import 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 */
48public 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
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
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
97    public Location location() {
98        if (location == null) {
99            getBaseLocations();
100        }
101        return location;
102    }
103
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
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
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
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
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
215    public List<LocalVariable> variables() throws AbsentInformationException {
216        return getVariables();
217    }
218
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
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
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
267    int argSlotCount() throws AbsentInformationException {
268        if (argSlotCount == -1) {
269            getVariables();
270        }
271        return argSlotCount;
272    }
273
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
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
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
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
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