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.verifier.structurals;
20  
21  import java.util.ArrayList;
22  import java.util.Arrays;
23  import java.util.HashMap;
24  import java.util.List;
25  import java.util.Map;
26  
27  import org.apache.bcel.generic.ATHROW;
28  import org.apache.bcel.generic.BranchInstruction;
29  import org.apache.bcel.generic.GotoInstruction;
30  import org.apache.bcel.generic.Instruction;
31  import org.apache.bcel.generic.InstructionHandle;
32  import org.apache.bcel.generic.JsrInstruction;
33  import org.apache.bcel.generic.MethodGen;
34  import org.apache.bcel.generic.RET;
35  import org.apache.bcel.generic.ReturnInstruction;
36  import org.apache.bcel.generic.Select;
37  import org.apache.bcel.verifier.exc.AssertionViolatedException;
38  import org.apache.bcel.verifier.exc.StructuralCodeConstraintException;
39  
40  /**
41   * This class represents a control flow graph of a method.
42   */
43  public class ControlFlowGraph {
44  
45      /**
46       * Objects of this class represent a node in a ControlFlowGraph. These nodes are instructions, not basic blocks.
47       */
48      private final class InstructionContextImpl implements InstructionContext {
49  
50          /**
51           * The TAG field is here for external temporary flagging, such as graph coloring.
52           *
53           * @see #getTag()
54           * @see #setTag(int)
55           */
56          private int TAG;
57  
58          /**
59           * The InstructionHandle this InstructionContext is wrapped around.
60           */
61          private final InstructionHandle instruction;
62  
63          /**
64           * The 'incoming' execution Frames.
65           */
66          private final Map<InstructionContext, Frame> inFrames; // key: the last-executed JSR
67  
68          /**
69           * The 'outgoing' execution Frames.
70           */
71          private final Map<InstructionContext, Frame> outFrames; // key: the last-executed JSR
72  
73          /**
74           * The 'execution predecessors' - a list of type InstructionContext of those instances that have been execute()d before
75           * in that order.
76           */
77          private List<InstructionContext> executionPredecessors; // Type: InstructionContext
78  
79          /**
80           * Creates an InstructionHandleImpl object from an InstructionHandle. Creation of one per InstructionHandle suffices.
81           * Don't create more.
82           */
83          InstructionContextImpl(final InstructionHandle inst) {
84              if (inst == null) {
85                  throw new AssertionViolatedException("Cannot instantiate InstructionContextImpl from NULL.");
86              }
87              instruction = inst;
88              inFrames = new HashMap<>();
89              outFrames = new HashMap<>();
90          }
91  
92          /**
93           * A utility method that calculates the successors of a given InstructionHandle That means, a RET does have successors
94           * as defined here. A JsrInstruction has its target as its successor (opposed to its physical successor) as defined
95           * here.
96           */
97  // TODO: implement caching!
98          private InstructionHandle[] _getSuccessors() {
99              final InstructionHandle[] single = new InstructionHandle[1];
100 
101             final Instruction inst = getInstruction().getInstruction();
102 
103             if (inst instanceof RET) {
104                 final Subroutine s = subroutines.subroutineOf(getInstruction());
105                 if (s == null) { // return empty;
106                     // RET in dead code. "empty" would be the correct answer, but we know something about the surrounding project...
107                     throw new AssertionViolatedException("Asking for successors of a RET in dead code?!");
108                 }
109 
110 //TODO: remove. Only JustIce must not use it, but foreign users of the ControlFlowGraph
111 //      will want it. Thanks Johannes Wust.
112 //throw new AssertionViolatedException("DID YOU REALLY WANT TO ASK FOR RET'S SUCCS?");
113 
114                 final InstructionHandle[] jsrs = s.getEnteringJsrInstructions();
115                 final InstructionHandle[] ret = new InstructionHandle[jsrs.length];
116                 Arrays.setAll(ret, i -> jsrs[i].getNext());
117                 return ret;
118             }
119 
120             // Terminates method normally.
121             // Terminates method abnormally, because JustIce mandates
122             // subroutines not to be protected by exception handlers.
123             if (inst instanceof ReturnInstruction || inst instanceof ATHROW) {
124                 return InstructionHandle.EMPTY_ARRAY;
125             }
126 
127             // See method comment.
128             if (inst instanceof JsrInstruction) {
129                 single[0] = ((JsrInstruction) inst).getTarget();
130                 return single;
131             }
132 
133             if (inst instanceof GotoInstruction) {
134                 single[0] = ((GotoInstruction) inst).getTarget();
135                 return single;
136             }
137 
138             if (inst instanceof BranchInstruction) {
139                 if (inst instanceof Select) {
140                     // BCEL's getTargets() returns only the non-default targets,
141                     // thanks to Eli Tilevich for reporting.
142                     final InstructionHandle[] matchTargets = ((Select) inst).getTargets();
143                     final InstructionHandle[] ret = new InstructionHandle[matchTargets.length + 1];
144                     ret[0] = ((Select) inst).getTarget();
145                     System.arraycopy(matchTargets, 0, ret, 1, matchTargets.length);
146                     return ret;
147                 }
148                 final InstructionHandle[] pair = new InstructionHandle[2];
149                 pair[0] = getInstruction().getNext();
150                 pair[1] = ((BranchInstruction) inst).getTarget();
151                 return pair;
152             }
153 
154             // default case: Fall through.
155             single[0] = getInstruction().getNext();
156             return single;
157         }
158 
159         /**
160          * "Merges in" (vmspec2, page 146) the "incoming" frame situation; executes the instructions symbolically and therefore
161          * calculates the "outgoing" frame situation. Returns: True iff the "incoming" frame situation changed after merging
162          * with "inFrame". The execPreds ArrayList must contain the InstructionContext objects executed so far in the correct
163          * order. This is just one execution path [out of many]. This is needed to correctly "merge" in the special case of a
164          * RET's successor. <B>The InstConstraintVisitor and ExecutionVisitor instances must be set up correctly.</B>
165          *
166          * @return true - if and only if the "outgoing" frame situation changed from the one before execute()ing.
167          */
168         @Override
169         public boolean execute(final Frame inFrame, final ArrayList<InstructionContext> execPreds, final InstConstraintVisitor icv, final ExecutionVisitor ev) {
170 
171             @SuppressWarnings("unchecked") // OK because execPreds is compatible type
172             final List<InstructionContext> clone = (List<InstructionContext>) execPreds.clone();
173             executionPredecessors = clone;
174 
175             // sanity check
176             if (lastExecutionJSR() == null && subroutines.subroutineOf(getInstruction()) != subroutines.getTopLevel()
177                 || lastExecutionJSR() != null && subroutines.subroutineOf(getInstruction()) == subroutines.getTopLevel()) {
178                 throw new AssertionViolatedException("Huh?! Am I '" + this + "' part of a subroutine or not?");
179             }
180 
181             Frame inF = inFrames.get(lastExecutionJSR());
182             if (inF == null) { // no incoming frame was set, so set it.
183                 inFrames.put(lastExecutionJSR(), inFrame);
184                 inF = inFrame;
185             } else if (inF.equals(inFrame) || !mergeInFrames(inFrame)) { // if there was an "old" inFrame
186                 return false;
187             }
188 
189             // Now we're sure the inFrame has changed!
190 
191             // new inFrame is already merged in, see above.
192             final Frame workingFrame = inF.getClone();
193 
194             try {
195                 // This verifies the InstructionConstraint for the current
196                 // instruction, but does not modify the workingFrame object.
197 //InstConstraintVisitor icv = InstConstraintVisitor.getInstance(VerifierFactory.getVerifier(method_gen.getClassName()));
198                 icv.setFrame(workingFrame);
199                 getInstruction().accept(icv);
200             } catch (final StructuralCodeConstraintException ce) {
201                 ce.extendMessage("", "\nInstructionHandle: " + getInstruction() + "\n");
202                 ce.extendMessage("", "\nExecution Frame:\n" + workingFrame);
203                 extendMessageWithFlow(ce);
204                 throw ce;
205             }
206 
207             // This executes the Instruction.
208             // Therefore the workingFrame object is modified.
209 //ExecutionVisitor ev = ExecutionVisitor.getInstance(VerifierFactory.getVerifier(method_gen.getClassName()));
210             ev.setFrame(workingFrame);
211             getInstruction().accept(ev);
212             // getInstruction().accept(ExecutionVisitor.withFrame(workingFrame));
213             outFrames.put(lastExecutionJSR(), workingFrame);
214 
215             return true; // new inFrame was different from old inFrame so merging them
216                          // yielded a different this.inFrame.
217         }
218 
219         /**
220          * Extends the StructuralCodeConstraintException ("e") object with an at-the-end-extended message. This extended message
221          * will then reflect the execution flow needed to get to the constraint violation that triggered the throwing of the "e"
222          * object.
223          */
224         private void extendMessageWithFlow(final StructuralCodeConstraintException e) {
225             final String s = "Execution flow:\n";
226             e.extendMessage("", s + getExecutionChain());
227         }
228 
229         /**
230          * Returns the exception handlers of this instruction.
231          */
232         @Override
233         public ExceptionHandler[] getExceptionHandlers() {
234             return exceptionhandlers.getExceptionHandlers(getInstruction());
235         }
236 
237         /**
238          * Returns the control flow execution chain. This is built while execute(Frame, ArrayList)-ing the code represented by
239          * the surrounding ControlFlowGraph.
240          */
241         private String getExecutionChain() {
242             final StringBuilder s = new StringBuilder(toString());
243             for (int i = executionPredecessors.size() - 1; i >= 0; i--) {
244                 s.insert(0, executionPredecessors.get(i) + "\n");
245             }
246             return s.toString();
247         }
248 
249         @Override
250         public Frame getInFrame() {
251             final InstructionContext jsr = lastExecutionJSR();
252             final Frame org = inFrames.get(jsr);
253             if (org == null) {
254                 throw new AssertionViolatedException("inFrame not set! This:\n" + this + "\nInFrames: '" + inFrames + "'.");
255             }
256             return org.getClone();
257         }
258 
259         @Override
260         public InstructionHandle getInstruction() {
261             return instruction;
262         }
263 
264         /**
265          * Returns a clone of the "outgoing" frame situation with respect to the given ExecutionChain.
266          */
267         @Override
268         public Frame getOutFrame(final ArrayList<InstructionContext> execChain) {
269             executionPredecessors = execChain;
270             final InstructionContext jsr = lastExecutionJSR();
271             final Frame org = outFrames.get(jsr);
272             if (org == null) {
273                 throw new AssertionViolatedException(
274                         "outFrame not set! This:\n" + this + "\nExecutionChain: " + getExecutionChain() + "\nOutFrames: '" + outFrames + "'.");
275             }
276             return org.getClone();
277         }
278 
279         @Override
280         public InstructionContext[] getSuccessors() {
281             return contextsOf(_getSuccessors());
282         }
283 
284         @Override
285         public int getTag() {
286             return TAG;
287         }
288 
289         /**
290          * Returns the InstructionContextImpl with an JSR/JSR_W that was last in the ExecutionChain, without a corresponding
291          * RET, i.e. we were called by this one. Returns null if we were called from the top level.
292          */
293         private InstructionContextImpl lastExecutionJSR() {
294 
295             final int size = executionPredecessors.size();
296             int retcount = 0;
297 
298             for (int i = size - 1; i >= 0; i--) {
299                 final InstructionContextImpl current = (InstructionContextImpl) executionPredecessors.get(i);
300                 final Instruction currentlast = current.getInstruction().getInstruction();
301                 if (currentlast instanceof RET) {
302                     retcount++;
303                 }
304                 if (currentlast instanceof JsrInstruction) {
305                     retcount--;
306                     if (retcount == -1) {
307                         return current;
308                     }
309                 }
310             }
311             return null;
312         }
313 
314         /**
315          * Does the actual merging (vmspec2, page 146). Returns true IFF this.inFrame was changed in course of merging with
316          * inFrame.
317          */
318         private boolean mergeInFrames(final Frame inFrame) {
319             // TODO: Can be performance-improved.
320             final Frame inF = inFrames.get(lastExecutionJSR());
321             final OperandStack oldstack = inF.getStack().getClone();
322             final LocalVariables oldlocals = inF.getLocals().getClone();
323             try {
324                 inF.getStack().merge(inFrame.getStack());
325                 inF.getLocals().merge(inFrame.getLocals());
326             } catch (final StructuralCodeConstraintException sce) {
327                 extendMessageWithFlow(sce);
328                 throw sce;
329             }
330             return !(oldstack.equals(inF.getStack()) && oldlocals.equals(inF.getLocals()));
331         }
332 
333         /* Satisfies InstructionContext.setTag(int). */
334         @Override
335         public void setTag(final int tag) { // part of InstructionContext interface
336             TAG = tag;
337         }
338 
339         /**
340          * Returns a simple String representation of this InstructionContext.
341          */
342         @Override
343         public String toString() {
344             // TODO: Put information in the brackets, for example
345             // Is this an ExceptionHandler? Is this a RET? Is this the start of
346             // a subroutine?
347             return getInstruction().toString(false) + "\t[InstructionContext]";
348         }
349 
350     } // End Inner InstructionContextImpl Class.
351 
352     /// ** The MethodGen object we're working on. */
353     // private final MethodGen method_gen;
354 
355     /** The Subroutines object for the method whose control flow is represented by this ControlFlowGraph. */
356     private final Subroutines subroutines;
357 
358     /** The ExceptionHandlers object for the method whose control flow is represented by this ControlFlowGraph. */
359     private final ExceptionHandlers exceptionhandlers;
360 
361     /** All InstructionContext instances of this ControlFlowGraph. */
362     private final Map<InstructionHandle, InstructionContext> instructionContexts = new HashMap<>();
363 
364     /**
365      * A Control Flow Graph; with additional JustIce checks
366      *
367      * @param methodGen the method generator instance
368      */
369     public ControlFlowGraph(final MethodGen methodGen) {
370         this(methodGen, true);
371     }
372 
373     /**
374      * A Control Flow Graph.
375      *
376      * @param methodGen the method generator instance
377      * @param enableJustIceCheck if true, additional JustIce checks are performed
378      * @since 6.0
379      */
380     public ControlFlowGraph(final MethodGen methodGen, final boolean enableJustIceCheck) {
381         subroutines = new Subroutines(methodGen, enableJustIceCheck);
382         exceptionhandlers = new ExceptionHandlers(methodGen);
383 
384         final InstructionHandle[] instructionhandles = methodGen.getInstructionList().getInstructionHandles();
385         for (final InstructionHandle instructionhandle : instructionhandles) {
386             instructionContexts.put(instructionhandle, new InstructionContextImpl(instructionhandle));
387         }
388 
389         // this.method_gen = method_gen;
390     }
391 
392     /**
393      * Returns the InstructionContext of a given instruction.
394      */
395     public InstructionContext contextOf(final InstructionHandle inst) {
396         final InstructionContext ic = instructionContexts.get(inst);
397         if (ic == null) {
398             throw new AssertionViolatedException("InstructionContext requested for an InstructionHandle that's not known!");
399         }
400         return ic;
401     }
402 
403     /**
404      * Returns the InstructionContext[] of a given InstructionHandle[], in a naturally ordered manner.
405      */
406     public InstructionContext[] contextsOf(final InstructionHandle[] insts) {
407         final InstructionContext[] ret = new InstructionContext[insts.length];
408         Arrays.setAll(ret, i -> contextOf(insts[i]));
409         return ret;
410     }
411 
412     /**
413      * Returns an InstructionContext[] with all the InstructionContext instances for the method whose control flow is
414      * represented by this ControlFlowGraph <B>(NOT ORDERED!)</B>.
415      */
416     public InstructionContext[] getInstructionContexts() {
417         final InstructionContext[] ret = new InstructionContext[instructionContexts.size()];
418         return instructionContexts.values().toArray(ret);
419     }
420 
421     /**
422      * Returns true, if and only if the said instruction is not reachable; that means, if it is not part of this
423      * ControlFlowGraph.
424      */
425     public boolean isDead(final InstructionHandle i) {
426         return subroutines.subroutineOf(i) == null;
427     }
428 }