001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * https://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.bcel.classfile; 020 021import java.io.DataInput; 022import java.io.DataOutputStream; 023import java.io.IOException; 024import java.util.Arrays; 025import java.util.Iterator; 026import java.util.stream.Stream; 027 028import org.apache.bcel.Const; 029import org.apache.bcel.util.Args; 030import org.apache.commons.lang3.SystemProperties; 031 032/** 033 * This class represents a table of line numbers for debugging purposes. This attribute is used by the <em>Code</em> 034 * attribute. It contains pairs of PCs and line numbers. 035 * 036 * @see Code 037 * @see LineNumber 038 */ 039public final class LineNumberTable extends Attribute implements Iterable<LineNumber> { 040 041 private static final int MAX_LINE_LENGTH = 72; 042 private LineNumber[] lineNumberTable; // Table of line/numbers pairs 043 044 /** 045 * Constructs a new instance from a data input stream. 046 * 047 * @param nameIndex Index of name. 048 * @param length Content length in bytes. 049 * @param input Input stream. 050 * @param constantPool Array of constants. 051 * @throws IOException Thrown if an I/O Exception occurs in readUnsignedShort. 052 */ 053 LineNumberTable(final int nameIndex, final int length, final DataInput input, final ConstantPool constantPool) throws IOException { 054 this(nameIndex, length, (LineNumber[]) null, constantPool); 055 final int lineNumberTableLength = input.readUnsignedShort(); 056 lineNumberTable = new LineNumber[lineNumberTableLength]; 057 for (int i = 0; i < lineNumberTableLength; i++) { 058 lineNumberTable[i] = new LineNumber(input); 059 } 060 } 061 062 /** 063 * Constructs a new instance. 064 * 065 * @param nameIndex Index of name. 066 * @param length Content length in bytes. 067 * @param lineNumberTable Table of line/numbers pairs. 068 * @param constantPool Array of constants. 069 */ 070 public LineNumberTable(final int nameIndex, final int length, final LineNumber[] lineNumberTable, final ConstantPool constantPool) { 071 super(Const.ATTR_LINE_NUMBER_TABLE, nameIndex, length, constantPool); 072 this.lineNumberTable = lineNumberTable != null ? lineNumberTable : LineNumber.EMPTY_ARRAY; 073 Args.requireU2(this.lineNumberTable.length, "lineNumberTable.length"); 074 } 075 076 /** 077 * Constructs a new instance from another. 078 * <p> 079 * Note that both objects use the same references (shallow copy). Use copy() for a physical copy. 080 * </p> 081 * 082 * @param c The instance to copy. 083 */ 084 public LineNumberTable(final LineNumberTable c) { 085 this(c.getNameIndex(), c.getLength(), c.getLineNumberTable(), c.getConstantPool()); 086 } 087 088 /** 089 * Called by objects that are traversing the nodes of the tree implicitly defined by the contents of a Java class. 090 * I.e., the hierarchy of methods, fields, attributes, etc. spawns a tree of objects. 091 * 092 * @param v Visitor object. 093 */ 094 @Override 095 public void accept(final Visitor v) { 096 v.visitLineNumberTable(this); 097 } 098 099 /** 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}