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 Thrown 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 * @param c The instance to copy.
83 */
84 public LineNumberTable(final LineNumberTable c) {
85 this(c.getNameIndex(), c.getLength(), c.getLineNumberTable(), c.getConstantPool());
86 }
87
88 /**
89 * Called by objects that are traversing the nodes of the tree implicitly defined by the contents of a Java class.
90 * I.e., the hierarchy of methods, fields, attributes, etc. spawns a tree of objects.
91 *
92 * @param v Visitor object.
93 */
94 @Override
95 public void accept(final Visitor v) {
96 v.visitLineNumberTable(this);
97 }
98
99 /**
100 * @return deep copy of this attribute.
101 */
102 @Override
103 public Attribute copy(final ConstantPool constantPool) {
104 // TODO could use the lower level constructor and thereby allow
105 // lineNumberTable to be made final
106 final LineNumberTable c = (LineNumberTable) clone();
107 c.lineNumberTable = new LineNumber[lineNumberTable.length];
108 Arrays.setAll(c.lineNumberTable, i -> lineNumberTable[i].copy());
109 c.setConstantPool(constantPool);
110 return c;
111 }
112
113 /**
114 * Dumps line number table attribute to file stream in binary format.
115 *
116 * @param file Output file stream.
117 * @throws IOException Thrown if an I/O Exception occurs in writeShort.
118 */
119 @Override
120 public void dump(final DataOutputStream file) throws IOException {
121 super.dump(file);
122 file.writeShort(Args.requireU2(lineNumberTable.length, "lineNumberTable.length"));
123 for (final LineNumber lineNumber : lineNumberTable) {
124 lineNumber.dump(file);
125 }
126 }
127
128 /**
129 * Gets the line number table.
130 *
131 * @return Array of (pc offset, line number) pairs.
132 */
133 public LineNumber[] getLineNumberTable() {
134 return lineNumberTable;
135 }
136
137 /**
138 * Map byte code positions to source code lines.
139 *
140 * @param pos byte code offset.
141 * @return corresponding line in source code.
142 */
143 public int getSourceLine(final int pos) {
144 int l = 0;
145 int r = lineNumberTable.length - 1;
146 if (r < 0) {
147 return -1;
148 }
149 int minIndex = -1;
150 int min = -1;
151 /*
152 * Do a binary search since the array is ordered.
153 */
154 do {
155 final int i = l + r >>> 1;
156 final int j = lineNumberTable[i].getStartPC();
157 if (j == pos) {
158 return lineNumberTable[i].getLineNumber();
159 }
160 if (pos < j) {
161 r = i - 1;
162 } else {
163 l = i + 1;
164 }
165 /*
166 * If exact match can't be found (which is the most common case) return the line number that corresponds to the greatest
167 * index less than pos.
168 */
169 if (j < pos && j > min) {
170 min = j;
171 minIndex = i;
172 }
173 } while (l <= r);
174 /*
175 * It's possible that we did not find any valid entry for the bytecode offset we were looking for.
176 */
177 if (minIndex < 0) {
178 return -1;
179 }
180 return lineNumberTable[minIndex].getLineNumber();
181 }
182
183 /**
184 * Gets the length of the line number table.
185 *
186 * @return The length of the line number table.
187 */
188 public int getTableLength() {
189 return lineNumberTable.length;
190 }
191
192 @Override
193 public Iterator<LineNumber> iterator() {
194 return Stream.of(lineNumberTable).iterator();
195 }
196
197 /**
198 * Sets the line number table.
199 *
200 * @param lineNumberTable The line number entries for this table.
201 */
202 public void setLineNumberTable(final LineNumber[] lineNumberTable) {
203 this.lineNumberTable = lineNumberTable != null ? lineNumberTable : LineNumber.EMPTY_ARRAY;
204 }
205
206 /**
207 * @return String representation.
208 */
209 @Override
210 public String toString() {
211 final StringBuilder buf = new StringBuilder();
212 final StringBuilder line = new StringBuilder();
213 final String newLine = SystemProperties.getLineSeparator(() -> "\n");
214 for (int i = 0; i < lineNumberTable.length; i++) {
215 line.append(lineNumberTable[i].toString());
216 if (i < lineNumberTable.length - 1) {
217 line.append(", ");
218 }
219 if (line.length() > MAX_LINE_LENGTH && i < lineNumberTable.length - 1) {
220 line.append(newLine);
221 buf.append(line);
222 line.setLength(0);
223 }
224 }
225 buf.append(line);
226 return buf.toString();
227 }
228 }