001/*
002 * Copyright (c) 2015, 2015, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.
008 *
009 * This code is distributed in the hope that it will be useful, but WITHOUT
010 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
011 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
012 * version 2 for more details (a copy is included in the LICENSE file that
013 * accompanied this code).
014 *
015 * You should have received a copy of the GNU General Public License version
016 * 2 along with this work; if not, write to the Free Software Foundation,
017 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
018 *
019 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
020 * or visit www.oracle.com if you need additional information or have any
021 * questions.
022 */
023package com.oracle.graal.compiler.test.tutorial;
024
025import java.util.*;
026
027import jdk.internal.jvmci.common.*;
028import com.oracle.graal.debug.*;
029import com.oracle.graal.debug.Debug.*;
030import jdk.internal.jvmci.meta.*;
031
032import com.oracle.graal.graph.*;
033import com.oracle.graal.graphbuilderconf.*;
034import com.oracle.graal.graphbuilderconf.GraphBuilderConfiguration.Plugins;
035import com.oracle.graal.java.*;
036import com.oracle.graal.nodes.CallTargetNode.InvokeKind;
037import com.oracle.graal.nodes.*;
038import com.oracle.graal.nodes.StructuredGraph.AllowAssumptions;
039import com.oracle.graal.nodes.java.*;
040import com.oracle.graal.nodes.spi.*;
041import com.oracle.graal.nodes.util.*;
042import com.oracle.graal.phases.*;
043import com.oracle.graal.phases.graph.*;
044
045/**
046 * A simple context-insensitive static analysis based on the Graal API. It is intended for
047 * educational purposes, not for use in production. Only a limited set of Java functionality is
048 * supported to keep the code minimal.
049 * <p>
050 * The analysis builds a directed graph of {@link TypeFlow type flows}. If a type is added to type
051 * flow, it is propagated to all {@link TypeFlow#uses uses} of the type flow. Types are propagated
052 * using a {@link #worklist} of changed type flows until a fixpoint is reached, i.e., until no more
053 * types need to be added to any type state.
054 * <p>
055 * The type flows are constructed from a high-level Graal graph by the {@link TypeFlowBuilder}. All
056 * nodes that operate on {@link Kind#Object object} values are converted to the appropriate type
057 * flows. The analysis is context insensitive: every Java field has {@link Results#lookupField one
058 * list} of types assigned to the field; every Java method has {@link Results#lookupMethod one
059 * state} for each {@link MethodState#formalParameters parameter} as well as the
060 * {@link MethodState#formalReturn return value}.
061 */
062public class StaticAnalysis {
063    /** Access to type, method, and fields using the Graal API. */
064    private final MetaAccessProvider metaAccess;
065    /** Access to platform dependent stamps. */
066    private final StampProvider stampProvider;
067    /** The results of the static analysis. */
068    private final Results results;
069    /** Worklist for fixpoint iteration. */
070    private final Deque<WorklistEntry> worklist;
071
072    public StaticAnalysis(MetaAccessProvider metaAccess, StampProvider stampProvider) {
073        this.metaAccess = metaAccess;
074        this.stampProvider = stampProvider;
075        this.results = new Results();
076        this.worklist = new ArrayDeque<>();
077    }
078
079    /**
080     * Adds a root method to the static analysis. The method must be static and must not have any
081     * parameters, because the possible types of the parameters would not be known.
082     */
083    public void addMethod(ResolvedJavaMethod method) {
084        if (!method.isStatic() || method.getSignature().getParameterCount(false) > 0) {
085            error("Entry point method is not static or has parameters: " + method.format("%H.%n(%p)"));
086        }
087        addToWorklist(results.lookupMethod(method));
088    }
089
090    /**
091     * Performs the fixed-point analysis that finds all methods transitively reachable from the
092     * {@link #addMethod root methods}.
093     */
094    public void finish() {
095        while (!worklist.isEmpty()) {
096            worklist.removeFirst().process();
097        }
098    }
099
100    /**
101     * Returns the static analysis results computed by {@link StaticAnalysis#finish}.
102     */
103    public Results getResults() {
104        return results;
105    }
106
107    protected void addToWorklist(WorklistEntry task) {
108        worklist.addLast(task);
109    }
110
111    protected static RuntimeException error(String msg) {
112        throw JVMCIError.shouldNotReachHere(msg);
113    }
114
115    /**
116     * Base class for all work items that can be {@link #addToWorklist added to the worklist}.
117     */
118    abstract class WorklistEntry {
119        protected abstract void process();
120    }
121
122    /**
123     * The results computed by the static analysis.
124     */
125    public class Results {
126        private final TypeFlow allInstantiatedTypes;
127        private final Map<ResolvedJavaField, TypeFlow> fields;
128        private final Map<ResolvedJavaMethod, MethodState> methods;
129
130        protected Results() {
131            allInstantiatedTypes = new TypeFlow();
132            fields = new HashMap<>();
133            methods = new HashMap<>();
134        }
135
136        /**
137         * All {@link TypeFlow#getTypes() types} that are found to be instantiated, i.e., all types
138         * allocated by the reachable instance and array allocation bytecodes.
139         */
140        public TypeFlow getAllInstantiatedTypes() {
141            return allInstantiatedTypes;
142        }
143
144        /**
145         * All {@link TypeFlow#getTypes() types} that the given field can have, i.e., all types
146         * assigned by the reachable field store bytecodes.
147         */
148        public TypeFlow lookupField(ResolvedJavaField field) {
149            TypeFlow result = fields.get(field);
150            if (result == null) {
151                result = new TypeFlow();
152                fields.put(field, result);
153            }
154            return result;
155        }
156
157        /**
158         * All {@link TypeFlow#getTypes() types} that {@link MethodState#formalParameters
159         * parameters} and {@link MethodState#formalReturn return value} of the given method can
160         * have.
161         */
162        public MethodState lookupMethod(ResolvedJavaMethod method) {
163            MethodState result = methods.get(method);
164            if (result == null) {
165                result = new MethodState(method);
166                methods.put(method, result);
167            }
168            return result;
169        }
170    }
171
172    /**
173     * The {@link TypeFlow#getTypes() types} of the parameters and return value of a method. Also
174     * serves as the worklist element to parse the bytecodes of the method.
175     */
176    public class MethodState extends WorklistEntry {
177        private final ResolvedJavaMethod method;
178        private final TypeFlow[] formalParameters;
179        private final TypeFlow formalReturn;
180        private boolean processed;
181
182        protected MethodState(ResolvedJavaMethod method) {
183            this.method = method;
184
185            formalParameters = new TypeFlow[method.getSignature().getParameterCount(!method.isStatic())];
186            for (int i = 0; i < formalParameters.length; i++) {
187                formalParameters[i] = new TypeFlow();
188            }
189            formalReturn = new TypeFlow();
190        }
191
192        /**
193         * All {@link TypeFlow#getTypes() types} that the parameters of this method can have.
194         */
195        public TypeFlow[] getFormalParameters() {
196            return formalParameters;
197        }
198
199        /**
200         * All {@link TypeFlow#getTypes() types} that the return value of this method can have.
201         */
202        public TypeFlow getFormalReturn() {
203            return formalReturn;
204        }
205
206        @Override
207        protected void process() {
208            if (!processed) {
209                /* We want to process a method only once. */
210                processed = true;
211
212                /*
213                 * Build the Graal graph for the method using the bytecode parser provided by Graal.
214                 */
215
216                StructuredGraph graph = new StructuredGraph(method, AllowAssumptions.NO);
217                /*
218                 * Support for graph dumping, IGV uses this information to show the method name of a
219                 * graph.
220                 */
221                try (Scope scope = Debug.scope("graph building", graph)) {
222                    /*
223                     * We want all types to be resolved by the graph builder, i.e., we want classes
224                     * referenced by the bytecodes to be loaded and initialized. Since we do not run
225                     * the code before static analysis, the classes would otherwise be not loaded
226                     * yet and the bytecode parser would only create a graph.
227                     */
228                    GraphBuilderConfiguration graphBuilderConfig = GraphBuilderConfiguration.getEagerDefault(new Plugins(new InvocationPlugins(metaAccess)));
229                    /*
230                     * For simplicity, we ignore all exception handling during the static analysis.
231                     * This is a constraint of this example code, a real static analysis needs to
232                     * handle the Graal nodes for throwing and handling exceptions.
233                     */
234                    graphBuilderConfig = graphBuilderConfig.withOmitAllExceptionEdges(true);
235                    /*
236                     * We do not want Graal to perform any speculative optimistic optimizations,
237                     * i.e., we do not want to use profiling information. Since we do not run the
238                     * code before static analysis, the profiling information is empty and therefore
239                     * wrong.
240                     */
241                    OptimisticOptimizations optimisticOpts = OptimisticOptimizations.NONE;
242
243                    GraphBuilderPhase.Instance graphBuilder = new GraphBuilderPhase.Instance(metaAccess, stampProvider, null, graphBuilderConfig, optimisticOpts, null);
244                    graphBuilder.apply(graph);
245                } catch (Throwable ex) {
246                    Debug.handle(ex);
247                }
248
249                /*
250                 * Build the type flow graph from the Graal graph, i.e., process all nodes that are
251                 * deal with objects.
252                 */
253
254                TypeFlowBuilder typeFlowBuilder = new TypeFlowBuilder(graph);
255                typeFlowBuilder.apply();
256            }
257        }
258    }
259
260    /**
261     * The active element during static analysis: types are added until a fixed point is reached.
262     * When a new type is added, it is propagated to all usages by putting this element on the
263     * {@link StaticAnalysis#addToWorklist worklist}.
264     */
265    public class TypeFlow extends WorklistEntry {
266        private final Set<ResolvedJavaType> types;
267        private final Set<TypeFlow> uses;
268
269        protected TypeFlow() {
270            types = new HashSet<>();
271            uses = new HashSet<>();
272        }
273
274        /** Returns the types of this element. */
275        public Set<ResolvedJavaType> getTypes() {
276            return types;
277        }
278
279        /**
280         * Adds new types to this element. If that changes the state of this element, it is added to
281         * the {@link StaticAnalysis#addToWorklist worklist} in order to propagate the added types
282         * to all usages.
283         */
284        protected void addTypes(Set<ResolvedJavaType> newTypes) {
285            if (types.addAll(newTypes)) {
286                addToWorklist(this);
287            }
288        }
289
290        /**
291         * Adds a new use to this element. All types of this element are propagated to the new
292         * usage.
293         */
294        protected void addUse(TypeFlow use) {
295            if (uses.add(use)) {
296                use.addTypes(types);
297            }
298        }
299
300        /**
301         * Processing of the worklist element: propagate the types to all usages. That in turn can
302         * add the usages to the worklist (if the types of the usage are changed).
303         */
304        @Override
305        protected void process() {
306            for (TypeFlow use : uses) {
307                use.addTypes(types);
308            }
309        }
310    }
311
312    /**
313     * The active element for method invocations. For {@link InvokeKind#Virtual virtual} and
314     * {@link InvokeKind#Interface interface} calls, the {@link TypeFlow#getTypes() types} of this
315     * node are the receiver types. When a new receiver type is added, a new callee might be added.
316     * Adding a new callee means linking the type flow of the actual parameters with the formal
317     * parameters of the callee, and linking the return value of the callee with the return value
318     * state of the invocation.
319     *
320     * Statically bindable methods calls ({@link InvokeKind#Static static} and
321     * {@link InvokeKind#Special special} calls) have only one callee, but use the same code for
322     * simplicity.
323     */
324    class InvokeTypeFlow extends TypeFlow {
325        private final MethodCallTargetNode callTarget;
326        private final TypeFlow[] actualParameters;
327        private final TypeFlow actualReturn;
328        private final Set<ResolvedJavaMethod> callees;
329
330        protected InvokeTypeFlow(MethodCallTargetNode callTarget, TypeFlow[] actualParameterFlows, TypeFlow actualReturnFlow) {
331            this.callTarget = callTarget;
332            this.actualParameters = actualParameterFlows;
333            this.actualReturn = actualReturnFlow;
334            this.callees = new HashSet<>();
335        }
336
337        private void linkCallee(ResolvedJavaMethod callee) {
338            if (callees.add(callee)) {
339                /* We have added a new callee. */
340
341                /*
342                 * Connect the actual parameters of the invocation with the formal parameters of the
343                 * callee.
344                 */
345                MethodState calleeState = results.lookupMethod(callee);
346                for (int i = 0; i < actualParameters.length; i++) {
347                    if (actualParameters[i] != null) {
348                        actualParameters[i].addUse(calleeState.formalParameters[i]);
349                    }
350                }
351
352                /*
353                 * Connect the formal return value of the callee with the actual return value of the
354                 * invocation.
355                 */
356                if (actualReturn != null) {
357                    calleeState.formalReturn.addUse(actualReturn);
358                }
359                addToWorklist(calleeState);
360            }
361        }
362
363        @Override
364        protected void process() {
365            if (callTarget.invokeKind().isDirect()) {
366                /* Static and special calls: link the statically known callee method. */
367                linkCallee(callTarget.targetMethod());
368            } else {
369                /* Virtual and interface call: Iterate all receiver types. */
370                for (ResolvedJavaType type : getTypes()) {
371                    /*
372                     * Resolve the method call for one exact receiver type. The method linking
373                     * semantics of Java are complicated, but fortunatley we can use the linker of
374                     * the hosting Java VM. The Graal API exposes this functionality.
375                     */
376                    ResolvedJavaMethod method = type.resolveConcreteMethod(callTarget.targetMethod(), callTarget.invoke().getContextType());
377
378                    /*
379                     * Since the static analysis is conservative, the list of receiver types can
380                     * contain types that actually do not provide the method to be called. Ignore
381                     * these.
382                     */
383                    if (method != null && !method.isAbstract()) {
384                        linkCallee(method);
385                    }
386                }
387            }
388            super.process();
389        }
390    }
391
392    /**
393     * Converts the Graal nodes of a method to a type flow graph. The main part of the algorithm is
394     * a reverse-postorder iteration of the Graal nodes, which is provided by the base class
395     * {@link StatelessPostOrderNodeIterator}.
396     */
397    class TypeFlowBuilder extends StatelessPostOrderNodeIterator {
398        private final StructuredGraph graph;
399        private final MethodState methodState;
400        /**
401         * Mapping from Graal nodes to type flows. This uses an efficient Graal-provided
402         * {@link NodeMap collection class}.
403         */
404        private final NodeMap<TypeFlow> typeFlows;
405
406        protected TypeFlowBuilder(StructuredGraph graph) {
407            super(graph.start());
408
409            this.graph = graph;
410            this.methodState = results.lookupMethod(graph.method());
411            this.typeFlows = new NodeMap<>(graph);
412        }
413
414        /**
415         * Register the type flow node for a Graal node.
416         */
417        private void registerFlow(ValueNode node, TypeFlow flow) {
418            /*
419             * We ignore intermediate nodes used by Graal that, e.g., add more type information or
420             * encapsulate values flowing out of loops.
421             */
422            ValueNode unproxiedNode = GraphUtil.unproxify(node);
423
424            assert typeFlows.get(unproxiedNode) == null : "overwriting existing value";
425            typeFlows.set(unproxiedNode, flow);
426        }
427
428        /**
429         * Lookup the type flow node for a Graal node.
430         */
431        private TypeFlow lookupFlow(ValueNode node) {
432            ValueNode unproxiedNode = GraphUtil.unproxify(node);
433            TypeFlow result = typeFlows.get(unproxiedNode);
434            if (result == null) {
435                /*
436                 * This is only the prototype of a static analysis, the handling of many Graal nodes
437                 * (such as array accesses) is not implemented.
438                 */
439                throw error("Node is not supported yet by static analysis: " + node.getClass().getName());
440            }
441            return result;
442        }
443
444        private boolean isObject(ValueNode node) {
445            return node.getKind() == Kind.Object;
446        }
447
448        @Override
449        public void apply() {
450            /*
451             * Before the reverse-postorder iteration of fixed nodes, we handle some classes of
452             * floating nodes.
453             */
454            for (Node n : graph.getNodes()) {
455                if (n instanceof ParameterNode) {
456                    /*
457                     * Incoming method parameter already have a type flow created by the
458                     * MethodState.
459                     */
460                    ParameterNode node = (ParameterNode) n;
461                    if (isObject(node)) {
462                        registerFlow(node, methodState.formalParameters[(node.index())]);
463                    }
464                } else if (n instanceof ValuePhiNode) {
465                    /*
466                     * Phi functions for loops are cyclic. We create the type flow here (before
467                     * processing any loop nodes), but link the phi values only later (after
468                     * processing of all loop nodes.
469                     */
470                    ValuePhiNode node = (ValuePhiNode) n;
471                    if (isObject(node)) {
472                        registerFlow(node, new TypeFlow());
473                    }
474                } else if (n instanceof ConstantNode) {
475                    /* Constants have a known type. */
476                    ConstantNode node = (ConstantNode) n;
477                    JavaConstant constant = node.asJavaConstant();
478                    if (constant.isNull()) {
479                        registerFlow(node, new TypeFlow());
480                    }
481                }
482            }
483
484            super.apply();
485
486            for (Node n : graph.getNodes()) {
487                if (n instanceof ValuePhiNode) {
488                    /*
489                     * Post-processing of phi functions. Now the type flow for all input values has
490                     * been created, so we can link the type flows together.
491                     */
492                    ValuePhiNode node = (ValuePhiNode) n;
493                    if (isObject(node)) {
494                        TypeFlow phiFlow = lookupFlow(node);
495                        for (ValueNode value : node.values()) {
496                            lookupFlow(value).addUse(phiFlow);
497                        }
498                    }
499                }
500            }
501        }
502
503        private void allocation(ValueNode node, ResolvedJavaType type) {
504            /*
505             * The type flow of allocation nodes is one exact type. This is the source of the
506             * fixpoint iteration, the types are propagated downwards from these sources.
507             */
508            TypeFlow flow = new TypeFlow();
509            flow.addTypes(Collections.singleton(type));
510            registerFlow(node, flow);
511            flow.addUse(results.getAllInstantiatedTypes());
512        }
513
514        @Override
515        protected void node(FixedNode n) {
516            if (n instanceof NewInstanceNode) {
517                NewInstanceNode node = (NewInstanceNode) n;
518                allocation(node, node.instanceClass());
519            } else if (n instanceof NewArrayNode) {
520                NewArrayNode node = (NewArrayNode) n;
521                allocation(node, node.elementType().getArrayClass());
522
523            } else if (n instanceof LoadFieldNode) {
524                /*
525                 * The type flow of a field load is the type flow of the field itself. It
526                 * accumulates all types ever stored to the field.
527                 */
528                LoadFieldNode node = (LoadFieldNode) n;
529                if (isObject(node)) {
530                    registerFlow(node, results.lookupField(node.field()));
531                }
532            } else if (n instanceof StoreFieldNode) {
533                /*
534                 * Connect the type flow of the stored value with the type flow of the field.
535                 */
536                StoreFieldNode node = (StoreFieldNode) n;
537                if (isObject(node.value())) {
538                    TypeFlow fieldFlow = results.lookupField(node.field());
539                    lookupFlow(node.value()).addUse(fieldFlow);
540                }
541
542            } else if (n instanceof ReturnNode) {
543                /*
544                 * Connect the type flow of the returned value with the formal return type flow of
545                 * the MethodState.
546                 */
547                ReturnNode node = (ReturnNode) n;
548                if (node.result() != null && isObject(node.result())) {
549                    lookupFlow(node.result()).addUse(methodState.formalReturn);
550                }
551
552            } else if (n instanceof Invoke) {
553                /*
554                 * Create the InvokeTypeFlow, which performs all the linking of actual and formal
555                 * parameter values with all identified callees.
556                 */
557                Invoke invoke = (Invoke) n;
558                MethodCallTargetNode callTarget = (MethodCallTargetNode) invoke.callTarget();
559
560                TypeFlow[] actualParameters = new TypeFlow[callTarget.arguments().size()];
561                for (int i = 0; i < actualParameters.length; i++) {
562                    ValueNode actualParam = callTarget.arguments().get(i);
563                    if (isObject(actualParam)) {
564                        actualParameters[i] = lookupFlow(actualParam);
565                    }
566                }
567                TypeFlow actualReturn = null;
568                if (isObject(invoke.asNode())) {
569                    actualReturn = new TypeFlow();
570                    registerFlow(invoke.asNode(), actualReturn);
571                }
572
573                InvokeTypeFlow invokeFlow = new InvokeTypeFlow(callTarget, actualParameters, actualReturn);
574
575                if (callTarget.invokeKind().isIndirect()) {
576                    /*
577                     * For virtual and interface calls, new receiver types can lead to new callees.
578                     * Connect the type flow of the receiver with the invocation flow.
579                     */
580                    lookupFlow(callTarget.arguments().get(0)).addUse(invokeFlow);
581                }
582                /*
583                 * Ensure the invocation is on the worklist at least once, even if it is a static
584                 * call with not parameters that does not involve any type flows.
585                 */
586                addToWorklist(invokeFlow);
587            }
588        }
589    }
590}