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}