View Javadoc
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.ByteArrayInputStream;
22  import java.io.DataInput;
23  import java.io.DataOutputStream;
24  import java.io.IOException;
25  import java.nio.charset.StandardCharsets;
26  import java.util.Objects;
27  
28  import org.apache.bcel.Const;
29  import org.apache.bcel.util.Args;
30  
31  /**
32   * This class is derived from <em>Attribute</em> and represents a reference to a GJ attribute.
33   *
34   * @see Attribute
35   */
36  public final class Signature extends Attribute {
37  
38      /**
39       * Extends ByteArrayInputStream to make 'unreading' chars possible.
40       */
41      private static final class MyByteArrayInputStream extends ByteArrayInputStream {
42  
43          MyByteArrayInputStream(final String data) {
44              super(data.getBytes(StandardCharsets.UTF_8));
45          }
46  
47          String getData() {
48              return new String(buf, StandardCharsets.UTF_8);
49          }
50  
51          void unread() {
52              if (pos > 0) {
53                  pos--;
54              }
55          }
56      }
57  
58      /**
59       * The maximum nesting depth of a signature accepted by {@link #translate(String)}. Guards against a
60       * {@link StackOverflowError} from deeply nested, attacker-supplied generic signatures.
61       */
62      private static final int MAX_NESTING_DEPTH = 512;
63  
64      private static boolean identStart(final int ch) {
65          return ch == 'T' || ch == 'L';
66      }
67  
68      /**
69       * Tests if a string is an actual parameter list.
70       *
71       * @param s The string to test.
72       * @return true if the string is an actual parameter list.
73       * @since 6.0 is no longer final
74       */
75      public static boolean isActualParameterList(final String s) {
76          return s.startsWith("L") && s.endsWith(">;");
77      }
78  
79      /**
80       * Tests if a string is a formal parameter list.
81       *
82       * @param s The string to test.
83       * @return true if the string is a formal parameter list.
84       * @since 6.0 is no longer final
85       */
86      public static boolean isFormalParameterList(final String s) {
87          return s.startsWith("<") && s.indexOf(':') > 0;
88      }
89  
90      private static void matchGJIdent(final MyByteArrayInputStream in, final StringBuilder buf) {
91          matchGJIdent(in, buf, 0);
92      }
93  
94      private static void matchGJIdent(final MyByteArrayInputStream in, final StringBuilder buf, final int depth) {
95          if (depth > MAX_NESTING_DEPTH) {
96              throw new IllegalArgumentException("Illegal signature: " + in.getData() + " exceeds maximum nesting depth " + MAX_NESTING_DEPTH);
97          }
98          int ch;
99          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 }