aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/apache/bcel/verifier/structurals/OperandStack.java
blob: 9b1e94d9be00979b2fab6bda379134d4e8e0ec66 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */
package org.apache.bcel.verifier.structurals;


import java.util.ArrayList;

import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.ReferenceType;
import org.apache.bcel.generic.Type;
import org.apache.bcel.verifier.exc.AssertionViolatedException;
import org.apache.bcel.verifier.exc.StructuralCodeConstraintException;

/**
 * This class implements a stack used for symbolic JVM stack simulation.
 * [It's used an an operand stack substitute.]
 * Elements of this stack are {@link Type} objects.
 *
 * @version $Id$
 */
public class OperandStack implements Cloneable {

    /** We hold the stack information here. */
    private ArrayList<Type> stack = new ArrayList<>();

    /** The maximum number of stack slots this OperandStack instance may hold. */
    private final int maxStack;

    /**
     * Creates an empty stack with a maximum of maxStack slots.
     */
    public OperandStack(final int maxStack) {
        this.maxStack = maxStack;
    }

    /**
     * Creates an otherwise empty stack with a maximum of maxStack slots and
     * the ObjectType 'obj' at the top.
     */
    public OperandStack(final int maxStack, final ObjectType obj) {
        this.maxStack = maxStack;
        this.push(obj);
    }
    /**
     * Returns a deep copy of this object; that means, the clone operates
     * on a new stack. However, the Type objects on the stack are
     * shared.
     */
    @Override
    public Object clone() {
        final OperandStack newstack = new OperandStack(this.maxStack);
        @SuppressWarnings("unchecked") // OK because this.stack is the same type
        final ArrayList<Type> clone = (ArrayList<Type>) this.stack.clone();
        newstack.stack = clone;
        return newstack;
    }

    /**
     * Clears the stack.
     */
    public void clear() {
        stack = new ArrayList<>();
    }

    /** @return a hash code value for the object.
     */
    @Override
    public int hashCode() { return stack.hashCode(); }

    /**
     * Returns true if and only if this OperandStack
     * equals another, meaning equal lengths and equal
     * objects on the stacks.
     */
    @Override
    public boolean equals(final Object o) {
        if (!(o instanceof OperandStack)) {
            return false;
        }
        final OperandStack s = (OperandStack) o;
        return this.stack.equals(s.stack);
    }

    /**
     * Returns a (typed!) clone of this.
     *
     * @see #clone()
     */
    public OperandStack getClone() {
        return (OperandStack) this.clone();
    }

    /**
     * Returns true IFF this OperandStack is empty.
   */
    public boolean isEmpty() {
        return stack.isEmpty();
    }

    /**
     * Returns the number of stack slots this stack can hold.
     */
    public int maxStack() {
        return this.maxStack;
    }

    /**
     * Returns the element on top of the stack. The element is not popped off the stack!
     */
    public Type peek() {
        return peek(0);
    }

    /**
   * Returns the element that's i elements below the top element; that means,
   * iff i==0 the top element is returned. The element is not popped off the stack!
   */
    public Type peek(final int i) {
        return stack.get(size()-i-1);
    }

    /**
     * Returns the element on top of the stack. The element is popped off the stack.
     */
    public Type pop() {
        final Type e = stack.remove(size()-1);
        return e;
    }

    /**
     * Pops i elements off the stack. ALWAYS RETURNS "null"!!!
     */
    public Type pop(final int i) {
        for (int j=0; j<i; j++) {
            pop();
        }
        return null;
    }

    /**
     * Pushes a Type object onto the stack.
     */
    public void push(final Type type) {
        if (type == null) {
            throw new AssertionViolatedException("Cannot push NULL onto OperandStack.");
        }
        if (type == Type.BOOLEAN || type == Type.CHAR || type == Type.BYTE || type == Type.SHORT) {
            throw new AssertionViolatedException("The OperandStack does not know about '"+type+"'; use Type.INT instead.");
        }
        if (slotsUsed() >= maxStack) {
            throw new AssertionViolatedException(
                "OperandStack too small, should have thrown proper Exception elsewhere. Stack: "+this);
        }
        stack.add(type);
    }

    /**
     * Returns the size of this OperandStack; that means, how many Type objects there are.
     */
    public int size() {
        return stack.size();
    }

    /**
     * Returns the number of stack slots used.
     * @see #maxStack()
     */
    public int slotsUsed() {
        /*  XXX change this to a better implementation using a variable
            that keeps track of the actual slotsUsed()-value monitoring
            all push()es and pop()s.
        */
        int slots = 0;
        for (int i=0; i<stack.size(); i++) {
            slots += peek(i).getSize();
        }
        return slots;
    }

    /**
     * Returns a String representation of this OperandStack instance.
     */
    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder();
        sb.append("Slots used: ");
        sb.append(slotsUsed());
        sb.append(" MaxStack: ");
        sb.append(maxStack);
        sb.append(".\n");
        for (int i=0; i<size(); i++) {
            sb.append(peek(i));
            sb.append(" (Size: ");
            sb.append(String.valueOf(peek(i).getSize()));
            sb.append(")\n");
        }
        return sb.toString();
    }

    /**
     * Merges another stack state into this instance's stack state.
     * See the Java Virtual Machine Specification, Second Edition, page 146: 4.9.2
     * for details.
     */
    public void merge(final OperandStack s) {
        try {
        if ( (slotsUsed() != s.slotsUsed()) || (size() != s.size()) ) {
            throw new StructuralCodeConstraintException(
                "Cannot merge stacks of different size:\nOperandStack A:\n"+this+"\nOperandStack B:\n"+s);
        }

        for (int i=0; i<size(); i++) {
            // If the object _was_ initialized and we're supposed to merge
            // in some uninitialized object, we reject the code (see vmspec2, 4.9.4, last paragraph).
            if ( (! (stack.get(i) instanceof UninitializedObjectType)) && (s.stack.get(i) instanceof UninitializedObjectType) ) {
                throw new StructuralCodeConstraintException("Backwards branch with an uninitialized object on the stack detected.");
            }
            // Even harder, we're not initialized but are supposed to broaden
            // the known object type
            if ( (!(stack.get(i).equals(s.stack.get(i)))) &&
                    (stack.get(i) instanceof UninitializedObjectType) && (!(s.stack.get(i) instanceof UninitializedObjectType))) {
                throw new StructuralCodeConstraintException("Backwards branch with an uninitialized object on the stack detected.");
            }
            // on the other hand...
            if (stack.get(i) instanceof UninitializedObjectType) { //if we have an uninitialized object here
                if (! (s.stack.get(i) instanceof UninitializedObjectType)) { //that has been initialized by now
                    stack.set(i, ((UninitializedObjectType) (stack.get(i))).getInitialized() ); //note that.
                }
            }
            if (! stack.get(i).equals(s.stack.get(i))) {
                if (    (stack.get(i) instanceof ReferenceType) &&
                            (s.stack.get(i) instanceof ReferenceType)  ) {
                    stack.set(i, ((ReferenceType) stack.get(i)).getFirstCommonSuperclass((ReferenceType) (s.stack.get(i))));
                }
                else{
                    throw new StructuralCodeConstraintException(
                        "Cannot merge stacks of different types:\nStack A:\n"+this+"\nStack B:\n"+s);
                }
            }
        }
        } catch (final ClassNotFoundException e) {
        // FIXME: maybe not the best way to handle this
        throw new AssertionViolatedException("Missing class: " + e, e);
        }
    }

    /**
     * Replaces all occurences of u in this OperandStack instance
     * with an "initialized" ObjectType.
     */
    public void initializeObject(final UninitializedObjectType u) {
        for (int i=0; i<stack.size(); i++) {
            if (stack.get(i) == u) {
                stack.set(i, u.getInitialized());
            }
        }
    }

}