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 * Dumps 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 }