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.ByteArrayInputStream; 022import java.io.DataInput; 023import java.io.DataOutputStream; 024import java.io.IOException; 025import java.nio.charset.StandardCharsets; 026import java.util.Objects; 027 028import org.apache.bcel.Const; 029import org.apache.bcel.util.Args; 030 031/** 032 * This class is derived from <em>Attribute</em> and represents a reference to a GJ attribute. 033 * 034 * @see Attribute 035 */ 036public final class Signature extends Attribute { 037 038 /** 039 * Extends ByteArrayInputStream to make 'unreading' chars possible. 040 */ 041 private static final class MyByteArrayInputStream extends ByteArrayInputStream { 042 043 MyByteArrayInputStream(final String data) { 044 super(data.getBytes(StandardCharsets.UTF_8)); 045 } 046 047 String getData() { 048 return new String(buf, StandardCharsets.UTF_8); 049 } 050 051 void unread() { 052 if (pos > 0) { 053 pos--; 054 } 055 } 056 } 057 058 /** 059 * The maximum nesting depth of a signature accepted by {@link #translate(String)}. Guards against a 060 * {@link StackOverflowError} from deeply nested, attacker-supplied generic signatures. 061 */ 062 private static final int MAX_NESTING_DEPTH = 512; 063 064 private static boolean identStart(final int ch) { 065 return ch == 'T' || ch == 'L'; 066 } 067 068 /** 069 * Tests if a string is an actual parameter list. 070 * 071 * @param s The string to test. 072 * @return true if the string is an actual parameter list. 073 * @since 6.0 is no longer final 074 */ 075 public static boolean isActualParameterList(final String s) { 076 return s.startsWith("L") && s.endsWith(">;"); 077 } 078 079 /** 080 * Tests if a string is a formal parameter list. 081 * 082 * @param s The string to test. 083 * @return true if the string is a formal parameter list. 084 * @since 6.0 is no longer final 085 */ 086 public static boolean isFormalParameterList(final String s) { 087 return s.startsWith("<") && s.indexOf(':') > 0; 088 } 089 090 private static void matchGJIdent(final MyByteArrayInputStream in, final StringBuilder buf) { 091 matchGJIdent(in, buf, 0); 092 } 093 094 private static void matchGJIdent(final MyByteArrayInputStream in, final StringBuilder buf, final int depth) { 095 if (depth > MAX_NESTING_DEPTH) { 096 throw new IllegalArgumentException("Illegal signature: " + in.getData() + " exceeds maximum nesting depth " + MAX_NESTING_DEPTH); 097 } 098 int ch; 099 matchIdent(in, buf); 100 ch = in.read(); 101 if (ch == '<' || ch == '(') { // Parameterized or method 102 // System.out.println("Enter <"); 103 buf.append((char) ch); 104 matchGJIdent(in, buf, depth + 1); 105 while ((ch = in.read()) != '>' && ch != ')') { // List of parameters 106 if (ch == -1) { 107 throw new IllegalArgumentException("Illegal signature: " + in.getData() + " reaching EOF"); 108 } 109 // System.out.println("Still no >"); 110 buf.append(", "); 111 in.unread(); 112 matchGJIdent(in, buf, depth + 1); // Recursive call 113 } 114 // System.out.println("Exit >"); 115 buf.append((char) ch); 116 } else { 117 in.unread(); 118 } 119 ch = in.read(); 120 if (identStart(ch)) { 121 in.unread(); 122 matchGJIdent(in, buf, depth + 1); 123 } else if (ch == ')') { 124 in.unread(); 125 } else if (ch != ';') { 126 throw new IllegalArgumentException("Illegal signature: " + in.getData() + " read " + (char) ch); 127 } 128 } 129 130 private static void matchIdent(final MyByteArrayInputStream in, final StringBuilder buf) { 131 int ch; 132 if ((ch = in.read()) == -1) { 133 throw new IllegalArgumentException("Illegal signature: " + in.getData() + " no ident, reaching EOF"); 134 } 135 // System.out.println("return from ident:" + (char) ch); 136 if (!identStart(ch)) { 137 final StringBuilder buf2 = new StringBuilder(); 138 int count = 1; 139 while (Character.isJavaIdentifierPart((char) ch)) { 140 buf2.append((char) ch); 141 count++; 142 ch = in.read(); 143 } 144 if (ch == ':') { // Ok, formal parameter 145 final int skipExpected = "Ljava/lang/Object".length(); 146 final long skipActual = in.skip(skipExpected); 147 if (skipActual != skipExpected) { 148 throw new IllegalStateException(String.format("Unexpected skip: expected=%,d, actual=%,d", skipExpected, skipActual)); 149 } 150 buf.append(buf2); 151 ch = in.read(); 152 in.unread(); 153 // System.out.println("so far:" + buf2 + ":next:" +(char) ch); 154 } else { 155 for (int i = 0; i < count; i++) { 156 in.unread(); 157 } 158 } 159 return; 160 } 161 final StringBuilder buf2 = new StringBuilder(); 162 ch = in.read(); 163 do { 164 buf2.append((char) ch); 165 ch = in.read(); 166 // System.out.println("within ident:"+ (char) ch); 167 } while (ch != -1 && (Character.isJavaIdentifierPart((char) ch) || ch == '/')); 168 buf.append(Utility.pathToPackage(buf2.toString())); 169 // System.out.println("regular return ident:"+ (char) ch + ":" + buf2); 170 if (ch != -1) { 171 in.unread(); 172 } 173 } 174 175 /** 176 * Translates a signature string. 177 * 178 * @param s The signature string. 179 * @return The translated signature. 180 */ 181 public static String translate(final String s) { 182 // System.out.println("Sig:" + s); 183 final StringBuilder buf = new StringBuilder(); 184 matchGJIdent(new MyByteArrayInputStream(s), buf); 185 return buf.toString(); 186 } 187 188 private int signatureIndex; 189 190 /** 191 * Constructs object from file stream. 192 * 193 * @param nameIndex Index in constant pool to CONSTANT_Utf8. 194 * @param length Content length in bytes. 195 * @param input Input stream. 196 * @param constantPool Array of constants. 197 * @throws IOException Thrown if an I/O error occurs. 198 */ 199 Signature(final int nameIndex, final int length, final DataInput input, final ConstantPool constantPool) throws IOException { 200 this(nameIndex, length, input.readUnsignedShort(), constantPool); 201 } 202 203 /** 204 * Constructs a Signature. 205 * 206 * @param nameIndex Index in constant pool to CONSTANT_Utf8. 207 * @param length Content length in bytes. 208 * @param signatureIndex Index in constant pool to CONSTANT_Utf8. 209 * @param constantPool Array of constants. 210 */ 211 public Signature(final int nameIndex, final int length, final int signatureIndex, final ConstantPool constantPool) { 212 super(Const.ATTR_SIGNATURE, nameIndex, Args.require(length, 2, "Signature length attribute"), constantPool); 213 this.signatureIndex = signatureIndex; 214 // validate: 215 Objects.requireNonNull(constantPool.getConstantUtf8(signatureIndex), "constantPool.getConstantUtf8(signatureIndex)"); 216 } 217 218 /** 219 * Initialize from another object. Note that both objects use the same references (shallow copy). Use clone() for a 220 * physical copy. 221 * 222 * @param c Source to copy. 223 */ 224 public Signature(final Signature c) { 225 this(c.getNameIndex(), c.getLength(), c.getSignatureIndex(), c.getConstantPool()); 226 } 227 228 /** 229 * Called by objects that are traversing the nodes of the tree implicitly defined by the contents of a Java class. 230 * I.e., the hierarchy of methods, fields, attributes, etc. spawns a tree of objects. 231 * 232 * @param v Visitor object. 233 */ 234 @Override 235 public void accept(final Visitor v) { 236 // System.err.println("Visiting non-standard Signature object"); 237 v.visitSignature(this); 238 } 239 240 /** 241 * @return deep copy of this attribute. 242 */ 243 @Override 244 public Attribute copy(final ConstantPool constantPool) { 245 return (Attribute) clone(); 246 } 247 248 /** 249 * Dumps source file attribute to file stream in binary format. 250 * 251 * @param file Output file stream. 252 * @throws IOException Thrown if an I/O error occurs. 253 */ 254 @Override 255 public void dump(final DataOutputStream file) throws IOException { 256 super.dump(file); 257 file.writeShort(signatureIndex); 258 } 259 260 /** 261 * Gets the GJ signature. 262 * 263 * @return GJ signature. 264 */ 265 public String getSignature() { 266 return super.getConstantPool().getConstantUtf8(signatureIndex).getBytes(); 267 } 268 269 /** 270 * Gets the signature index. 271 * 272 * @return Index in constant pool of source file name. 273 */ 274 public int getSignatureIndex() { 275 return signatureIndex; 276 } 277 278 /** 279 * Sets the signature index. 280 * 281 * @param signatureIndex The index info the constant pool of this signature. 282 */ 283 public void setSignatureIndex(final int signatureIndex) { 284 this.signatureIndex = signatureIndex; 285 } 286 287 /** 288 * @return String representation. 289 */ 290 @Override 291 public String toString() { 292 return "Signature: " + getSignature(); 293 } 294}