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.verifier.structurals;
020
021import java.util.ArrayList;
022
023import org.apache.bcel.generic.ObjectType;
024import org.apache.bcel.generic.ReferenceType;
025import org.apache.bcel.generic.Type;
026import org.apache.bcel.verifier.exc.AssertionViolatedException;
027import org.apache.bcel.verifier.exc.StructuralCodeConstraintException;
028
029/**
030 * This class implements a stack used for symbolic JVM stack simulation. [It's used as an operand stack substitute.]
031 * Elements of this stack are {@link Type} objects.
032 */
033public class OperandStack implements Cloneable {
034
035    /** We hold the stack information here. */
036    private ArrayList<Type> stack = new ArrayList<>();
037
038    /** The maximum number of stack slots this OperandStack instance may hold. */
039    private final int maxStack;
040
041    /**
042     * Creates an empty stack with a maximum of maxStack slots.
043     *
044     * @param maxStack The maximum stack size.
045     */
046    public OperandStack(final int maxStack) {
047        this.maxStack = maxStack;
048    }
049
050    /**
051     * Creates an otherwise empty stack with a maximum of maxStack slots and the ObjectType 'obj' at the top.
052     *
053     * @param maxStack The maximum stack size.
054     * @param obj The object type to place at the top.
055     */
056    public OperandStack(final int maxStack, final ObjectType obj) {
057        this.maxStack = maxStack;
058        push(obj);
059    }
060
061    /**
062     * Clears the stack.
063     */
064    public void clear() {
065        stack = new ArrayList<>();
066    }
067
068    /**
069     * Returns a deep copy of this object; that means, the clone operates on a new stack. However, the Type objects on the
070     * stack are shared.
071     */
072    @Override
073    public Object clone() {
074        final OperandStack newstack = new OperandStack(this.maxStack);
075        @SuppressWarnings("unchecked") // OK because this.stack is the same type
076        final ArrayList<Type> clone = (ArrayList<Type>) this.stack.clone();
077        newstack.stack = clone;
078        return newstack;
079    }
080
081    /**
082     * Returns true if and only if this OperandStack equals another, meaning equal lengths and equal objects on the stacks.
083     */
084    @Override
085    public boolean equals(final Object o) {
086        if (!(o instanceof OperandStack)) {
087            return false;
088        }
089        final OperandStack s = (OperandStack) o;
090        return this.stack.equals(s.stack);
091    }
092
093    /**
094     * Returns a (typed!) clone of this.
095     *
096     * @return A clone of this operand stack.
097     * @see #clone()
098     */
099    public OperandStack getClone() {
100        return (OperandStack) clone();
101    }
102
103    /**
104     * Gets the hash code.
105     *
106     * @return A hash code value for the object.
107     */
108    @Override
109    public int hashCode() {
110        return stack.hashCode();
111    }
112
113    /**
114     * Replaces all occurrences of u in this OperandStack instance with an "initialized" ObjectType.
115     *
116     * @param u The uninitialized object type.
117     */
118    public void initializeObject(final UninitializedObjectType u) {
119        for (int i = 0; i < stack.size(); i++) {
120            if (stack.get(i) == u) {
121                stack.set(i, u.getInitialized());
122            }
123        }
124    }
125
126    /**
127     * Returns true IFF this OperandStack is empty.
128     *
129     * @return true if empty, false otherwise.
130     */
131    public boolean isEmpty() {
132        return stack.isEmpty();
133    }
134
135    /**
136     * Returns the number of stack slots this stack can hold.
137     *
138     * @return The maximum stack size.
139     */
140    public int maxStack() {
141        return this.maxStack;
142    }
143
144    /**
145     * Merges another stack state into this instance's stack state. See the Java Virtual Machine Specification, Second
146     * Edition, page 146: 4.9.2 for details.
147     *
148     * @param s The stack to merge.
149     */
150    public void merge(final OperandStack s) {
151        try {
152            if (slotsUsed() != s.slotsUsed() || size() != s.size()) {
153                throw new StructuralCodeConstraintException("Cannot merge stacks of different size:\nOperandStack A:\n" + this + "\nOperandStack B:\n" + s);
154            }
155
156            for (int i = 0; i < size(); i++) {
157                // If the object _was_ initialized and we're supposed to merge
158                // in some uninitialized object, we reject the code (see vmspec2, 4.9.4, last paragraph).
159                if (!(stack.get(i) instanceof UninitializedObjectType) && s.stack.get(i) instanceof UninitializedObjectType) {
160                    throw new StructuralCodeConstraintException("Backwards branch with an uninitialized object on the stack detected.");
161                }
162                // Even harder, we're not initialized but are supposed to broaden
163                // the known object type
164                if (!stack.get(i).equals(s.stack.get(i)) && stack.get(i) instanceof UninitializedObjectType
165                    && !(s.stack.get(i) instanceof UninitializedObjectType)) {
166                    throw new StructuralCodeConstraintException("Backwards branch with an uninitialized object on the stack detected.");
167                }
168                // on the other hand...
169                if (stack.get(i) instanceof UninitializedObjectType && !(s.stack.get(i) instanceof UninitializedObjectType)) { // that has been initialized by
170                                                                                                                               // now
171                    stack.set(i, ((UninitializedObjectType) stack.get(i)).getInitialized()); // note that.
172                }
173                if (!stack.get(i).equals(s.stack.get(i))) {
174                    if (!(stack.get(i) instanceof ReferenceType) || !(s.stack.get(i) instanceof ReferenceType)) {
175                        throw new StructuralCodeConstraintException("Cannot merge stacks of different types:\nStack A:\n" + this + "\nStack B:\n" + s);
176                    }
177                    stack.set(i, ((ReferenceType) stack.get(i)).getFirstCommonSuperclass((ReferenceType) s.stack.get(i)));
178                }
179            }
180        } catch (final ClassNotFoundException e) {
181            // FIXME: maybe not the best way to handle this
182            throw new AssertionViolatedException("Missing class: " + e, e);
183        }
184    }
185
186    /**
187     * Returns the element on top of the stack. The element is not popped off the stack!
188     *
189     * @return The top element.
190     */
191    public Type peek() {
192        return peek(0);
193    }
194
195    /**
196     * Returns the element that's i elements below the top element; that means, iff i==0 the top element is returned. The
197     * element is not popped off the stack!
198     *
199     * @param depth The depth.
200     * @return The element at the specified depth.
201     */
202    public Type peek(final int depth) {
203        return stack.get(size() - depth - 1);
204    }
205
206    /**
207     * Returns the element on top of the stack. The element is popped off the stack.
208     *
209     * @return The popped element.
210     */
211    public Type pop() {
212        return stack.remove(size() - 1);
213    }
214
215    /**
216     * Pops i elements off the stack. Always returns null.
217     *
218     * @param count The number of elements to pop.
219     * @return Always returns null.
220     */
221    public Type pop(final int count) {
222        for (int j = 0; j < count; j++) {
223            pop();
224        }
225        return null;
226    }
227
228    /**
229     * Pushes a Type object onto the stack.
230     *
231     * @param type The type to push.
232     */
233    public void push(final Type type) {
234        if (type == null) {
235            throw new AssertionViolatedException("Cannot push NULL onto OperandStack.");
236        }
237        if (type == Type.BOOLEAN || type == Type.CHAR || type == Type.BYTE || type == Type.SHORT) {
238            throw new AssertionViolatedException("The OperandStack does not know about '" + type + "'; use Type.INT instead.");
239        }
240        if (slotsUsed() >= maxStack) {
241            throw new AssertionViolatedException("OperandStack too small, should have thrown proper Exception elsewhere. Stack: " + this);
242        }
243        stack.add(type);
244    }
245
246    /**
247     * Returns the size of this OperandStack; that means, how many Type objects there are.
248     *
249     * @return The stack size.
250     */
251    public int size() {
252        return stack.size();
253    }
254
255    /**
256     * Returns the number of stack slots used.
257     *
258     * @return The number of slots used.
259     * @see #maxStack()
260     */
261    public int slotsUsed() {
262        /*
263         * XXX change this to a better implementation using a variable that keeps track of the actual slotsUsed()-value
264         * monitoring all push()es and pop()s.
265         */
266        int slots = 0;
267        for (int i = 0; i < stack.size(); i++) {
268            slots += peek(i).getSize();
269        }
270        return slots;
271    }
272
273    /**
274     * Returns a String representation of this OperandStack instance.
275     *
276     * @return string representation.
277     */
278    @Override
279    public String toString() {
280        final StringBuilder sb = new StringBuilder();
281        sb.append("Slots used: ");
282        sb.append(slotsUsed());
283        sb.append(" MaxStack: ");
284        sb.append(maxStack);
285        sb.append(".\n");
286        for (int i = 0; i < size(); i++) {
287            sb.append(peek(i));
288            sb.append(" (Size: ");
289            sb.append(String.valueOf(peek(i).getSize()));
290            sb.append(")\n");
291        }
292        return sb.toString();
293    }
294
295}