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 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 public LineNumberTable(final LineNumberTable c) { 083 this(c.getNameIndex(), c.getLength(), c.getLineNumberTable(), c.getConstantPool()); 084 } 085 086 /** 087 * Called by objects that are traversing the nodes of the tree implicitly defined by the contents of a Java class. 088 * I.e., the hierarchy of methods, fields, attributes, etc. spawns a tree of objects. 089 * 090 * @param v Visitor object 091 */ 092 @Override 093 public void accept(final Visitor v) { 094 v.visitLineNumberTable(this); 095 } 096 097 /** 098 * @return deep copy of this attribute 099 */ 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}