View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.bcel.classfile;
20  
21  import java.io.DataInput;
22  import java.io.DataOutputStream;
23  import java.io.IOException;
24  import java.util.Arrays;
25  import java.util.Iterator;
26  import java.util.stream.Stream;
27  
28  import org.apache.bcel.Const;
29  import org.apache.bcel.util.Args;
30  import org.apache.commons.lang3.SystemProperties;
31  
32  /**
33   * This class represents a table of line numbers for debugging purposes. This attribute is used by the <em>Code</em>
34   * attribute. It contains pairs of PCs and line numbers.
35   *
36   * @see Code
37   * @see LineNumber
38   */
39  public final class LineNumberTable extends Attribute implements Iterable<LineNumber> {
40  
41      private static final int MAX_LINE_LENGTH = 72;
42      private LineNumber[] lineNumberTable; // Table of line/numbers pairs
43  
44      /**
45       * Constructs a new instance from a data input stream.
46       *
47       * @param nameIndex Index of name
48       * @param length Content length in bytes
49       * @param input Input stream
50       * @param constantPool Array of constants
51       * @throws IOException if an I/O Exception occurs in readUnsignedShort
52       */
53      LineNumberTable(final int nameIndex, final int length, final DataInput input, final ConstantPool constantPool) throws IOException {
54          this(nameIndex, length, (LineNumber[]) null, constantPool);
55          final int lineNumberTableLength = input.readUnsignedShort();
56          lineNumberTable = new LineNumber[lineNumberTableLength];
57          for (int i = 0; i < lineNumberTableLength; i++) {
58              lineNumberTable[i] = new LineNumber(input);
59          }
60      }
61  
62      /**
63       * Constructs a new instance.
64       *
65       * @param nameIndex Index of name
66       * @param length Content length in bytes
67       * @param lineNumberTable Table of line/numbers pairs
68       * @param constantPool Array of constants
69       */
70      public LineNumberTable(final int nameIndex, final int length, final LineNumber[] lineNumberTable, final ConstantPool constantPool) {
71          super(Const.ATTR_LINE_NUMBER_TABLE, nameIndex, length, constantPool);
72          this.lineNumberTable = lineNumberTable != null ? lineNumberTable : LineNumber.EMPTY_ARRAY;
73          Args.requireU2(this.lineNumberTable.length, "lineNumberTable.length");
74      }
75  
76      /**
77       * Constructs a new instance from another.
78       * <p>
79       * Note that both objects use the same references (shallow copy). Use copy() for a physical copy.
80       * </p>
81       */
82      public LineNumberTable(final LineNumberTable c) {
83          this(c.getNameIndex(), c.getLength(), c.getLineNumberTable(), c.getConstantPool());
84      }
85  
86      /**
87       * Called by objects that are traversing the nodes of the tree implicitly defined by the contents of a Java class.
88       * I.e., the hierarchy of methods, fields, attributes, etc. spawns a tree of objects.
89       *
90       * @param v Visitor object
91       */
92      @Override
93      public void accept(final Visitor v) {
94          v.visitLineNumberTable(this);
95      }
96  
97      /**
98       * @return deep copy of this attribute
99       */
100     @Override
101     public Attribute copy(final ConstantPool constantPool) {
102         // TODO could use the lower level constructor and thereby allow
103         // lineNumberTable to be made final
104         final LineNumberTable c = (LineNumberTable) clone();
105         c.lineNumberTable = new LineNumber[lineNumberTable.length];
106         Arrays.setAll(c.lineNumberTable, i -> lineNumberTable[i].copy());
107         c.setConstantPool(constantPool);
108         return c;
109     }
110 
111     /**
112      * Dump line number table attribute to file stream in binary format.
113      *
114      * @param file Output file stream
115      * @throws IOException if an I/O Exception occurs in writeShort
116      */
117     @Override
118     public void dump(final DataOutputStream file) throws IOException {
119         super.dump(file);
120         file.writeShort(lineNumberTable.length);
121         for (final LineNumber lineNumber : lineNumberTable) {
122             lineNumber.dump(file);
123         }
124     }
125 
126     /**
127      * @return Array of (pc offset, line number) pairs.
128      */
129     public LineNumber[] getLineNumberTable() {
130         return lineNumberTable;
131     }
132 
133     /**
134      * Map byte code positions to source code lines.
135      *
136      * @param pos byte code offset
137      * @return corresponding line in source code
138      */
139     public int getSourceLine(final int pos) {
140         int l = 0;
141         int r = lineNumberTable.length - 1;
142         if (r < 0) {
143             return -1;
144         }
145         int minIndex = -1;
146         int min = -1;
147         /*
148          * Do a binary search since the array is ordered.
149          */
150         do {
151             final int i = l + r >>> 1;
152             final int j = lineNumberTable[i].getStartPC();
153             if (j == pos) {
154                 return lineNumberTable[i].getLineNumber();
155             }
156             if (pos < j) {
157                 r = i - 1;
158             } else {
159                 l = i + 1;
160             }
161             /*
162              * If exact match can't be found (which is the most common case) return the line number that corresponds to the greatest
163              * index less than pos.
164              */
165             if (j < pos && j > min) {
166                 min = j;
167                 minIndex = i;
168             }
169         } while (l <= r);
170         /*
171          * It's possible that we did not find any valid entry for the bytecode offset we were looking for.
172          */
173         if (minIndex < 0) {
174             return -1;
175         }
176         return lineNumberTable[minIndex].getLineNumber();
177     }
178 
179     public int getTableLength() {
180         return lineNumberTable.length;
181     }
182 
183     @Override
184     public Iterator<LineNumber> iterator() {
185         return Stream.of(lineNumberTable).iterator();
186     }
187 
188     /**
189      * @param lineNumberTable the line number entries for this table
190      */
191     public void setLineNumberTable(final LineNumber[] lineNumberTable) {
192         this.lineNumberTable = lineNumberTable != null ? lineNumberTable : LineNumber.EMPTY_ARRAY;
193     }
194 
195     /**
196      * @return String representation.
197      */
198     @Override
199     public String toString() {
200         final StringBuilder buf = new StringBuilder();
201         final StringBuilder line = new StringBuilder();
202         final String newLine = SystemProperties.getLineSeparator(() -> "\n");
203         for (int i = 0; i < lineNumberTable.length; i++) {
204             line.append(lineNumberTable[i].toString());
205             if (i < lineNumberTable.length - 1) {
206                 line.append(", ");
207             }
208             if (line.length() > MAX_LINE_LENGTH && i < lineNumberTable.length - 1) {
209                 line.append(newLine);
210                 buf.append(line);
211                 line.setLength(0);
212             }
213         }
214         buf.append(line);
215         return buf.toString();
216     }
217 }