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}