diff options
Diffstat (limited to 'third_party/checker_framework_dataflow')
115 files changed, 17413 insertions, 0 deletions
diff --git a/third_party/checker_framework_dataflow/LICENSE.txt b/third_party/checker_framework_dataflow/LICENSE.txt new file mode 100644 index 0000000000..c4b232d9bc --- /dev/null +++ b/third_party/checker_framework_dataflow/LICENSE.txt @@ -0,0 +1,417 @@ +Most of the Checker Framework is licensed under the GNU General Public +License, version 2 (GPL2), with the classpath exception. The text of this +license appears below. This is the same license used for OpenJDK. + +A few parts of the Checker Framework have more permissive licenses. + + * The annotations are licensed under the MIT License. (The text of this + license appears below.) More specifically, all the parts of the Checker + Framework that you might want to include with your own program use the + MIT License. This is the checker-qual.jar file and all the files that + appear in it: every file in a qual/ directory, plus utility files such + as NullnessUtils.java, RegexUtil.java, UnsignednessUtil.java, etc. + In addition, the cleanroom implementations of third-party annotations, + which the Checker Framework recognizes as aliases for its own + annotations, are licensed under the MIT License. + + * The Maven plugin is dual-licensed (you may use whichever you prefer) + under GPL2 and the Apache License, version 2.0 (Apache2). The text of + Apache2 appears in file maven-plugin/LICENSE.txt. Maven itself uses + Apache2. + + * The Eclipse plugin is dual-licensed (you may use whichever you prefer) + under GPL2 and the Eclipse Public License Version 1.0 (EPL). EPL + appears http://www.eclipse.org/org/documents/epl-v10.php. Eclipse + itself uses EPL. + +Some external libraries that are included with the Checker Framework have +different licenses. + + * javaparser is licensed under the LGPL. (The javaparser source code + contains a file with the text of the GPL, but it is not clear why, since + javaparser does not use the GPL.) See file javaparser/COPYING.LESSER + and the source code of all its files. + + * JUnit is licensed under the Common Public License v1.0 (see + http://www.junit.org/license), with parts (Hamcrest) licensed under the + BSD License (see http://hamcrest.org/JavaHamcrest/). + + * plume-lib is licensed under the MIT License. + +The Checker Framework includes annotations for several libraries, in +directory checker/jdk/. Each annotated library uses the same license as +the unannotated version of the library. + +=========================================================================== + +The GNU General Public License (GPL) + +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. +59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public License is intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. This General Public License applies to +most of the Free Software Foundation's software and to any other program whose +authors commit to using it. (Some other Free Software Foundation software is +covered by the GNU Library General Public License instead.) You can apply it to +your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our +General Public Licenses are designed to make sure that you have the freedom to +distribute copies of free software (and charge for this service if you wish), +that you receive source code or can get it if you want it, that you can change +the software or use pieces of it in new free programs; and that you know you +can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to deny +you these rights or to ask you to surrender the rights. These restrictions +translate to certain responsibilities for you if you distribute copies of the +software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or for +a fee, you must give the recipients all the rights that you have. You must +make sure that they, too, receive or can get the source code. And you must +show them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) +offer you this license which gives you legal permission to copy, distribute +and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that +everyone understands that there is no warranty for this free software. If the +software is modified by someone else and passed on, we want its recipients to +know that what they have is not the original, so that any problems introduced +by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We +wish to avoid the danger that redistributors of a free program will +individually obtain patent licenses, in effect making the program proprietary. +To prevent this, we have made it clear that any patent must be licensed for +everyone's free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification +follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a notice +placed by the copyright holder saying it may be distributed under the terms of +this General Public License. The "Program", below, refers to any such program +or work, and a "work based on the Program" means either the Program or any +derivative work under copyright law: that is to say, a work containing the +Program or a portion of it, either verbatim or with modifications and/or +translated into another language. (Hereinafter, translation is included +without limitation in the term "modification".) Each licensee is addressed as +"you". + +Activities other than copying, distribution and modification are not covered by +this License; they are outside its scope. The act of running the Program is +not restricted, and the output from the Program is covered only if its contents +constitute a work based on the Program (independent of having been made by +running the Program). Whether that is true depends on what the Program does. + +1. You may copy and distribute verbatim copies of the Program's source code as +you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and +disclaimer of warranty; keep intact all the notices that refer to this License +and to the absence of any warranty; and give any other recipients of the +Program a copy of this License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you may +at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, thus +forming a work based on the Program, and copy and distribute such modifications +or work under the terms of Section 1 above, provided that you also meet all of +these conditions: + + a) You must cause the modified files to carry prominent notices stating + that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in whole or + in part contains or is derived from the Program or any part thereof, to be + licensed as a whole at no charge to all third parties under the terms of + this License. + + c) If the modified program normally reads commands interactively when run, + you must cause it, when started running for such interactive use in the + most ordinary way, to print or display an announcement including an + appropriate copyright notice and a notice that there is no warranty (or + else, saying that you provide a warranty) and that users may redistribute + the program under these conditions, and telling the user how to view a copy + of this License. (Exception: if the Program itself is interactive but does + not normally print such an announcement, your work based on the Program is + not required to print an announcement.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Program, and can be reasonably +considered independent and separate works in themselves, then this License, and +its terms, do not apply to those sections when you distribute them as separate +works. But when you distribute the same sections as part of a whole which is a +work based on the Program, the distribution of the whole must be on the terms +of this License, whose permissions for other licensees extend to the entire +whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise the +right to control the distribution of derivative or collective works based on +the Program. + +In addition, mere aggregation of another work not based on the Program with the +Program (or with a work based on the Program) on a volume of a storage or +distribution medium does not bring the other work under the scope of this +License. + +3. You may copy and distribute the Program (or a work based on it, under +Section 2) in object code or executable form under the terms of Sections 1 and +2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable source + code, which must be distributed under the terms of Sections 1 and 2 above + on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three years, to + give any third party, for a charge no more than your cost of physically + performing source distribution, a complete machine-readable copy of the + corresponding source code, to be distributed under the terms of Sections 1 + and 2 above on a medium customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer to + distribute corresponding source code. (This alternative is allowed only + for noncommercial distribution and only if you received the program in + object code or executable form with such an offer, in accord with + Subsection b above.) + +The source code for a work means the preferred form of the work for making +modifications to it. For an executable work, complete source code means all +the source code for all modules it contains, plus any associated interface +definition files, plus the scripts used to control compilation and installation +of the executable. However, as a special exception, the source code +distributed need not include anything that is normally distributed (in either +source or binary form) with the major components (compiler, kernel, and so on) +of the operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the source +code from the same place counts as distribution of the source code, even though +third parties are not compelled to copy the source along with the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except as +expressly provided under this License. Any attempt otherwise to copy, modify, +sublicense or distribute the Program is void, and will automatically terminate +your rights under this License. However, parties who have received copies, or +rights, from you under this License will not have their licenses terminated so +long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed it. +However, nothing else grants you permission to modify or distribute the Program +or its derivative works. These actions are prohibited by law if you do not +accept this License. Therefore, by modifying or distributing the Program (or +any work based on the Program), you indicate your acceptance of this License to +do so, and all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), +the recipient automatically receives a license from the original licensor to +copy, distribute or modify the Program subject to these terms and conditions. +You may not impose any further restrictions on the recipients' exercise of the +rights granted herein. You are not responsible for enforcing compliance by +third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), conditions +are imposed on you (whether by court order, agreement or otherwise) that +contradict the conditions of this License, they do not excuse you from the +conditions of this License. If you cannot distribute so as to satisfy +simultaneously your obligations under this License and any other pertinent +obligations, then as a consequence you may not distribute the Program at all. +For example, if a patent license would not permit royalty-free redistribution +of the Program by all those who receive copies directly or indirectly through +you, then the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply and +the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or +other property right claims or to contest validity of any such claims; this +section has the sole purpose of protecting the integrity of the free software +distribution system, which is implemented by public license practices. Many +people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose that +choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain +countries either by patents or by copyrighted interfaces, the original +copyright holder who places the Program under this License may add an explicit +geographical distribution limitation excluding those countries, so that +distribution is permitted only in or among countries not thus excluded. In +such case, this License incorporates the limitation as if written in the body +of this License. + +9. The Free Software Foundation may publish revised and/or new versions of the +General Public License from time to time. Such new versions will be similar in +spirit to the present version, but may differ in detail to address new problems +or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any later +version", you have the option of following the terms and conditions either of +that version or of any later version published by the Free Software Foundation. +If the Program does not specify a version number of this License, you may +choose any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs +whose distribution conditions are different, write to the author to ask for +permission. For software which is copyrighted by the Free Software Foundation, +write to the Free Software Foundation; we sometimes make exceptions for this. +Our decision will be guided by the two goals of preserving the free status of +all derivatives of our free software and of promoting the sharing and reuse of +software generally. + +NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR +THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE +PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, +YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL +ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE +PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR +INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA +BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER +OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible +use to the public, the best way to achieve this is to make it free software +which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach +them to the start of each source file to most effectively convey the exclusion +of warranty; and each file should have at least the "copyright" line and a +pointer to where the full notice is found. + + One line to give the program's name and a brief idea of what it does. + + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., 59 + Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this when it +starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author Gnomovision comes + with ABSOLUTELY NO WARRANTY; for details type 'show w'. This is free + software, and you are welcome to redistribute it under certain conditions; + type 'show c' for details. + +The hypothetical commands 'show w' and 'show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may be +called something other than 'show w' and 'show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the program, if necessary. Here +is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + 'Gnomovision' (which makes passes at compilers) written by James Hacker. + + signature of Ty Coon, 1 April 1989 + + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General Public +License instead of this License. + + +"CLASSPATH" EXCEPTION TO THE GPL + +Certain source files distributed by Oracle America and/or its affiliates are +subject to the following clarification and special exception to the GPL, but +only where Oracle has expressly included in the particular source file's header +the words "Oracle designates this particular file as subject to the "Classpath" +exception as provided by Oracle in the LICENSE file that accompanied this code." + + Linking this library statically or dynamically with other modules is making + a combined work based on this library. Thus, the terms and conditions of + the GNU General Public License cover the whole combination. + + As a special exception, the copyright holders of this library give you + permission to link this library with independent modules to produce an + executable, regardless of the license terms of these independent modules, + and to copy and distribute the resulting executable under terms of your + choice, provided that you also meet, for each linked independent module, + the terms and conditions of the license of that module. An independent + module is a module which is not derived from or based on this library. If + you modify this library, you may extend this exception to your version of + the library, but you are not obligated to do so. If you do not wish to do + so, delete this exception statement from your version. + +=========================================================================== + +MIT License: + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +=========================================================================== diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/AbstractValue.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/AbstractValue.java new file mode 100644 index 0000000000..2dbcbd4b03 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/AbstractValue.java @@ -0,0 +1,27 @@ +package org.checkerframework.dataflow.analysis; + +/** + * An abstract value used in the org.checkerframework.dataflow analysis. + * + * @author Stefan Heule + * + */ +public interface AbstractValue<V extends AbstractValue<V>> { + + /** + * Compute the least upper bound of two stores. + * + * <p> + * + * <em>Important</em>: This method must fulfill the following contract: + * <ul> + * <li>Does not change {@code this}.</li> + * <li>Does not change {@code other}.</li> + * <li>Returns a fresh object which is not aliased yet.</li> + * <li>Returns an object of the same (dynamic) type as {@code this}, even if + * the signature is more permissive.</li> + * <li>Is commutative.</li> + * </ul> + */ + V leastUpperBound(V other); +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/Analysis.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/Analysis.java new file mode 100644 index 0000000000..2d0e7595de --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/Analysis.java @@ -0,0 +1,718 @@ +package org.checkerframework.dataflow.analysis; + +/*>>> +import org.checkerframework.checker.nullness.qual.Nullable; +*/ + +import com.sun.source.tree.LambdaExpressionTree; +import org.checkerframework.dataflow.cfg.ControlFlowGraph; +import org.checkerframework.dataflow.cfg.UnderlyingAST; +import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGLambda; +import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGMethod; +import org.checkerframework.dataflow.cfg.UnderlyingAST.Kind; +import org.checkerframework.dataflow.cfg.block.Block; +import org.checkerframework.dataflow.cfg.block.ConditionalBlock; +import org.checkerframework.dataflow.cfg.block.ExceptionBlock; +import org.checkerframework.dataflow.cfg.block.RegularBlock; +import org.checkerframework.dataflow.cfg.block.SpecialBlock; +import org.checkerframework.dataflow.cfg.node.AssignmentNode; +import org.checkerframework.dataflow.cfg.node.LocalVariableNode; +import org.checkerframework.dataflow.cfg.node.Node; +import org.checkerframework.dataflow.cfg.node.ReturnNode; + +import org.checkerframework.javacutil.ElementUtils; +import org.checkerframework.javacutil.Pair; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.PriorityQueue; +import java.util.Set; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; + +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.VariableTree; + +/** + * An implementation of an iterative algorithm to solve a org.checkerframework.dataflow problem, + * given a control flow graph and a transfer function. + * + * @author Stefan Heule + * + * @param <A> + * The abstract value type to be tracked by the analysis. + * @param <S> + * The store type used in the analysis. + * @param <T> + * The transfer function type that is used to approximated runtime + * behavior. + */ +public class Analysis<A extends AbstractValue<A>, S extends Store<S>, T extends TransferFunction<A, S>> { + + /** Is the analysis currently running? */ + protected boolean isRunning = false; + + /** The transfer function for regular nodes. */ + protected T transferFunction; + + /** The control flow graph to perform the analysis on. */ + protected ControlFlowGraph cfg; + + /** The associated processing environment */ + protected final ProcessingEnvironment env; + + /** Instance of the types utility. */ + protected final Types types; + + /** + * Then stores before every basic block (assumed to be 'no information' if + * not present). + */ + protected IdentityHashMap<Block, S> thenStores; + + /** + * Else stores before every basic block (assumed to be 'no information' if + * not present). + */ + protected IdentityHashMap<Block, S> elseStores; + + /** + * The transfer inputs before every basic block (assumed to be 'no information' if + * not present). + */ + protected IdentityHashMap<Block, TransferInput<A, S>> inputs; + + /** + * The stores after every return statement. + */ + protected IdentityHashMap<ReturnNode, TransferResult<A, S>> storesAtReturnStatements; + + /** The worklist used for the fix-point iteration. */ + protected Worklist worklist; + + /** Abstract values of nodes. */ + protected IdentityHashMap<Node, A> nodeValues; + + /** Map from (effectively final) local variable elements to their abstract value. */ + public HashMap<Element, A> finalLocalValues; + + /** + * The node that is currently handled in the analysis (if it is running). + * The following invariant holds: + * + * <pre> + * !isRunning ⇒ (currentNode == null) + * </pre> + */ + protected Node currentNode; + + /** + * The tree that is currently being looked at. The transfer function can set + * this tree to make sure that calls to {@code getValue} will not return + * information for this given tree. + */ + protected Tree currentTree; + + /** + * The current transfer input when the analysis is running. + */ + protected TransferInput<A, S> currentInput; + + public Tree getCurrentTree() { + return currentTree; + } + + public void setCurrentTree(Tree currentTree) { + this.currentTree = currentTree; + } + + /** + * Construct an object that can perform a org.checkerframework.dataflow analysis over a control + * flow graph. The transfer function is set later using + * {@code setTransferFunction}. + */ + public Analysis(ProcessingEnvironment env) { + this.env = env; + types = env.getTypeUtils(); + } + + /** + * Construct an object that can perform a org.checkerframework.dataflow analysis over a control + * flow graph, given a transfer function. + */ + public Analysis(ProcessingEnvironment env, T transfer) { + this(env); + this.transferFunction = transfer; + } + + public void setTransferFunction(T transfer) { + this.transferFunction = transfer; + } + + public T getTransferFunction() { + return transferFunction; + } + + public Types getTypes() { + return types; + } + + public ProcessingEnvironment getEnv() { + return env; + } + + /** + * Perform the actual analysis. Should only be called once after the object + * has been created. + */ + public void performAnalysis(ControlFlowGraph cfg) { + assert isRunning == false; + isRunning = true; + + init(cfg); + + while (!worklist.isEmpty()) { + Block b = worklist.poll(); + + switch (b.getType()) { + case REGULAR_BLOCK: { + RegularBlock rb = (RegularBlock) b; + + // apply transfer function to contents + TransferInput<A, S> inputBefore = getInputBefore(rb); + currentInput = inputBefore.copy(); + TransferResult<A, S> transferResult = null; + Node lastNode = null; + boolean addToWorklistAgain = false; + for (Node n : rb.getContents()) { + transferResult = callTransferFunction(n, currentInput); + addToWorklistAgain |= updateNodeValues(n, transferResult); + currentInput = new TransferInput<>(n, this, transferResult); + lastNode = n; + } + // loop will run at least one, making transferResult non-null + + // propagate store to successors + Block succ = rb.getSuccessor(); + assert succ != null : "regular basic block without non-exceptional successor unexpected"; + propagateStoresTo(succ, lastNode, currentInput, rb.getFlowRule(), addToWorklistAgain); + break; + } + + case EXCEPTION_BLOCK: { + ExceptionBlock eb = (ExceptionBlock) b; + + // apply transfer function to content + TransferInput<A, S> inputBefore = getInputBefore(eb); + currentInput = inputBefore.copy(); + Node node = eb.getNode(); + TransferResult<A, S> transferResult = callTransferFunction( + node, currentInput); + boolean addToWorklistAgain = updateNodeValues(node, transferResult); + + // propagate store to successor + Block succ = eb.getSuccessor(); + if (succ != null) { + currentInput = new TransferInput<>(node, this, transferResult); + // TODO? Variable wasn't used. + // Store.FlowRule storeFlow = eb.getFlowRule(); + propagateStoresTo(succ, node, currentInput, eb.getFlowRule(), addToWorklistAgain); + } + + // propagate store to exceptional successors + for (Entry<TypeMirror, Set<Block>> e : eb.getExceptionalSuccessors() + .entrySet()) { + TypeMirror cause = e.getKey(); + S exceptionalStore = transferResult + .getExceptionalStore(cause); + if (exceptionalStore != null) { + for (Block exceptionSucc : e.getValue()) { + addStoreBefore(exceptionSucc, node, exceptionalStore, Store.Kind.BOTH, + addToWorklistAgain); + } + } else { + for (Block exceptionSucc : e.getValue()) { + addStoreBefore(exceptionSucc, node, inputBefore.copy().getRegularStore(), + Store.Kind.BOTH, addToWorklistAgain); + } + } + } + break; + } + + case CONDITIONAL_BLOCK: { + ConditionalBlock cb = (ConditionalBlock) b; + + // get store before + TransferInput<A, S> inputBefore = getInputBefore(cb); + TransferInput<A, S> input = inputBefore.copy(); + + // propagate store to successor + Block thenSucc = cb.getThenSuccessor(); + Block elseSucc = cb.getElseSuccessor(); + + propagateStoresTo(thenSucc, null, input, cb.getThenFlowRule(), false); + propagateStoresTo(elseSucc, null, input, cb.getElseFlowRule(), false); + break; + } + + case SPECIAL_BLOCK: { + // special basic blocks are empty and cannot throw exceptions, + // thus there is no need to perform any analysis. + SpecialBlock sb = (SpecialBlock) b; + Block succ = sb.getSuccessor(); + if (succ != null) { + propagateStoresTo(succ, null, getInputBefore(b), sb.getFlowRule(), false); + } + break; + } + + default: + assert false; + break; + } + } + + assert isRunning == true; + isRunning = false; + } + + /** + * Propagate the stores in currentInput to the successor block, succ, according to the + * flowRule. + */ + protected void propagateStoresTo(Block succ, Node node, TransferInput<A, S> currentInput, + Store.FlowRule flowRule, boolean addToWorklistAgain) { + switch (flowRule) { + case EACH_TO_EACH: + if (currentInput.containsTwoStores()) { + addStoreBefore(succ, node, currentInput.getThenStore(), Store.Kind.THEN, + addToWorklistAgain); + addStoreBefore(succ, node, currentInput.getElseStore(), Store.Kind.ELSE, + addToWorklistAgain); + } else { + addStoreBefore(succ, node, currentInput.getRegularStore(), Store.Kind.BOTH, + addToWorklistAgain); + } + break; + case THEN_TO_BOTH: + addStoreBefore(succ, node, currentInput.getThenStore(), Store.Kind.BOTH, + addToWorklistAgain); + break; + case ELSE_TO_BOTH: + addStoreBefore(succ, node, currentInput.getElseStore(), Store.Kind.BOTH, + addToWorklistAgain); + break; + case THEN_TO_THEN: + addStoreBefore(succ, node, currentInput.getThenStore(), Store.Kind.THEN, + addToWorklistAgain); + break; + case ELSE_TO_ELSE: + addStoreBefore(succ, node, currentInput.getElseStore(), Store.Kind.ELSE, + addToWorklistAgain); + break; + } + } + + /** + * Updates the value of node {@code node} to the value of the + * {@code transferResult}. Returns true if the node's value changed, or a + * store was updated. + */ + protected boolean updateNodeValues(Node node, TransferResult<A, S> transferResult) { + A newVal = transferResult.getResultValue(); + boolean nodeValueChanged = false; + + if (newVal != null) { + A oldVal = nodeValues.get(node); + nodeValues.put(node, newVal); + nodeValueChanged = !Objects.equals(oldVal, newVal); + } + + return nodeValueChanged || transferResult.storeChanged(); + } + + /** + * Call the transfer function for node {@code node}, and set that node as + * current node first. + */ + protected TransferResult<A, S> callTransferFunction(Node node, + TransferInput<A, S> store) { + + if (node.isLValue()) { + // TODO: should the default behavior be to return either a regular + // transfer result or a conditional transfer result (depending on + // store.hasTwoStores()), or is the following correct? + return new RegularTransferResult<A, S>(null, + store.getRegularStore()); + } + store.node = node; + currentNode = node; + TransferResult<A, S> transferResult = node.accept(transferFunction, + store); + currentNode = null; + if (node instanceof ReturnNode) { + // save a copy of the store to later check if some property held at + // a given return statement + storesAtReturnStatements.put((ReturnNode) node, transferResult); + } + if (node instanceof AssignmentNode) { + // store the flow-refined value for effectively final local variables + AssignmentNode assignment = (AssignmentNode) node; + Node lhst = assignment.getTarget(); + if (lhst instanceof LocalVariableNode) { + LocalVariableNode lhs = (LocalVariableNode) lhst; + Element elem = lhs.getElement(); + if (ElementUtils.isEffectivelyFinal(elem)) { + finalLocalValues.put(elem, transferResult.getResultValue()); + } + } + } + return transferResult; + } + + /** Initialize the analysis with a new control flow graph. */ + protected void init(ControlFlowGraph cfg) { + this.cfg = cfg; + thenStores = new IdentityHashMap<>(); + elseStores = new IdentityHashMap<>(); + inputs = new IdentityHashMap<>(); + storesAtReturnStatements = new IdentityHashMap<>(); + worklist = new Worklist(cfg); + nodeValues = new IdentityHashMap<>(); + finalLocalValues = new HashMap<>(); + worklist.add(cfg.getEntryBlock()); + + List<LocalVariableNode> parameters = null; + UnderlyingAST underlyingAST = cfg.getUnderlyingAST(); + if (underlyingAST.getKind() == Kind.METHOD) { + MethodTree tree = ((CFGMethod) underlyingAST).getMethod(); + parameters = new ArrayList<>(); + for (VariableTree p : tree.getParameters()) { + LocalVariableNode var = new LocalVariableNode(p); + parameters.add(var); + // TODO: document that LocalVariableNode has no block that it + // belongs to + } + } else if (underlyingAST.getKind() == Kind.LAMBDA) { + LambdaExpressionTree lambda = ((CFGLambda) underlyingAST).getLambdaTree(); + parameters = new ArrayList<>(); + for (VariableTree p : lambda.getParameters()) { + LocalVariableNode var = new LocalVariableNode(p); + parameters.add(var); + // TODO: document that LocalVariableNode has no block that it + // belongs to + } + + } else { + // nothing to do + } + S initialStore = transferFunction.initialStore(underlyingAST, parameters); + Block entry = cfg.getEntryBlock(); + thenStores.put(entry, initialStore); + elseStores.put(entry, initialStore); + inputs.put(entry, new TransferInput<>(null, this, initialStore)); + } + + /** + * Add a basic block to the worklist. If {@code b} is already present, + * the method does nothing. + */ + protected void addToWorklist(Block b) { + // TODO: use a more efficient way to check if b is already present + if (!worklist.contains(b)) { + worklist.add(b); + } + } + + /** + * Add a store before the basic block {@code b} by merging with the + * existing stores for that location. + */ + protected void addStoreBefore(Block b, Node node, S s, Store.Kind kind, + boolean addBlockToWorklist) { + S thenStore = getStoreBefore(b, Store.Kind.THEN); + S elseStore = getStoreBefore(b, Store.Kind.ELSE); + + switch (kind) { + case THEN: { + // Update the then store + S newThenStore = (thenStore != null) ? + thenStore.leastUpperBound(s) : s; + if (!newThenStore.equals(thenStore)) { + thenStores.put(b, newThenStore); + if (elseStore != null) { + inputs.put(b, new TransferInput<>(node, this, newThenStore, elseStore)); + addBlockToWorklist = true; + } + } + break; + } + case ELSE: { + // Update the else store + S newElseStore = (elseStore != null) ? + elseStore.leastUpperBound(s) : s; + if (!newElseStore.equals(elseStore)) { + elseStores.put(b, newElseStore); + if (thenStore != null) { + inputs.put(b, new TransferInput<>(node, this, thenStore, newElseStore)); + addBlockToWorklist = true; + } + } + break; + } + case BOTH: + if (thenStore == elseStore) { + // Currently there is only one regular store + S newStore = (thenStore != null) ? + thenStore.leastUpperBound(s) : s; + if (!newStore.equals(thenStore)) { + thenStores.put(b, newStore); + elseStores.put(b, newStore); + inputs.put(b, new TransferInput<>(node, this, newStore)); + addBlockToWorklist = true; + } + } else { + boolean storeChanged = false; + + S newThenStore = (thenStore != null) ? + thenStore.leastUpperBound(s) : s; + if (!newThenStore.equals(thenStore)) { + thenStores.put(b, newThenStore); + storeChanged = true; + } + + S newElseStore = (elseStore != null) ? + elseStore.leastUpperBound(s) : s; + if (!newElseStore.equals(elseStore)) { + elseStores.put(b, newElseStore); + storeChanged = true; + } + + if (storeChanged) { + inputs.put(b, new TransferInput<>(node, this, newThenStore, newElseStore)); + addBlockToWorklist = true; + } + } + } + + if (addBlockToWorklist) { + addToWorklist(b); + } + } + + /** + * A worklist is a priority queue of blocks in which the order is given + * by depth-first ordering to place non-loop predecessors ahead of successors. + */ + protected static class Worklist { + + /** Map all blocks in the CFG to their depth-first order. */ + protected IdentityHashMap<Block, Integer> depthFirstOrder; + + /** Comparator to allow priority queue to order blocks by their depth-first + order. */ + public class DFOComparator implements Comparator<Block> { + @Override + public int compare(Block b1, Block b2) { + return depthFirstOrder.get(b1) - depthFirstOrder.get(b2); + } + } + + /** The backing priority queue. */ + protected PriorityQueue<Block> queue; + + + public Worklist(ControlFlowGraph cfg) { + depthFirstOrder = new IdentityHashMap<>(); + int count = 1; + for (Block b : cfg.getDepthFirstOrderedBlocks()) { + depthFirstOrder.put(b, count++); + } + + queue = new PriorityQueue<Block>(11, new DFOComparator()); + } + + public boolean isEmpty() { + return queue.isEmpty(); + } + + public boolean contains(Block block) { + return queue.contains(block); + } + + public void add(Block block) { + queue.add(block); + } + + public Block poll() { + return queue.poll(); + } + + @Override + public String toString() { + return "Worklist(" + queue + ")"; + } + } + + /** + * Read the {@link TransferInput} for a particular basic block (or {@code null} if + * none exists yet). + */ + public /*@Nullable*/ TransferInput<A, S> getInput(Block b) { + return getInputBefore(b); + } + + /** + * @return the transfer input corresponding to the location right before the basic + * block {@code b}. + */ + protected /*@Nullable*/ TransferInput<A, S> getInputBefore(Block b) { + return inputs.get(b); + } + + /** + * @return the store corresponding to the location right before the basic + * block {@code b}. + */ + protected /*@Nullable*/ S getStoreBefore(Block b, Store.Kind kind) { + switch (kind) { + case THEN: + return readFromStore(thenStores, b); + case ELSE: + return readFromStore(elseStores, b); + default: + assert false; + return null; + } + } + + /** + * Read the {@link Store} for a particular basic block from a map of stores + * (or {@code null} if none exists yet). + */ + protected static <S> /*@Nullable*/ S readFromStore(Map<Block, S> stores, + Block b) { + return stores.get(b); + } + + /** Is the analysis currently running? */ + public boolean isRunning() { + return isRunning; + } + + /** + * @return the abstract value for {@link Node} {@code n}, or {@code null} if + * no information is available. Note that if the analysis has not + * finished yet, this value might not represent the final value for + * this node. + */ + public /*@Nullable*/ A getValue(Node n) { + if (isRunning) { + // we do not yet have a org.checkerframework.dataflow fact about the current node + if (currentNode == n + || (currentTree != null && currentTree == n.getTree())) { + return null; + } + // check that 'n' is a subnode of 'node'. Check immediate operands + // first for efficiency. + assert currentNode != null; + assert !n.isLValue() : "Did not expect an lvalue, but got " + n; + if (!(currentNode != n && (currentNode.getOperands().contains(n) || currentNode + .getTransitiveOperands().contains(n)))) { + return null; + } + return nodeValues.get(n); + } + return nodeValues.get(n); + } + + /** + * @return the abstract value for {@link Tree} {@code t}, or {@code null} if + * no information is available. Note that if the analysis has not + * finished yet, this value might not represent the final value for + * this node. + */ + public /*@Nullable*/ A getValue(Tree t) { + // we do not yet have a org.checkerframework.dataflow fact about the current node + if (t == currentTree) { + return null; + } + Node nodeCorrespondingToTree = getNodeForTree(t); + if (nodeCorrespondingToTree == null || nodeCorrespondingToTree.isLValue()) { + return null; + } + return getValue(nodeCorrespondingToTree); + } + + /** + * Get the {@link Node} for a given {@link Tree}. + */ + public Node getNodeForTree(Tree t) { + return cfg.getNodeCorrespondingToTree(t); + } + + /** + * Get the {@link MethodTree} of the current CFG if the argument {@link Tree} maps + * to a {@link Node} in the CFG or null otherwise. + */ + public /*@Nullable*/ MethodTree getContainingMethod(Tree t) { + return cfg.getContainingMethod(t); + } + + /** + * Get the {@link ClassTree} of the current CFG if the argument {@link Tree} maps + * to a {@link Node} in the CFG or null otherwise. + */ + public /*@Nullable*/ ClassTree getContainingClass(Tree t) { + return cfg.getContainingClass(t); + } + + public List<Pair<ReturnNode, TransferResult<A, S>>> getReturnStatementStores() { + List<Pair<ReturnNode, TransferResult<A, S>>> result = new ArrayList<>(); + for (ReturnNode returnNode : cfg.getReturnNodes()) { + TransferResult<A, S> store = storesAtReturnStatements + .get(returnNode); + result.add(Pair.of(returnNode, store)); + } + return result; + } + + public AnalysisResult<A, S> getResult() { + assert !isRunning; + IdentityHashMap<Tree, Node> treeLookup = cfg.getTreeLookup(); + return new AnalysisResult<>(nodeValues, inputs, treeLookup, finalLocalValues); + } + + /** + * @return the regular exit store, or {@code null}, if there is no such + * store (because the method cannot exit through the regular exit + * block). + */ + public /*@Nullable*/ S getRegularExitStore() { + SpecialBlock regularExitBlock = cfg.getRegularExitBlock(); + if (inputs.containsKey(regularExitBlock)) { + S regularExitStore = inputs.get(regularExitBlock).getRegularStore(); + return regularExitStore; + } else { + return null; + } + } + + public S getExceptionalExitStore() { + S exceptionalExitStore = inputs.get(cfg.getExceptionalExitBlock()) + .getRegularStore(); + return exceptionalExitStore; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/AnalysisResult.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/AnalysisResult.java new file mode 100644 index 0000000000..f4d6d10451 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/AnalysisResult.java @@ -0,0 +1,237 @@ +package org.checkerframework.dataflow.analysis; + +/*>>> +import org.checkerframework.checker.nullness.qual.Nullable; +*/ + +import org.checkerframework.dataflow.cfg.block.Block; +import org.checkerframework.dataflow.cfg.block.ExceptionBlock; +import org.checkerframework.dataflow.cfg.block.RegularBlock; +import org.checkerframework.dataflow.cfg.node.Node; + +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Map.Entry; + +import javax.lang.model.element.Element; + +import com.sun.source.tree.Tree; + +/** + * An {@link AnalysisResult} represents the result of a org.checkerframework.dataflow analysis by + * providing the abstract values given a node or a tree. Note that it does not + * keep track of custom results computed by some analysis. + * + * @author Stefan Heule + * + * @param <A> + * type of the abstract value that is tracked. + */ +public class AnalysisResult<A extends AbstractValue<A>, S extends Store<S>> { + + /** Abstract values of nodes. */ + protected final IdentityHashMap<Node, A> nodeValues; + + /** Map from AST {@link Tree}s to {@link Node}s. */ + protected final IdentityHashMap<Tree, Node> treeLookup; + + /** Map from (effectively final) local variable elements to their abstract value. */ + protected final HashMap<Element, A> finalLocalValues; + + /** + * The stores before every method call. + */ + protected final IdentityHashMap<Block, TransferInput<A, S>> stores; + + /** + * Initialize with a given node-value mapping. + */ + public AnalysisResult(Map<Node, A> nodeValues, + IdentityHashMap<Block, TransferInput<A, S>> stores, + IdentityHashMap<Tree, Node> treeLookup, HashMap<Element, A> finalLocalValues) { + this.nodeValues = new IdentityHashMap<>(nodeValues); + this.treeLookup = new IdentityHashMap<>(treeLookup); + this.stores = stores; + this.finalLocalValues = finalLocalValues; + } + + /** + * Initialize empty result. + */ + public AnalysisResult() { + nodeValues = new IdentityHashMap<>(); + treeLookup = new IdentityHashMap<>(); + stores = new IdentityHashMap<>(); + finalLocalValues = new HashMap<>(); + } + + /** + * Combine with another analysis result. + */ + public void combine(AnalysisResult<A, S> other) { + for (Entry<Node, A> e : other.nodeValues.entrySet()) { + nodeValues.put(e.getKey(), e.getValue()); + } + for (Entry<Tree, Node> e : other.treeLookup.entrySet()) { + treeLookup.put(e.getKey(), e.getValue()); + } + for (Entry<Block, TransferInput<A, S>> e : other.stores.entrySet()) { + stores.put(e.getKey(), e.getValue()); + } + for (Entry<Element, A> e : other.finalLocalValues.entrySet()) { + finalLocalValues.put(e.getKey(), e.getValue()); + } + } + + /** + * @return the value of effectively final local variables + */ + public HashMap<Element, A> getFinalLocalValues() { + return finalLocalValues; + } + + /** + * @return the abstract value for {@link Node} {@code n}, or {@code null} if + * no information is available. + */ + public /*@Nullable*/ A getValue(Node n) { + return nodeValues.get(n); + } + + /** + * @return the abstract value for {@link Tree} {@code t}, or {@code null} if + * no information is available. + */ + public /*@Nullable*/ A getValue(Tree t) { + A val = getValue(treeLookup.get(t)); + return val; + } + + /** + * @return the {@link Node} for a given {@link Tree}. + */ + public /*@Nullable*/ Node getNodeForTree(Tree tree) { + return treeLookup.get(tree); + } + + /** + * @return the store immediately before a given {@link Tree}. + */ + public S getStoreBefore(Tree tree) { + Node node = getNodeForTree(tree); + if (node == null) { + return null; + } + return getStoreBefore(node); + } + + /** + * @return the store immediately before a given {@link Node}. + */ + public S getStoreBefore(Node node) { + return runAnalysisFor(node, true); + } + + /** + * @return the store immediately after a given {@link Tree}. + */ + public S getStoreAfter(Tree tree) { + Node node = getNodeForTree(tree); + if (node == null) { + return null; + } + return runAnalysisFor(node, false); + } + + /** + * Runs the analysis again within the block of {@code node} and returns the + * store at the location of {@code node}. If {@code before} is true, then + * the store immediately before the {@link Node} {@code node} is returned. + * Otherwise, the store after {@code node} is returned. + * + * <p> + * If the given {@link Node} cannot be reached (in the control flow graph), + * then {@code null} is returned. + */ + protected S runAnalysisFor(Node node, boolean before) { + Block block = node.getBlock(); + TransferInput<A, S> transferInput = stores.get(block); + if (transferInput == null) { + return null; + } + return runAnalysisFor(node, before, transferInput); + } + + /** + * Runs the analysis again within the block of {@code node} and returns the + * store at the location of {@code node}. If {@code before} is true, then + * the store immediately before the {@link Node} {@code node} is returned. + * Otherwise, the store after {@code node} is returned. + */ + public static <A extends AbstractValue<A>, S extends Store<S>> S runAnalysisFor( + Node node, boolean before, TransferInput<A, S> transferInput) { + assert node != null; + Block block = node.getBlock(); + assert transferInput != null; + Analysis<A, S, ?> analysis = transferInput.analysis; + Node oldCurrentNode = analysis.currentNode; + + if (analysis.isRunning) { + return analysis.currentInput.getRegularStore(); + } + analysis.isRunning = true; + try { + switch (block.getType()) { + case REGULAR_BLOCK: { + RegularBlock rb = (RegularBlock) block; + + // Apply transfer function to contents until we found the node + // we + // are looking for. + TransferInput<A, S> store = transferInput; + TransferResult<A, S> transferResult = null; + for (Node n : rb.getContents()) { + analysis.currentNode = n; + if (n == node && before) { + return store.getRegularStore(); + } + transferResult = analysis.callTransferFunction(n, store); + if (n == node) { + return transferResult.getRegularStore(); + } + store = new TransferInput<>(n, analysis, transferResult); + } + // This point should never be reached. If the block of 'node' is + // 'block', then 'node' must be part of the contents of 'block'. + assert false; + return null; + } + + case EXCEPTION_BLOCK: { + ExceptionBlock eb = (ExceptionBlock) block; + + // apply transfer function to content + assert eb.getNode() == node; + if (before) { + return transferInput.getRegularStore(); + } + analysis.currentNode = node; + TransferResult<A, S> transferResult = analysis + .callTransferFunction(node, transferInput); + return transferResult.getRegularStore(); + } + + default: + // Only regular blocks and exceptional blocks can hold nodes. + assert false; + break; + } + + return null; + } finally { + analysis.currentNode = oldCurrentNode; + analysis.isRunning = false; + } + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/ConditionalTransferResult.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/ConditionalTransferResult.java new file mode 100644 index 0000000000..c49357a3ff --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/ConditionalTransferResult.java @@ -0,0 +1,137 @@ +package org.checkerframework.dataflow.analysis; + +import java.util.Map; + +import javax.lang.model.type.TypeMirror; + +/** + * Implementation of a {@link TransferResult} with two non-exceptional store; + * one for the 'then' edge and one for 'else'. The result of + * {@code getRegularStore} will be the least upper bound of the two underlying + * stores. + * + * @author Stefan Heule + * + * @param <S> + * The {@link Store} used to keep track of intermediate results. + */ +public class ConditionalTransferResult<A extends AbstractValue<A>, S extends Store<S>> + extends TransferResult<A, S> { + + private final boolean storeChanged; + + /** The 'then' result store. */ + protected S thenStore; + + /** The 'else' result store. */ + protected S elseStore; + + /** + * Create a {@code ConditionalTransferResult} with {@code thenStore} as the + * resulting store if the corresponding {@link org.checkerframework.dataflow.cfg.node.Node} evaluates to + * {@code true} and {@code elseStore} otherwise. + * + * For the meaning of storeChanged, see + * {@link org.checkerframework.dataflow.analysis.TransferResult#storeChanged}. + * + * <p> + * + * <em>Exceptions</em>: If the corresponding {@link org.checkerframework.dataflow.cfg.node.Node} throws an + * exception, then it is assumed that no special handling is necessary and + * the store before the corresponding {@link org.checkerframework.dataflow.cfg.node.Node} will be passed along any + * exceptional edge. + * + * <p> + * + * <em>Aliasing</em>: {@code thenStore} and {@code elseStore} are not + * allowed to be used anywhere outside of this class (including use through + * aliases). Complete control over the objects is transfered to this class. + */ + public ConditionalTransferResult(A value, S thenStore, S elseStore, boolean storeChanged) { + super(value); + this.thenStore = thenStore; + this.elseStore = elseStore; + this.storeChanged = storeChanged; + } + + public ConditionalTransferResult(A value, S thenStore, S elseStore) { + this(value, thenStore, elseStore, false); + } + + /** + * Create a {@code ConditionalTransferResult} with {@code thenStore} as the + * resulting store if the corresponding {@link org.checkerframework.dataflow.cfg.node.Node} evaluates to + * {@code true} and {@code elseStore} otherwise. + * + * <p> + * + * <em>Exceptions</em>: If the corresponding {@link org.checkerframework.dataflow.cfg.node.Node} throws an + * exception, then the corresponding store in {@code exceptionalStores} is + * used. If no exception is found in {@code exceptionalStores}, then it is + * assumed that no special handling is necessary and the store before the + * corresponding {@link org.checkerframework.dataflow.cfg.node.Node} will be passed along any exceptional edge. + * + * <p> + * + * <em>Aliasing</em>: {@code thenStore}, {@code elseStore}, and any store in + * {@code exceptionalStores} are not allowed to be used anywhere outside of + * this class (including use through aliases). Complete control over the + * objects is transfered to this class. + */ + public ConditionalTransferResult(A value, S thenStore, S elseStore, + Map<TypeMirror, S> exceptionalStores, boolean storeChanged) { + super(value); + this.exceptionalStores = exceptionalStores; + this.thenStore = thenStore; + this.elseStore = elseStore; + this.storeChanged = storeChanged; + } + + public ConditionalTransferResult(A value, S thenStore, S elseStore, + Map<TypeMirror, S> exceptionalStores) { + this(value, thenStore, elseStore, exceptionalStores, false); + } + + @Override + public S getRegularStore() { + return thenStore.leastUpperBound(elseStore); + } + + @Override + public S getThenStore() { + return thenStore; + } + + @Override + public S getElseStore() { + return elseStore; + } + + @Override + public boolean containsTwoStores() { + return true; + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append("RegularTransferResult("); + result.append(System.getProperty("line.separator")); + result.append("resultValue = " + resultValue); + result.append(System.getProperty("line.separator")); + result.append("thenStore = " + thenStore); + result.append("elseStore = " + elseStore); + result.append(System.getProperty("line.separator")); + result.append(")"); + return result.toString(); + } + + /** + * @see org.checkerframework.dataflow.analysis.TransferResult#storeChanged() + */ + @Override + public boolean storeChanged() { + return storeChanged; + } + +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/FlowExpressions.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/FlowExpressions.java new file mode 100644 index 0000000000..fcf6a61beb --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/FlowExpressions.java @@ -0,0 +1,940 @@ +package org.checkerframework.dataflow.analysis; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + +import org.checkerframework.dataflow.cfg.node.ArrayAccessNode; +import org.checkerframework.dataflow.cfg.node.ArrayCreationNode; +import org.checkerframework.dataflow.cfg.node.ClassNameNode; +import org.checkerframework.dataflow.cfg.node.ExplicitThisLiteralNode; +import org.checkerframework.dataflow.cfg.node.FieldAccessNode; +import org.checkerframework.dataflow.cfg.node.LocalVariableNode; +import org.checkerframework.dataflow.cfg.node.MethodInvocationNode; +import org.checkerframework.dataflow.cfg.node.NarrowingConversionNode; +import org.checkerframework.dataflow.cfg.node.Node; +import org.checkerframework.dataflow.cfg.node.StringConversionNode; +import org.checkerframework.dataflow.cfg.node.SuperNode; +import org.checkerframework.dataflow.cfg.node.ThisLiteralNode; +import org.checkerframework.dataflow.cfg.node.ValueLiteralNode; +import org.checkerframework.dataflow.cfg.node.WideningConversionNode; +import org.checkerframework.dataflow.util.HashCodeUtils; +import org.checkerframework.dataflow.util.PurityUtils; +import org.checkerframework.javacutil.AnnotationProvider; +import org.checkerframework.javacutil.ElementUtils; +import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.javacutil.TypesUtils; + +import com.sun.tools.javac.code.Symbol.VarSymbol; + +/** + * Collection of classes and helper functions to represent Java expressions + * about which the org.checkerframework.dataflow analysis can possibly infer facts. Expressions + * include: + * <ul> + * <li>Field accesses (e.g., <em>o.f</em>)</li> + * <li>Local variables (e.g., <em>l</em>)</li> + * <li>This reference (e.g., <em>this</em>)</li> + * <li>Pure method calls (e.g., <em>o.m()</em>)</li> + * <li>Unknown other expressions to mark that something else was present.</li> + * </ul> + * + * @author Stefan Heule + * + */ +public class FlowExpressions { + + /** + * @return the internal representation (as {@link FieldAccess}) of a + * {@link FieldAccessNode}. Can contain {@link Unknown} as receiver. + */ + public static FieldAccess internalReprOfFieldAccess( + AnnotationProvider provider, FieldAccessNode node) { + Receiver receiver; + Node receiverNode = node.getReceiver(); + if (node.isStatic()) { + receiver = new ClassName(receiverNode.getType()); + } else { + receiver = internalReprOf(provider, receiverNode); + } + return new FieldAccess(receiver, node); + } + + /** + * @return the internal representation (as {@link FieldAccess}) of a + * {@link FieldAccessNode}. Can contain {@link Unknown} as receiver. + */ + public static ArrayAccess internalReprOfArrayAccess( + AnnotationProvider provider, ArrayAccessNode node) { + Receiver receiver = internalReprOf(provider, node.getArray()); + Receiver index = internalReprOf(provider, node.getIndex()); + return new ArrayAccess(node.getType(), receiver, index); + } + + /** + * We ignore operations such as widening and + * narrowing when computing the internal representation. + * + * @return the internal representation (as {@link Receiver}) of any + * {@link Node}. Might contain {@link Unknown}. + */ + public static Receiver internalReprOf(AnnotationProvider provider, + Node receiverNode) { + return internalReprOf(provider, receiverNode, false); + } + + /** + * We ignore operations such as widening and + * narrowing when computing the internal representation. + * + * @return the internal representation (as {@link Receiver}) of any + * {@link Node}. Might contain {@link Unknown}. + */ + public static Receiver internalReprOf(AnnotationProvider provider, + Node receiverNode, boolean allowNonDeterministic) { + Receiver receiver = null; + if (receiverNode instanceof FieldAccessNode) { + FieldAccessNode fan = (FieldAccessNode) receiverNode; + + if (fan.getFieldName().equals("this")) { + // For some reason, "className.this" is considered a field access. + // We right this wrong here. + receiver = new ThisReference(fan.getReceiver().getType()); + } else if (fan.getFieldName().equals("class")) { + // "className.class" is considered a field access. This makes sense, + // since .class is similar to a field access which is the equivalent + // of a call to getClass(). However for the purposes of dataflow + // analysis, and value stores, this is the equivalent of a ClassNameNode. + receiver = new ClassName(fan.getReceiver().getType()); + } else { + receiver = internalReprOfFieldAccess(provider, fan); + } + } else if (receiverNode instanceof ExplicitThisLiteralNode) { + receiver = new ThisReference(receiverNode.getType()); + } else if (receiverNode instanceof ThisLiteralNode) { + receiver = new ThisReference(receiverNode.getType()); + } else if (receiverNode instanceof SuperNode) { + receiver = new ThisReference(receiverNode.getType()); + } else if (receiverNode instanceof LocalVariableNode) { + LocalVariableNode lv = (LocalVariableNode) receiverNode; + receiver = new LocalVariable(lv); + } else if (receiverNode instanceof ArrayAccessNode) { + ArrayAccessNode a = (ArrayAccessNode) receiverNode; + receiver = internalReprOfArrayAccess(provider, a); + } else if (receiverNode instanceof StringConversionNode) { + // ignore string conversion + return internalReprOf(provider, + ((StringConversionNode) receiverNode).getOperand()); + } else if (receiverNode instanceof WideningConversionNode) { + // ignore widening + return internalReprOf(provider, + ((WideningConversionNode) receiverNode).getOperand()); + } else if (receiverNode instanceof NarrowingConversionNode) { + // ignore narrowing + return internalReprOf(provider, + ((NarrowingConversionNode) receiverNode).getOperand()); + } else if (receiverNode instanceof ClassNameNode) { + ClassNameNode cn = (ClassNameNode) receiverNode; + receiver = new ClassName(cn.getType()); + } else if (receiverNode instanceof ValueLiteralNode) { + ValueLiteralNode vn = (ValueLiteralNode) receiverNode; + receiver = new ValueLiteral(vn.getType(), vn); + } else if (receiverNode instanceof ArrayCreationNode) { + ArrayCreationNode an = (ArrayCreationNode)receiverNode; + receiver = new ArrayCreation(an.getType(), an.getDimensions(), an.getInitializers()); + } else if (receiverNode instanceof MethodInvocationNode) { + MethodInvocationNode mn = (MethodInvocationNode) receiverNode; + ExecutableElement invokedMethod = TreeUtils.elementFromUse(mn + .getTree()); + + // check if this represents a boxing operation of a constant, in which + // case we treat the method call as deterministic, because there is no way + // to behave differently in two executions where two constants are being used. + boolean considerDeterministic = false; + if (invokedMethod.toString().equals("valueOf(long)") + && mn.getTarget().getReceiver().toString().equals("Long")) { + Node arg = mn.getArgument(0); + if (arg instanceof ValueLiteralNode) { + considerDeterministic = true; + } + } + + if (PurityUtils.isDeterministic(provider, invokedMethod) || allowNonDeterministic || considerDeterministic) { + List<Receiver> parameters = new ArrayList<>(); + for (Node p : mn.getArguments()) { + parameters.add(internalReprOf(provider, p)); + } + Receiver methodReceiver; + if (ElementUtils.isStatic(invokedMethod)) { + methodReceiver = new ClassName(mn.getTarget().getReceiver() + .getType()); + } else { + methodReceiver = internalReprOf(provider, mn.getTarget() + .getReceiver()); + } + receiver = new MethodCall(mn.getType(), invokedMethod, + methodReceiver, parameters); + } + } + + if (receiver == null) { + receiver = new Unknown(receiverNode.getType()); + } + return receiver; + } + + public static abstract class Receiver { + protected final TypeMirror type; + + public Receiver(TypeMirror type) { + assert type != null; + this.type = type; + } + + public TypeMirror getType() { + return type; + } + + public abstract boolean containsOfClass(Class<? extends FlowExpressions.Receiver> clazz); + + public boolean containsUnknown() { + return containsOfClass(Unknown.class); + } + + /** + * Returns true if and only if the value this expression stands for + * cannot be changed by a method call. This is the case for local + * variables, the self reference as well as final field accesses for + * whose receiver {@link #isUnmodifiableByOtherCode} is true. + */ + public abstract boolean isUnmodifiableByOtherCode(); + + /** + * @return true if and only if the two receiver are syntactically + * identical + */ + public boolean syntacticEquals(Receiver other) { + return other == this; + } + + /** + * @return true if and only if this receiver contains a receiver that is + * syntactically equal to {@code other}. + */ + public boolean containsSyntacticEqualReceiver(Receiver other) { + return syntacticEquals(other); + } + + /** + * Returns true if and only if {@code other} appears anywhere in this + * receiver or an expression appears in this receiver such that + * {@code other} might alias this expression, and that expression is + * modifiable. + * + * <p> + * This is always true, except for cases where the Java type information + * prevents aliasing and none of the subexpressions can alias 'other'. + */ + public boolean containsModifiableAliasOf(Store<?> store, Receiver other) { + return this.equals(other) || store.canAlias(this, other); + } + } + + public static class FieldAccess extends Receiver { + protected Receiver receiver; + protected VariableElement field; + + public Receiver getReceiver() { + return receiver; + } + + public VariableElement getField() { + return field; + } + + public FieldAccess(Receiver receiver, FieldAccessNode node) { + super(node.getType()); + this.receiver = receiver; + this.field = node.getElement(); + } + + public FieldAccess(Receiver receiver, TypeMirror type, + VariableElement fieldElement) { + super(type); + this.receiver = receiver; + this.field = fieldElement; + } + + public boolean isFinal() { + return ElementUtils.isFinal(field); + } + + public boolean isStatic() { + return ElementUtils.isStatic(field); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof FieldAccess)) { + return false; + } + FieldAccess fa = (FieldAccess) obj; + return fa.getField().equals(getField()) + && fa.getReceiver().equals(getReceiver()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getField(), getReceiver()); + } + + @Override + public boolean containsModifiableAliasOf(Store<?> store, Receiver other) { + return super.containsModifiableAliasOf(store, other) + || receiver.containsModifiableAliasOf(store, other); + } + + @Override + public boolean containsSyntacticEqualReceiver(Receiver other) { + return syntacticEquals(other) + || receiver.containsSyntacticEqualReceiver(other); + } + + @Override + public boolean syntacticEquals(Receiver other) { + if (!(other instanceof FieldAccess)) { + return false; + } + FieldAccess fa = (FieldAccess) other; + return super.syntacticEquals(other) + || fa.getField().equals(getField()) + && fa.getReceiver().syntacticEquals(getReceiver()); + } + + @Override + public String toString() { + return receiver + "." + field; + } + + @Override + public boolean containsOfClass(Class<? extends FlowExpressions.Receiver> clazz) { + return getClass().equals(clazz) || receiver.containsOfClass(clazz); + } + + @Override + public boolean isUnmodifiableByOtherCode() { + return isFinal() && getReceiver().isUnmodifiableByOtherCode(); + } + } + + public static class ThisReference extends Receiver { + public ThisReference(TypeMirror type) { + super(type); + } + + @Override + public boolean equals(Object obj) { + return obj != null && obj instanceof ThisReference; + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(0); + } + + @Override + public String toString() { + return "this"; + } + + @Override + public boolean containsOfClass(Class<? extends FlowExpressions.Receiver> clazz) { + return getClass().equals(clazz); + } + + @Override + public boolean syntacticEquals(Receiver other) { + return other instanceof ThisReference; + } + + @Override + public boolean isUnmodifiableByOtherCode() { + return true; + } + + @Override + public boolean containsModifiableAliasOf(Store<?> store, Receiver other) { + return false; // 'this' is not modifiable + } + } + + /** + * A ClassName represents the occurrence of a class as part of a static + * field access or method invocation. + */ + public static class ClassName extends Receiver { + public ClassName(TypeMirror type) { + super(type); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof ClassName)) { + return false; + } + ClassName other = (ClassName) obj; + return getType().toString().equals(other.getType().toString()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getType().toString()); + } + + @Override + public String toString() { + return getType().toString(); + } + + @Override + public boolean containsOfClass(Class<? extends FlowExpressions.Receiver> clazz) { + return getClass().equals(clazz); + } + + @Override + public boolean syntacticEquals(Receiver other) { + return this.equals(other); + } + + @Override + public boolean isUnmodifiableByOtherCode() { + return true; + } + + @Override + public boolean containsModifiableAliasOf(Store<?> store, Receiver other) { + return false; // not modifiable + } + } + + public static class Unknown extends Receiver { + public Unknown(TypeMirror type) { + super(type); + } + + @Override + public boolean equals(Object obj) { + return obj == this; + } + + @Override + public int hashCode() { + return System.identityHashCode(this); + } + + @Override + public String toString() { + return "?"; + } + + @Override + public boolean containsModifiableAliasOf(Store<?> store, Receiver other) { + return true; + } + + @Override + public boolean containsOfClass(Class<? extends FlowExpressions.Receiver> clazz) { + return getClass().equals(clazz); + } + + @Override + public boolean isUnmodifiableByOtherCode() { + return false; + } + + } + + public static class LocalVariable extends Receiver { + protected Element element; + + public LocalVariable(LocalVariableNode localVar) { + super(localVar.getType()); + this.element = localVar.getElement(); + } + + public LocalVariable(Element elem) { + super(ElementUtils.getType(elem)); + this.element = elem; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof LocalVariable)) { + return false; + } + LocalVariable other = (LocalVariable) obj; + VarSymbol vs = (VarSymbol) element; + VarSymbol vsother = (VarSymbol) other.element; + // Use type.unannotatedType().toString().equals(...) instead of Types.isSameType(...) + // because Types requires a processing environment, and FlowExpressions is + // designed to be independent of processing environment. See also + // calls to getType().toString() in FlowExpressions. + return vsother.name.contentEquals(vs.name) && + vsother.type.unannotatedType().toString().equals(vs.type.unannotatedType().toString()) && + vsother.owner.toString().equals(vs.owner.toString()); + } + + public Element getElement() { + return element; + } + + @Override + public int hashCode() { + VarSymbol vs = (VarSymbol) element; + return HashCodeUtils.hash(vs.name.toString(), + vs.type.unannotatedType().toString(), + vs.owner.toString()); + } + + @Override + public String toString() { + return element.toString(); + } + + @Override + public boolean containsOfClass(Class<? extends FlowExpressions.Receiver> clazz) { + return getClass().equals(clazz); + } + + @Override + public boolean syntacticEquals(Receiver other) { + if (!(other instanceof LocalVariable)) { + return false; + } + LocalVariable l = (LocalVariable) other; + return l.equals(this); + } + + @Override + public boolean containsSyntacticEqualReceiver(Receiver other) { + return syntacticEquals(other); + } + + @Override + public boolean isUnmodifiableByOtherCode() { + return true; + } + } + + public static class ValueLiteral extends Receiver { + + protected final Object value; + + public ValueLiteral(TypeMirror type, ValueLiteralNode node) { + super(type); + value = node.getValue(); + } + + public ValueLiteral(TypeMirror type, Object value) { + super(type); + this.value = value; + } + + @Override + public boolean containsOfClass(Class<? extends FlowExpressions.Receiver> clazz) { + return getClass().equals(clazz); + } + + @Override + public boolean isUnmodifiableByOtherCode() { + return true; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof ValueLiteral)) { + return false; + } + ValueLiteral other = (ValueLiteral) obj; + if (value == null) { + return type.toString().equals(other.type.toString()) + && other.value == null; + } + return type.toString().equals(other.type.toString()) + && value.equals(other.value); + } + + @Override + public String toString() { + if (TypesUtils.isString(type)) { + return "\"" + value + "\""; + } else if (type.getKind() == TypeKind.LONG) { + return value.toString() + "L"; + } + return value == null ? "null" : value.toString(); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(value, type.toString()); + } + + @Override + public boolean syntacticEquals(Receiver other) { + return this.equals(other); + } + + @Override + public boolean containsModifiableAliasOf(Store<?> store, Receiver other) { + return false; // not modifiable + } + } + + /** + * A method call. + */ + public static class MethodCall extends Receiver { + + protected final Receiver receiver; + protected final List<Receiver> parameters; + protected final ExecutableElement method; + + public MethodCall(TypeMirror type, ExecutableElement method, + Receiver receiver, List<Receiver> parameters) { + super(type); + this.receiver = receiver; + this.parameters = parameters; + this.method = method; + } + + @Override + public boolean containsOfClass(Class<? extends FlowExpressions.Receiver> clazz) { + if (getClass().equals(clazz)) { + return true; + } + if (receiver.containsOfClass(clazz)) { + return true; + } + for (Receiver p : parameters) { + if (p.containsOfClass(clazz)) { + return true; + } + } + return false; + } + + /** + * @return the method call receiver (for inspection only - do not modify) + */ + public Receiver getReceiver() { + return receiver; + } + + /** + * @return the method call parameters (for inspection only - do not modify any of the parameters) + */ + public List<Receiver> getParameters() { + return Collections.unmodifiableList(parameters); + } + + /** + * @return the ExecutableElement for the method call + */ + public ExecutableElement getElement() { + return method; + } + + @Override + public boolean isUnmodifiableByOtherCode() { + return false; + } + + @Override + public boolean containsSyntacticEqualReceiver(Receiver other) { + return syntacticEquals(other) || receiver.syntacticEquals(other); + } + + @Override + public boolean syntacticEquals(Receiver other) { + if (!(other instanceof MethodCall)) { + return false; + } + MethodCall otherMethod = (MethodCall) other; + if (!receiver.syntacticEquals(otherMethod.receiver)) { + return false; + } + if (parameters.size() != otherMethod.parameters.size()) { + return false; + } + int i = 0; + for (Receiver p : parameters) { + if (!p.syntacticEquals(otherMethod.parameters.get(i))) { + return false; + } + i++; + } + return method.equals(otherMethod.method); + } + + public boolean containsSyntacticEqualParameter(LocalVariable var) { + for (Receiver p : parameters) { + if (p.containsSyntacticEqualReceiver(var)) { + return true; + } + } + return false; + } + + @Override + public boolean containsModifiableAliasOf(Store<?> store, Receiver other) { + if (receiver.containsModifiableAliasOf(store, other)) { + return true; + } + for (Receiver p : parameters) { + if (p.containsModifiableAliasOf(store, other)) { + return true; + } + } + return false; // the method call itself is not modifiable + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof MethodCall)) { + return false; + } + MethodCall other = (MethodCall) obj; + int i = 0; + for (Receiver p : parameters) { + if (!p.equals(other.parameters.get(i))) { + return false; + } + i++; + } + return receiver.equals(other.receiver) + && method.equals(other.method); + } + + @Override + public int hashCode() { + int hash = HashCodeUtils.hash(method, receiver); + for (Receiver p : parameters) { + hash = HashCodeUtils.hash(hash, p); + } + return hash; + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append(receiver.toString()); + result.append("."); + String methodName = method.getSimpleName().toString(); + result.append(methodName); + result.append("("); + boolean first = true; + for (Receiver p : parameters) { + if (!first) { + result.append(", "); + } + result.append(p.toString()); + first = false; + } + result.append(")"); + return result.toString(); + } + } + + /** + * A deterministic method call. + */ + public static class ArrayAccess extends Receiver { + + protected final Receiver receiver; + protected final Receiver index; + + public ArrayAccess(TypeMirror type, Receiver receiver, Receiver index) { + super(type); + this.receiver = receiver; + this.index = index; + } + + @Override + public boolean containsOfClass(Class<? extends FlowExpressions.Receiver> clazz) { + if (getClass().equals(clazz)) { + return true; + } + if (receiver.containsOfClass(clazz)) { + return true; + } + return index.containsOfClass(clazz); + } + + public Receiver getReceiver() { + return receiver; + } + + public Receiver getIndex() { + return index; + } + + @Override + public boolean isUnmodifiableByOtherCode() { + return false; + } + + @Override + public boolean containsSyntacticEqualReceiver(Receiver other) { + return syntacticEquals(other) || receiver.syntacticEquals(other) + || index.syntacticEquals(other); + } + + @Override + public boolean syntacticEquals(Receiver other) { + if (!(other instanceof ArrayAccess)) { + return false; + } + ArrayAccess otherArrayAccess = (ArrayAccess) other; + if (!receiver.syntacticEquals(otherArrayAccess.receiver)) { + return false; + } + return index.syntacticEquals(otherArrayAccess.index); + } + + @Override + public boolean containsModifiableAliasOf(Store<?> store, Receiver other) { + if (receiver.containsModifiableAliasOf(store, other)) { + return true; + } + return index.containsModifiableAliasOf(store, other); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof ArrayAccess)) { + return false; + } + ArrayAccess other = (ArrayAccess) obj; + return receiver.equals(other.receiver) && index.equals(other.index); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(receiver, index); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append(receiver.toString()); + result.append("["); + result.append(index.toString()); + result.append("]"); + return result.toString(); + } + } + + public static class ArrayCreation extends Receiver { + + protected List<Node> dimensions; + protected List<Node> initializers; + + public ArrayCreation(TypeMirror type, List<Node> dimensions, List<Node> initializers) { + super(type); + this.dimensions = dimensions; + this.initializers = initializers; + } + + public List<Node> getDimensions() { + return dimensions; + } + + public List<Node> getInitializers() { + return initializers; + } + + @Override + public boolean containsOfClass(Class<? extends Receiver> clazz) { + for (Node n : dimensions) { + if (n.getClass().equals(clazz)) return true; + } + for (Node n : initializers) { + if (n.getClass().equals(clazz)) return true; + } + return false; + } + + @Override + public boolean isUnmodifiableByOtherCode() { + return false; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((dimensions == null) ? 0 : dimensions.hashCode()); + result = prime * result + ((initializers == null) ? 0 : initializers.hashCode()); + result = prime * result + HashCodeUtils.hash(getType().toString()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof ArrayCreation)) { + return false; + } + ArrayCreation other = (ArrayCreation) obj; + return this.dimensions.equals(other.getDimensions()) + && this.initializers.equals(other.getInitializers()) + && getType().toString().equals(other.getType().toString()); + } + + @Override + public boolean syntacticEquals(Receiver other) { + return this.equals(other); + } + + @Override + public boolean containsSyntacticEqualReceiver(Receiver other) { + return syntacticEquals(other); + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("new " + type); + if (!dimensions.isEmpty()) { + boolean needComma = false; + sb.append(" ("); + for (Node dim : dimensions) { + if (needComma) { + sb.append(", "); + } + sb.append(dim); + needComma = true; + } + sb.append(")"); + } + if (!initializers.isEmpty()) { + boolean needComma = false; + sb.append(" = {"); + for (Node init : initializers) { + if (needComma) { + sb.append(", "); + } + sb.append(init); + needComma = true; + } + sb.append("}"); + } + return sb.toString(); + } + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/RegularTransferResult.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/RegularTransferResult.java new file mode 100644 index 0000000000..8872e7f808 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/RegularTransferResult.java @@ -0,0 +1,130 @@ +package org.checkerframework.dataflow.analysis; + +import java.util.Map; + +import javax.lang.model.type.TypeMirror; + +/** + * Implementation of a {@link TransferResult} with just one non-exceptional + * store. The result of {@code getThenStore} and {@code getElseStore} is equal + * to the only underlying store. + * + * @author Stefan Heule + * + * @param <S> + * The {@link Store} used to keep track of intermediate results. + */ +public class RegularTransferResult<A extends AbstractValue<A>, S extends Store<S>> + extends TransferResult<A, S> { + + /** The regular result store. */ + protected S store; + final private boolean storeChanged; + + /** + * Create a {@code TransferResult} with {@code resultStore} as the resulting + * store. If the corresponding {@link org.checkerframework.dataflow.cfg.node.Node} is a boolean node, then + * {@code resultStore} is used for both the 'then' and 'else' edge. + * + * <p> + * + * <em>Exceptions</em>: If the corresponding {@link org.checkerframework.dataflow.cfg.node.Node} throws an + * exception, then it is assumed that no special handling is necessary and + * the store before the corresponding {@link org.checkerframework.dataflow.cfg.node.Node} will be passed along any + * exceptional edge. + * + * <p> + * + * <em>Aliasing</em>: {@code resultStore} is not allowed to be used anywhere + * outside of this class (including use through aliases). Complete control + * over the object is transfered to this class. + */ + public RegularTransferResult(A value, S resultStore, boolean storeChanged) { + super(value); + this.store = resultStore; + this.storeChanged = storeChanged; + } + + public RegularTransferResult(A value, S resultStore) { + this(value, resultStore, false); + } + + /** + * Create a {@code TransferResult} with {@code resultStore} as the resulting + * store. If the corresponding {@link org.checkerframework.dataflow.cfg.node.Node} is a boolean node, then + * {@code resultStore} is used for both the 'then' and 'else' edge. + * + * For the meaning of storeChanged, see + * {@link org.checkerframework.dataflow.analysis.TransferResult#storeChanged}. + * + * <p> + * + * <em>Exceptions</em>: If the corresponding {@link org.checkerframework.dataflow.cfg.node.Node} throws an + * exception, then the corresponding store in {@code exceptionalStores} is + * used. If no exception is found in {@code exceptionalStores}, then it is + * assumed that no special handling is necessary and the store before the + * corresponding {@link org.checkerframework.dataflow.cfg.node.Node} will be passed along any exceptional edge. + * + * <p> + * + * <em>Aliasing</em>: {@code resultStore} and any store in + * {@code exceptionalStores} are not allowed to be used anywhere outside of + * this class (including use through aliases). Complete control over the + * objects is transfered to this class. + */ + public RegularTransferResult(A value, S resultStore, + Map<TypeMirror, S> exceptionalStores, boolean storeChanged) { + super(value); + this.store = resultStore; + this.storeChanged = storeChanged; + this.exceptionalStores = exceptionalStores; + } + + public RegularTransferResult(A value, S resultStore, + Map<TypeMirror, S> exceptionalStores) { + this(value, resultStore, exceptionalStores, false); + } + + @Override + public S getRegularStore() { + return store; + } + + @Override + public S getThenStore() { + return store; + } + + @Override + public S getElseStore() { + // copy the store such that it is the same as the result of getThenStore + // (that is, identical according to equals), but two different objects. + return store.copy(); + } + + @Override + public boolean containsTwoStores() { + return false; + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append("RegularTransferResult("); + result.append(System.getProperty("line.separator")); + result.append("resultValue = " + resultValue); + result.append(System.getProperty("line.separator")); + result.append("store = " + store); + result.append(System.getProperty("line.separator")); + result.append(")"); + return result.toString(); + } + + /** + * @see org.checkerframework.dataflow.analysis.TransferResult#storeChanged() + */ + @Override + public boolean storeChanged() { + return storeChanged; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/Store.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/Store.java new file mode 100644 index 0000000000..7d1b8f9259 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/Store.java @@ -0,0 +1,73 @@ +package org.checkerframework.dataflow.analysis; + +import org.checkerframework.dataflow.cfg.CFGVisualizer; + +/** + * A store is used to keep track of the information that the org.checkerframework.dataflow analysis + * has accumulated at any given point in time. + * + * @author Stefan Heule + * + * @param <S> + * The type of the store returned by {@code copy} and that is used in + * {@code leastUpperBound}. Usually it is the implementing class + * itself, e.g. in {@code T extends Store<T>}. + */ +public interface Store<S extends Store<S>> { + + // We maintain a then store and an else store before each basic block. + // When they are identical (by reference equality), they can be treated + // as a regular unconditional store. + // Once we have some information for both the then and else store, we + // create a TransferInput for the block and allow it to be analyzed. + public static enum Kind { + THEN, + ELSE, + BOTH + } + + /** A flow rule describes how stores flow along one edge between basic blocks. */ + public static enum FlowRule { + EACH_TO_EACH, // The normal case, then store flows to the then store + // and else store flows to the else store. + THEN_TO_BOTH, // Then store flows to both then and else of successor. + ELSE_TO_BOTH, // Else store flows to both then and else of successor. + THEN_TO_THEN, // Then store flows to the then of successor. Else store is ignored. + ELSE_TO_ELSE, // Else store flows to the else of successor. Then store is ignored. + } + + /** @return an exact copy of this store. */ + S copy(); + + /** + * Compute the least upper bound of two stores. + * + * <p> + * + * <em>Important</em>: This method must fulfill the following contract: + * <ul> + * <li>Does not change {@code this}.</li> + * <li>Does not change {@code other}.</li> + * <li>Returns a fresh object which is not aliased yet.</li> + * <li>Returns an object of the same (dynamic) type as {@code this}, even if + * the signature is more permissive.</li> + * <li>Is commutative.</li> + * </ul> + */ + S leastUpperBound(S other); + + /** + * Can the objects {@code a} and {@code b} be aliases? Returns a + * conservative answer (i.e., returns {@code true} if not enough information + * is available to determine aliasing). + */ + boolean canAlias(FlowExpressions.Receiver a, + FlowExpressions.Receiver b); + + /** + * Delegate visualization responsibility to a visualizer. + * + * @param viz the visualizer to visualize this store + */ + void visualize(CFGVisualizer<?, S, ?> viz); +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/TransferFunction.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/TransferFunction.java new file mode 100644 index 0000000000..f4a539398a --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/TransferFunction.java @@ -0,0 +1,49 @@ +package org.checkerframework.dataflow.analysis; + +/*>>> +import org.checkerframework.checker.nullness.qual.Nullable; +*/ + +import org.checkerframework.dataflow.cfg.UnderlyingAST; +import org.checkerframework.dataflow.cfg.node.LocalVariableNode; +import org.checkerframework.dataflow.cfg.node.Node; +import org.checkerframework.dataflow.cfg.node.NodeVisitor; + +import java.util.List; + +/** + * Interface of a transfer function for the abstract interpretation used for the + * flow analysis. + * + * <p> + * + * A transfer function consists of the following components: + * <ul> + * <li>A method {@code initialStore} that determines which initial store should + * be used in the org.checkerframework.dataflow analysis.</li> + * <li>A function for every {@link Node} type that determines the behavior of + * the org.checkerframework.dataflow analysis in that case. This method takes a {@link Node} and an + * incoming store, and produces a {@link RegularTransferResult}.</li> + * </ul> + * + * <p> + * + * <em>Important</em>: The individual transfer functions ( {@code visit*}) are + * allowed to use (and modify) the stores contained in the argument passed; the + * ownership is transfered from the caller to that function. + * + * @author Stefan Heule + * + * @param <S> + * The {@link Store} used to keep track of intermediate results. + */ +public interface TransferFunction<A extends AbstractValue<A>, S extends Store<S>> + extends NodeVisitor<TransferResult<A, S>, TransferInput<A, S>> { + + /** + * @return the initial store to be used by the org.checkerframework.dataflow analysis. + * {@code parameters} is only set if the underlying AST is a method. + */ + S initialStore(UnderlyingAST underlyingAST, + /*@Nullable*/ List<LocalVariableNode> parameters); +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/TransferInput.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/TransferInput.java new file mode 100644 index 0000000000..fd707ae59d --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/TransferInput.java @@ -0,0 +1,283 @@ +package org.checkerframework.dataflow.analysis; + +/*>>> +import org.checkerframework.checker.nullness.qual.Nullable; +*/ + +import org.checkerframework.dataflow.cfg.node.Node; +import org.checkerframework.dataflow.util.HashCodeUtils; + +/** + * {@code TransferInput} is used as the input type of the individual transfer + * functions of a {@link TransferFunction}. It also contains a reference to the + * node for which the transfer function will be applied. + * + * <p> + * + * A {@code TransferInput} contains one or two stores. If two stores are + * present, one belongs to 'then', and the other to 'else'. + * + * @author Stefan Heule + * + * @param <S> + * The {@link Store} used to keep track of intermediate results. + */ +public class TransferInput<A extends AbstractValue<A>, S extends Store<S>> { + + /** + * The corresponding node. + */ + protected Node node; + + /** + * The regular result store (or {@code null} if none is present). The + * following invariant is maintained: + * + * <pre>{@code + * store == null ⇔ thenStore != null && elseStore != null + * }</pre> + */ + protected final /*@Nullable*/ S store; + + /** + * The 'then' result store (or {@code null} if none is present). The + * following invariant is maintained: + * + * <pre>{@code + * store == null ⇔ thenStore != null && elseStore != null + * }</pre> + */ + protected final /*@Nullable*/ S thenStore; + + /** + * The 'else' result store (or {@code null} if none is present). The + * following invariant is maintained: + * + * <pre>{@code + * store == null ⇔ thenStore != null && elseStore != null + * }</pre> + */ + protected final /*@Nullable*/ S elseStore; + + /** + * The corresponding analysis class to get intermediate flow results. + */ + protected final Analysis<A, S, ?> analysis; + + /** + * Create a {@link TransferInput}, given a {@link TransferResult} and a + * node-value mapping. + * + * <p> + * + * <em>Aliasing</em>: The stores returned by any methods of {@code to} will + * be stored internally and are not allowed to be used elsewhere. Full + * control of them is transfered to this object. + * + * <p> + * + * The node-value mapping {@code nodeValues} is provided by the analysis and + * is only read from within this {@link TransferInput}. + */ + public TransferInput(Node n, Analysis<A, S, ?> analysis, + TransferResult<A, S> to) { + node = n; + this.analysis = analysis; + if (to.containsTwoStores()) { + thenStore = to.getThenStore(); + elseStore = to.getElseStore(); + store = null; + } else { + store = to.getRegularStore(); + thenStore = elseStore = null; + } + } + + /** + * Create a {@link TransferInput}, given a store and a node-value mapping. + * + * <p> + * + * <em>Aliasing</em>: The store {@code s} will be stored internally and is + * not allowed to be used elsewhere. Full control over {@code s} is + * transfered to this object. + * + * <p> + * + * The node-value mapping {@code nodeValues} is provided by the analysis and + * is only read from within this {@link TransferInput}. + */ + public TransferInput(Node n, Analysis<A, S, ?> analysis, S s) { + node = n; + this.analysis = analysis; + store = s; + thenStore = elseStore = null; + } + + /** + * Create a {@link TransferInput}, given two stores and a node-value + * mapping. + * + * <p> + * + * <em>Aliasing</em>: The two stores {@code s1} and {@code s2} will be + * stored internally and are not allowed to be used elsewhere. Full control + * of them is transfered to this object. + */ + public TransferInput(Node n, Analysis<A, S, ?> analysis, S s1, S s2) { + node = n; + this.analysis = analysis; + thenStore = s1; + elseStore = s2; + store = null; + } + + /** + * Copy constructor. + */ + protected TransferInput(TransferInput<A, S> from) { + this.node = from.node; + this.analysis = from.analysis; + if (from.store == null) { + thenStore = from.thenStore.copy(); + elseStore = from.elseStore.copy(); + store = null; + } else { + store = from.store.copy(); + thenStore = elseStore = null; + } + } + + /** + * @return the {@link Node} for this {@link TransferInput}. + */ + public Node getNode() { + return node; + } + + /** + * @return the abstract value of {@link Node} {@code n}, which is required + * to be a 'sub-node' (that is, a direct or indirect child) of the + * node this transfer input is associated with. Furthermore, + * {@code n} cannot be a l-value node. Returns {@code null} if no + * value if available. + */ + public /*@Nullable*/ A getValueOfSubNode(Node n) { + return analysis.getValue(n); + } + + /** + * @return the regular result store produced if no exception is thrown by + * the {@link Node} corresponding to this transfer function result. + */ + public S getRegularStore() { + if (store == null) { + return thenStore.leastUpperBound(elseStore); + } else { + return store; + } + } + + /** + * @return the result store produced if the {@link Node} this result belongs + * to evaluates to {@code true}. + */ + public S getThenStore() { + if (store == null) { + return thenStore; + } + return store; + } + + /** + * @return the result store produced if the {@link Node} this result belongs + * to evaluates to {@code false}. + */ + public S getElseStore() { + if (store == null) { + return elseStore; + } + // copy the store such that it is the same as the result of getThenStore + // (that is, identical according to equals), but two different objects. + return store.copy(); + } + + /** + * @return {@code true} if and only if this transfer input contains two + * stores that are potentially not equal. Note that the result + * {@code true} does not imply that {@code getRegularStore} cannot + * be called (or vice versa for {@code false}). Rather, it indicates + * that {@code getThenStore} or {@code getElseStore} can be used to + * give more precise results. Otherwise, if the result is + * {@code false}, then all three methods {@code getRegularStore}, + * {@code getThenStore}, and {@code getElseStore} return equivalent + * stores. + */ + public boolean containsTwoStores() { + return (thenStore != null && elseStore != null); + } + + /** @return an exact copy of this store. */ + public TransferInput<A, S> copy() { + return new TransferInput<>(this); + } + + /** + * Compute the least upper bound of two stores. + * + * <p> + * + * <em>Important</em>: This method must fulfill the same contract as + * {@code leastUpperBound} of {@link Store}. + */ + public TransferInput<A, S> leastUpperBound(TransferInput<A, S> other) { + if (store == null) { + S newThenStore = thenStore.leastUpperBound(other.getThenStore()); + S newElseStore = elseStore.leastUpperBound(other.getElseStore()); + return new TransferInput<>(node, analysis, newThenStore, + newElseStore); + } else { + if (other.store == null) { + // make sure we do not lose precision and keep two stores if at + // least one of the two TransferInput's has two stores. + return other.leastUpperBound(this); + } + return new TransferInput<>(node, analysis, + store.leastUpperBound(other.getRegularStore())); + } + } + + @Override + public boolean equals(Object o) { + if (o != null && o instanceof TransferInput) { + @SuppressWarnings("unchecked") + TransferInput<A, S> other = (TransferInput<A, S>) o; + if (containsTwoStores()) { + if (other.containsTwoStores()) { + return getThenStore().equals(other.getThenStore()) && + getElseStore().equals(other.getElseStore()); + } + } else { + if (!other.containsTwoStores()) { + return getRegularStore().equals(other.getRegularStore()); + } + } + } + return false; + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(this.analysis, this.node, this.store, this.thenStore, this.elseStore); + } + + @Override + public String toString() { + if (store == null) { + return "[then=" + thenStore + ", else=" + elseStore + "]"; + } else { + return "[" + store + "]"; + } + } + +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/TransferResult.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/TransferResult.java new file mode 100644 index 0000000000..fabba9c511 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/analysis/TransferResult.java @@ -0,0 +1,116 @@ +package org.checkerframework.dataflow.analysis; + +/*>>> +import org.checkerframework.checker.nullness.qual.Nullable; +*/ + +import java.util.Map; + +import javax.lang.model.type.TypeMirror; + +/** + * {@code TransferResult} is used as the result type of the individual transfer + * functions of a {@link TransferFunction}. It always belongs to the result of + * the individual transfer function for a particular {@link org.checkerframework.dataflow.cfg.node.Node}, even though + * that {@code org.checkerframework.dataflow.cfg.node.Node} is not explicitly store in {@code TransferResult}. + * + * <p> + * + * A {@code TransferResult} contains one or two stores (for 'then' and 'else'), + * and zero or more stores with a cause ({@link TypeMirror}). + * + * @author Stefan Heule + * + * @param <S> + * The {@link Store} used to keep track of intermediate results. + */ +abstract public class TransferResult<A extends AbstractValue<A>, S extends Store<S>> { + + /** + * The stores in case the basic block throws an exception (or {@code null} + * if the corresponding {@link org.checkerframework.dataflow.cfg.node.Node} does not throw any exceptions). Does + * not necessarily contain a store for every exception, in which case the + * in-store will be used. + */ + protected /*@Nullable*/ Map<TypeMirror, S> exceptionalStores; + + /** + * The abstract value of the {@link org.checkerframework.dataflow.cfg.node.Node} associated with this + * {@link TransferResult}, or {@code null} if no value has been produced. + */ + protected /*@Nullable*/ A resultValue; + + public TransferResult(/*@Nullable*/ A resultValue) { + this.resultValue = resultValue; + } + + /** + * @return the abstract value produced by the transfer function + */ + public A getResultValue() { + return resultValue; + } + + public void setResultValue(A resultValue) { + this.resultValue = resultValue; + } + + /** + * @return the regular result store produced if no exception is thrown by + * the {@link org.checkerframework.dataflow.cfg.node.Node} corresponding to this transfer function result. + */ + abstract public S getRegularStore(); + + /** + * @return the result store produced if the {@link org.checkerframework.dataflow.cfg.node.Node} this result belongs + * to evaluates to {@code true}. + */ + abstract public S getThenStore(); + + /** + * @return the result store produced if the {@link org.checkerframework.dataflow.cfg.node.Node} this result belongs + * to evaluates to {@code false}. + */ + abstract public S getElseStore(); + + /** + * @return the store that flows along the outgoing exceptional edge labeled + * with {@code exception} (or {@code null} if no special handling is + * required for exceptional edges). + */ + public /*@Nullable*/ S getExceptionalStore( + TypeMirror exception) { + if (exceptionalStores == null) { + return null; + } + return exceptionalStores.get(exception); + } + + /** + * @return a Map of {@link TypeMirror} to {@link Store}. + * + * @see TransferResult#getExceptionalStore(TypeMirror) + */ + public Map<TypeMirror, S> getExceptionalStores() { + return exceptionalStores; + } + + /** + * @return {@code true} if and only if this transfer result contains two + * stores that are potentially not equal. Note that the result + * {@code true} does not imply that {@code getRegularStore} cannot + * be called (or vice versa for {@code false}). Rather, it indicates + * that {@code getThenStore} or {@code getElseStore} can be used to + * give more precise results. Otherwise, if the result is + * {@code false}, then all three methods {@code getRegularStore}, + * {@code getThenStore}, and {@code getElseStore} return equivalent + * stores. + */ + abstract public boolean containsTwoStores(); + + /** + * @return {@code true} if and only if the transfer function returning this + * transfer result changed the regularStore, elseStore, or thenStore. + */ + abstract public boolean storeChanged(); +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/CFGBuilder.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/CFGBuilder.java new file mode 100644 index 0000000000..2cff0f2802 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/CFGBuilder.java @@ -0,0 +1,4448 @@ +package org.checkerframework.dataflow.cfg; + +/*>>> +import org.checkerframework.checker.nullness.qual.Nullable; +*/ + +import org.checkerframework.dataflow.analysis.Store; +import org.checkerframework.dataflow.cfg.CFGBuilder.ExtendedNode.ExtendedNodeType; +import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGMethod; +import org.checkerframework.dataflow.cfg.block.Block; +import org.checkerframework.dataflow.cfg.block.Block.BlockType; +import org.checkerframework.dataflow.cfg.block.BlockImpl; +import org.checkerframework.dataflow.cfg.block.ConditionalBlockImpl; +import org.checkerframework.dataflow.cfg.block.ExceptionBlockImpl; +import org.checkerframework.dataflow.cfg.block.RegularBlockImpl; +import org.checkerframework.dataflow.cfg.block.SingleSuccessorBlockImpl; +import org.checkerframework.dataflow.cfg.block.SpecialBlock.SpecialBlockType; +import org.checkerframework.dataflow.cfg.block.SpecialBlockImpl; +import org.checkerframework.dataflow.cfg.node.*; +import org.checkerframework.dataflow.qual.TerminatesExecution; +import org.checkerframework.dataflow.util.MostlySingleton; +import org.checkerframework.javacutil.AnnotationProvider; +import org.checkerframework.javacutil.BasicAnnotationProvider; +import org.checkerframework.javacutil.ElementUtils; +import org.checkerframework.javacutil.InternalUtils; +import org.checkerframework.javacutil.Pair; +import org.checkerframework.javacutil.TreeUtils; +import org.checkerframework.javacutil.TypesUtils; +import org.checkerframework.javacutil.trees.TreeBuilder; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.PrimitiveType; +import javax.lang.model.type.ReferenceType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.type.UnionType; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; + +import com.sun.source.tree.*; +import com.sun.source.tree.Tree.Kind; +import com.sun.source.util.TreePath; +import com.sun.source.util.TreePathScanner; +import com.sun.source.util.Trees; +import com.sun.tools.javac.code.Symbol.MethodSymbol; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.processing.JavacProcessingEnvironment; +import com.sun.tools.javac.util.Context; + +/** + * Builds the control flow graph of some Java code (either a method, or an + * arbitrary statement). + * + * <p> + * + * The translation of the AST to the CFG is split into three phases: + * <ol> + * <li><em>Phase one.</em> In the first phase, the AST is translated into a + * sequence of {@link org.checkerframework.dataflow.cfg.CFGBuilder.ExtendedNode}s. An extended node can either be a + * {@link Node}, or one of several meta elements such as a conditional or + * unconditional jump or a node with additional information about exceptions. + * Some of the extended nodes contain labels (e.g., for the jump target), and + * phase one additionally creates a mapping from labels to extended nodes. + * Finally, the list of leaders is computed: A leader is an extended node which + * will give rise to a basic block in phase two.</li> + * <li><em>Phase two.</em> In this phase, the sequence of extended nodes is + * translated to a graph of control flow blocks that contain nodes. The meta + * elements from phase one are translated into the correct edges.</li> + * <li><em>Phase three.</em> The control flow graph generated in phase two can + * contain degenerate basic blocks such as empty regular basic blocks or + * conditional basic blocks that have the same block as both 'then' and 'else' + * successor. This phase removes these cases while preserving the control flow + * structure.</li> + * </ol> + * + * @author Stefan Heule + * + */ +public class CFGBuilder { + + /** Can assertions be assumed to be disabled? */ + protected final boolean assumeAssertionsDisabled; + + /** Can assertions be assumed to be enabled? */ + protected final boolean assumeAssertionsEnabled; + + public CFGBuilder(boolean assumeAssertionsEnabled, boolean assumeAssertionsDisabled) { + assert !(assumeAssertionsDisabled && assumeAssertionsEnabled); + this.assumeAssertionsEnabled = assumeAssertionsEnabled; + this.assumeAssertionsDisabled = assumeAssertionsDisabled; + } + + /** + * Class declarations that have been encountered when building the + * control-flow graph for a method. + */ + protected final List<ClassTree> declaredClasses = new LinkedList<>(); + + public List<ClassTree> getDeclaredClasses() { + return declaredClasses; + } + + /** + * Lambdas encountered when building the control-flow graph for + * a method, variable initializer, or initializer. + */ + protected final List<LambdaExpressionTree> declaredLambdas = new LinkedList<>(); + + public List<LambdaExpressionTree> getDeclaredLambdas() { + return declaredLambdas; + } + + /** + * Build the control flow graph of some code. + */ + public static ControlFlowGraph build( + CompilationUnitTree root, ProcessingEnvironment env, + UnderlyingAST underlyingAST, boolean assumeAssertionsEnabled, boolean assumeAssertionsDisabled) { + return new CFGBuilder(assumeAssertionsEnabled, assumeAssertionsDisabled).run(root, env, underlyingAST); + } + + /** + * Build the control flow graph of some code (method, initializer block, ...). + * bodyPath is the TreePath to the body of that code. + */ + public static ControlFlowGraph build( + TreePath bodyPath, ProcessingEnvironment env, + UnderlyingAST underlyingAST, boolean assumeAssertionsEnabled, boolean assumeAssertionsDisabled) { + return new CFGBuilder(assumeAssertionsEnabled, assumeAssertionsDisabled).run(bodyPath, env, underlyingAST); + } + + /** + * Build the control flow graph of a method. + */ + public static ControlFlowGraph build( + CompilationUnitTree root, ProcessingEnvironment env, + MethodTree tree, ClassTree classTree, boolean assumeAssertionsEnabled, boolean assumeAssertionsDisabled) { + return new CFGBuilder(assumeAssertionsEnabled, assumeAssertionsDisabled).run(root, env, tree, classTree); + } + + /** + * Build the control flow graph of some code. + */ + public static ControlFlowGraph build( + CompilationUnitTree root, ProcessingEnvironment env, + UnderlyingAST underlyingAST) { + return new CFGBuilder(false, false).run(root, env, underlyingAST); + } + + /** + * Build the control flow graph of a method. + */ + public static ControlFlowGraph build( + CompilationUnitTree root, ProcessingEnvironment env, + MethodTree tree, ClassTree classTree) { + return new CFGBuilder(false, false).run(root, env, tree, classTree); + } + + /** + * Build the control flow graph of some code. + */ + public ControlFlowGraph run( + CompilationUnitTree root, ProcessingEnvironment env, + UnderlyingAST underlyingAST) { + declaredClasses.clear(); + declaredLambdas.clear(); + + TreeBuilder builder = new TreeBuilder(env); + AnnotationProvider annotationProvider = new BasicAnnotationProvider(); + PhaseOneResult phase1result = new CFGTranslationPhaseOne().process( + root, env, underlyingAST, exceptionalExitLabel, builder, annotationProvider); + ControlFlowGraph phase2result = new CFGTranslationPhaseTwo() + .process(phase1result); + ControlFlowGraph phase3result = CFGTranslationPhaseThree + .process(phase2result); + return phase3result; + } + + /** + * Build the control flow graph of some code (method, initializer block, ...). + * bodyPath is the TreePath to the body of that code. + */ + public ControlFlowGraph run( + TreePath bodyPath, ProcessingEnvironment env, + UnderlyingAST underlyingAST) { + declaredClasses.clear(); + TreeBuilder builder = new TreeBuilder(env); + AnnotationProvider annotationProvider = new BasicAnnotationProvider(); + PhaseOneResult phase1result = new CFGTranslationPhaseOne().process( + bodyPath, env, underlyingAST, exceptionalExitLabel, builder, annotationProvider); + ControlFlowGraph phase2result = new CFGTranslationPhaseTwo() + .process(phase1result); + ControlFlowGraph phase3result = CFGTranslationPhaseThree + .process(phase2result); + return phase3result; + } + + /** + * Build the control flow graph of a method. + */ + public ControlFlowGraph run( + CompilationUnitTree root, ProcessingEnvironment env, + MethodTree tree, ClassTree classTree) { + UnderlyingAST underlyingAST = new CFGMethod(tree, classTree); + return run(root, env, underlyingAST); + } + + /* --------------------------------------------------------- */ + /* Extended Node Types and Labels */ + /* --------------------------------------------------------- */ + + /** Special label to identify the exceptional exit. */ + protected final Label exceptionalExitLabel = new Label(); + + /** Special label to identify the regular exit. */ + protected final Label regularExitLabel = new Label(); + + /** + * An extended node can be one of several things (depending on its + * {@code type}): + * <ul> + * <li><em>NODE</em>. An extended node of this type is just a wrapper for a + * {@link Node} (that cannot throw exceptions).</li> + * <li><em>EXCEPTION_NODE</em>. A wrapper for a {@link Node} which can throw + * exceptions. It contains a label for every possible exception type the + * node might throw.</li> + * <li><em>UNCONDITIONAL_JUMP</em>. An unconditional jump to a label.</li> + * <li><em>TWO_TARGET_CONDITIONAL_JUMP</em>. A conditional jump with two + * targets for both the 'then' and 'else' branch.</li> + * </ul> + */ + protected static abstract class ExtendedNode { + + /** + * The basic block this extended node belongs to (as determined in phase + * two). + */ + protected BlockImpl block; + + /** Type of this node. */ + protected ExtendedNodeType type; + + /** Does this node terminate the execution? (e.g., "System.exit()") */ + protected boolean terminatesExecution = false; + + public ExtendedNode(ExtendedNodeType type) { + this.type = type; + } + + /** Extended node types (description see above). */ + public enum ExtendedNodeType { + NODE, EXCEPTION_NODE, UNCONDITIONAL_JUMP, CONDITIONAL_JUMP + } + + public ExtendedNodeType getType() { + return type; + } + + public boolean getTerminatesExecution() { + return terminatesExecution; + } + + public void setTerminatesExecution(boolean terminatesExecution) { + this.terminatesExecution = terminatesExecution; + } + + /** + * @return the node contained in this extended node (only applicable if + * the type is {@code NODE} or {@code EXCEPTION_NODE}). + */ + public Node getNode() { + assert false; + return null; + } + + /** + * @return the label associated with this extended node (only applicable + * if type is {@link ExtendedNodeType#CONDITIONAL_JUMP} or + * {@link ExtendedNodeType#UNCONDITIONAL_JUMP}). + */ + public Label getLabel() { + assert false; + return null; + } + + public BlockImpl getBlock() { + return block; + } + + public void setBlock(BlockImpl b) { + this.block = b; + } + + @Override + public String toString() { + return "ExtendedNode(" + type + ")"; + } + } + + /** + * An extended node of type {@code NODE}. + */ + protected static class NodeHolder extends ExtendedNode { + + protected Node node; + + public NodeHolder(Node node) { + super(ExtendedNodeType.NODE); + this.node = node; + } + + @Override + public Node getNode() { + return node; + } + + @Override + public String toString() { + return "NodeHolder(" + node + ")"; + } + + } + + /** + * An extended node of type {@code EXCEPTION_NODE}. + */ + protected static class NodeWithExceptionsHolder extends ExtendedNode { + + protected Node node; + /** + * Map from exception type to labels of successors that may + * be reached as a result of that exception. + */ + protected Map<TypeMirror, Set<Label>> exceptions; + + public NodeWithExceptionsHolder(Node node, + Map<TypeMirror, Set<Label>> exceptions) { + super(ExtendedNodeType.EXCEPTION_NODE); + this.node = node; + this.exceptions = exceptions; + } + + public Map<TypeMirror, Set<Label>> getExceptions() { + return exceptions; + } + + @Override + public Node getNode() { + return node; + } + + @Override + public String toString() { + return "NodeWithExceptionsHolder(" + node + ")"; + } + + } + + /** + * An extended node of type {@link ExtendedNodeType#CONDITIONAL_JUMP}. + * + * <p> + * + * <em>Important:</em> In the list of extended nodes, there should not be + * any labels that point to a conditional jump. Furthermore, the node + * directly ahead of any conditional jump has to be a + * {@link NodeWithExceptionsHolder} or {@link NodeHolder}, and the node held + * by that extended node is required to be of boolean type. + */ + protected static class ConditionalJump extends ExtendedNode { + + protected Label trueSucc; + protected Label falseSucc; + + protected Store.FlowRule trueFlowRule; + protected Store.FlowRule falseFlowRule; + + public ConditionalJump(Label trueSucc, Label falseSucc) { + super(ExtendedNodeType.CONDITIONAL_JUMP); + this.trueSucc = trueSucc; + this.falseSucc = falseSucc; + } + + public Label getThenLabel() { + return trueSucc; + } + + public Label getElseLabel() { + return falseSucc; + } + + public Store.FlowRule getTrueFlowRule() { + return trueFlowRule; + } + + public Store.FlowRule getFalseFlowRule() { + return falseFlowRule; + } + + public void setTrueFlowRule(Store.FlowRule rule) { + trueFlowRule = rule; + } + + public void setFalseFlowRule(Store.FlowRule rule) { + falseFlowRule = rule; + } + + @Override + public String toString() { + return "TwoTargetConditionalJump(" + getThenLabel() + "," + + getElseLabel() + ")"; + } + } + + /** + * An extended node of type {@link ExtendedNodeType#UNCONDITIONAL_JUMP}. + */ + protected static class UnconditionalJump extends ExtendedNode { + + protected Label jumpTarget; + + public UnconditionalJump(Label jumpTarget) { + super(ExtendedNodeType.UNCONDITIONAL_JUMP); + this.jumpTarget = jumpTarget; + } + + @Override + public Label getLabel() { + return jumpTarget; + } + + @Override + public String toString() { + return "JumpMarker(" + getLabel() + ")"; + } + } + + /** + * A label is used to refer to other extended nodes using a mapping from + * labels to extended nodes. Labels get their names either from labeled + * statements in the source code or from internally generated unique names. + */ + protected static class Label { + private static int uid = 0; + + protected String name; + + public Label(String name) { + this.name = name; + } + + public Label() { + this.name = uniqueName(); + } + + @Override + public String toString() { + return name; + } + + /** + * Return a new unique label name that cannot be confused with a Java + * source code label. + * + * @return a new unique label name + */ + private static String uniqueName() { + return "%L" + uid++; + } + } + + /** + * A TryFrame takes a thrown exception type and maps it to a set + * of possible control-flow successors. + */ + protected static interface TryFrame { + /** + * Given a type of thrown exception, add the set of possible control + * flow successor {@link Label}s to the argument set. Return true + * if the exception is known to be caught by one of those labels and + * false if it may propagate still further. + */ + public boolean possibleLabels(TypeMirror thrown, Set<Label> labels); + } + + /** + * A TryCatchFrame contains an ordered list of catch labels that apply + * to exceptions with specific types. + */ + protected static class TryCatchFrame implements TryFrame { + protected Types types; + + /** An ordered list of pairs because catch blocks are ordered. */ + protected List<Pair<TypeMirror, Label>> catchLabels; + + public TryCatchFrame(Types types, List<Pair<TypeMirror, Label>> catchLabels) { + this.types = types; + this.catchLabels = catchLabels; + } + + /** + * Given a type of thrown exception, add the set of possible control + * flow successor {@link Label}s to the argument set. Return true + * if the exception is known to be caught by one of those labels and + * false if it may propagate still further. + */ + @Override + public boolean possibleLabels(TypeMirror thrown, Set<Label> labels) { + // A conservative approach would be to say that every catch block + // might execute for any thrown exception, but we try to do better. + // + // We rely on several assumptions that seem to hold as of Java 7. + // 1) An exception parameter in a catch block must be either + // a declared type or a union composed of declared types, + // all of which are subtypes of Throwable. + // 2) A thrown type must either be a declared type or a variable + // that extends a declared type, which is a subtype of Throwable. + // + // Under those assumptions, if the thrown type (or its bound) is + // a subtype of the caught type (or one of its alternatives), then + // the catch block must apply and none of the later ones can apply. + // Otherwise, if the thrown type (or its bound) is a supertype + // of the caught type (or one of its alternatives), then the catch + // block may apply, but so may later ones. + // Otherwise, the thrown type and the caught type are unrelated + // declared types, so they do not overlap on any non-null value. + + while (!(thrown instanceof DeclaredType)) { + assert thrown instanceof TypeVariable : + "thrown type must be a variable or a declared type"; + thrown = ((TypeVariable)thrown).getUpperBound(); + } + DeclaredType declaredThrown = (DeclaredType)thrown; + assert thrown != null : "thrown type must be bounded by a declared type"; + + for (Pair<TypeMirror, Label> pair : catchLabels) { + TypeMirror caught = pair.first; + boolean canApply = false; + + if (caught instanceof DeclaredType) { + DeclaredType declaredCaught = (DeclaredType)caught; + if (types.isSubtype(declaredThrown, declaredCaught)) { + // No later catch blocks can apply. + labels.add(pair.second); + return true; + } else if (types.isSubtype(declaredCaught, declaredThrown)) { + canApply = true; + } + } else { + assert caught instanceof UnionType : + "caught type must be a union or a declared type"; + UnionType caughtUnion = (UnionType)caught; + for (TypeMirror alternative : caughtUnion.getAlternatives()) { + assert alternative instanceof DeclaredType : + "alternatives of an caught union type must be declared types"; + DeclaredType declaredAlt = (DeclaredType)alternative; + if (types.isSubtype(declaredThrown, declaredAlt)) { + // No later catch blocks can apply. + labels.add(pair.second); + return true; + } else if (types.isSubtype(declaredAlt, declaredThrown)) { + canApply = true; + } + } + } + + if (canApply) { + labels.add(pair.second); + } + } + + return false; + } + } + + /** + * A TryFinallyFrame applies to exceptions of any type + */ + protected class TryFinallyFrame implements TryFrame { + protected Label finallyLabel; + + public TryFinallyFrame(Label finallyLabel) { + this.finallyLabel = finallyLabel; + } + + @Override + public boolean possibleLabels(TypeMirror thrown, Set<Label> labels) { + labels.add(finallyLabel); + return true; + } + } + + /** + * An exception stack represents the set of all try-catch blocks + * in effect at a given point in a program. It maps an exception + * type to a set of Labels and it maps a block exit (via return or + * fall-through) to a single Label. + */ + protected static class TryStack { + protected Label exitLabel; + protected LinkedList<TryFrame> frames; + + public TryStack(Label exitLabel) { + this.exitLabel = exitLabel; + this.frames = new LinkedList<>(); + } + + public void pushFrame(TryFrame frame) { + frames.addFirst(frame); + } + + public void popFrame() { + frames.removeFirst(); + } + + /** + * Returns the set of possible {@link Label}s where control may + * transfer when an exception of the given type is thrown. + */ + public Set<Label> possibleLabels(TypeMirror thrown) { + // Work up from the innermost frame until the exception is known to + // be caught. + Set<Label> labels = new MostlySingleton<>(); + for (TryFrame frame : frames) { + if (frame.possibleLabels(thrown, labels)) { + return labels; + } + } + labels.add(exitLabel); + return labels; + } + } + + /* --------------------------------------------------------- */ + /* Phase Three */ + /* --------------------------------------------------------- */ + + /** + * Class that performs phase three of the translation process. In + * particular, the following degenerate cases of basic blocks are removed: + * + * <ol> + * <li>Empty regular basic blocks: These blocks will be removed and their + * predecessors linked directly to the successor.</li> + * <li>Conditional basic blocks that have the same basic block as the 'then' + * and 'else' successor: The conditional basic block will be removed in this + * case.</li> + * <li>Two consecutive, non-empty, regular basic blocks where the second + * block has exactly one predecessor (namely the other of the two blocks): + * In this case, the two blocks are merged.</li> + * <li>Some basic blocks might not be reachable from the entryBlock. These + * basic blocks are removed, and the list of predecessors (in the + * doubly-linked structure of basic blocks) are adapted correctly.</li> + * </ol> + * + * Eliminating the second type of degenerate cases might introduce cases of + * the third problem. These are also removed. + */ + public static class CFGTranslationPhaseThree { + + /** + * A simple wrapper object that holds a basic block and allows to set + * one of its successors. + */ + protected interface PredecessorHolder { + void setSuccessor(BlockImpl b); + + BlockImpl getBlock(); + } + + /** + * Perform phase three on the control flow graph {@code cfg}. + * + * @param cfg + * The control flow graph. Ownership is transfered to this + * method and the caller is not allowed to read or modify + * {@code cfg} after the call to {@code process} any more. + * @return the resulting control flow graph + */ + public static ControlFlowGraph process(ControlFlowGraph cfg) { + Set<Block> worklist = cfg.getAllBlocks(); + Set<Block> dontVisit = new HashSet<>(); + + // note: this method has to be careful when relinking basic blocks + // to not forget to adjust the predecessors, too + + // fix predecessor lists by removing any unreachable predecessors + for (Block c : worklist) { + BlockImpl cur = (BlockImpl) c; + for (BlockImpl pred : new HashSet<>(cur.getPredecessors())) { + if (!worklist.contains(pred)) { + cur.removePredecessor(pred); + } + } + } + + // remove empty blocks + for (Block cur : worklist) { + if (dontVisit.contains(cur)) { + continue; + } + + if (cur.getType() == BlockType.REGULAR_BLOCK) { + RegularBlockImpl b = (RegularBlockImpl) cur; + if (b.isEmpty()) { + Set<RegularBlockImpl> empty = new HashSet<>(); + Set<PredecessorHolder> predecessors = new HashSet<>(); + BlockImpl succ = computeNeighborhoodOfEmptyBlock(b, + empty, predecessors); + for (RegularBlockImpl e : empty) { + succ.removePredecessor(e); + dontVisit.add(e); + } + for (PredecessorHolder p : predecessors) { + BlockImpl block = p.getBlock(); + dontVisit.add(block); + succ.removePredecessor(block); + p.setSuccessor(succ); + } + } + } + } + + // remove useless conditional blocks + worklist = cfg.getAllBlocks(); + for (Block c : worklist) { + BlockImpl cur = (BlockImpl) c; + + if (cur.getType() == BlockType.CONDITIONAL_BLOCK) { + ConditionalBlockImpl cb = (ConditionalBlockImpl) cur; + assert cb.getPredecessors().size() == 1; + if (cb.getThenSuccessor() == cb.getElseSuccessor()) { + BlockImpl pred = cb.getPredecessors().iterator().next(); + PredecessorHolder predecessorHolder = getPredecessorHolder( + pred, cb); + BlockImpl succ = (BlockImpl) cb.getThenSuccessor(); + succ.removePredecessor(cb); + predecessorHolder.setSuccessor(succ); + } + } + } + + // merge consecutive basic blocks if possible + worklist = cfg.getAllBlocks(); + for (Block cur : worklist) { + if (cur.getType() == BlockType.REGULAR_BLOCK) { + RegularBlockImpl b = (RegularBlockImpl) cur; + Block succ = b.getRegularSuccessor(); + if (succ.getType() == BlockType.REGULAR_BLOCK) { + RegularBlockImpl rs = (RegularBlockImpl) succ; + if (rs.getPredecessors().size() == 1) { + b.setSuccessor(rs.getRegularSuccessor()); + b.addNodes(rs.getContents()); + rs.getRegularSuccessor().removePredecessor(rs); + } + } + } + } + + return cfg; + } + + /** + * Compute the set of empty regular basic blocks {@code empty}, starting + * at {@code start} and going both forward and backwards. Furthermore, + * compute the predecessors of these empty blocks ({@code predecessors} + * ), and their single successor (return value). + * + * @param start + * The starting point of the search (an empty, regular basic + * block). + * @param empty + * An empty set to be filled by this method with all empty + * basic blocks found (including {@code start}). + * @param predecessors + * An empty set to be filled by this method with all + * predecessors. + * @return the single successor of the set of the empty basic blocks + */ + protected static BlockImpl computeNeighborhoodOfEmptyBlock( + RegularBlockImpl start, Set<RegularBlockImpl> empty, + Set<PredecessorHolder> predecessors) { + + // get empty neighborhood that come before 'start' + computeNeighborhoodOfEmptyBlockBackwards(start, empty, predecessors); + + // go forward + BlockImpl succ = (BlockImpl) start.getSuccessor(); + while (succ.getType() == BlockType.REGULAR_BLOCK) { + RegularBlockImpl cur = (RegularBlockImpl) succ; + if (cur.isEmpty()) { + computeNeighborhoodOfEmptyBlockBackwards(cur, empty, + predecessors); + assert empty.contains(cur) : "cur ought to be in empty"; + succ = (BlockImpl) cur.getSuccessor(); + if (succ == cur) { + // An infinite loop, making exit block unreachable + break; + } + } else { + break; + } + } + return succ; + } + + /** + * Compute the set of empty regular basic blocks {@code empty}, starting + * at {@code start} and looking only backwards in the control flow + * graph. Furthermore, compute the predecessors of these empty blocks ( + * {@code predecessors}). + * + * @param start + * The starting point of the search (an empty, regular basic + * block). + * @param empty + * A set to be filled by this method with all empty basic + * blocks found (including {@code start}). + * @param predecessors + * A set to be filled by this method with all predecessors. + */ + protected static void computeNeighborhoodOfEmptyBlockBackwards( + RegularBlockImpl start, Set<RegularBlockImpl> empty, + Set<PredecessorHolder> predecessors) { + + RegularBlockImpl cur = start; + empty.add(cur); + for (final BlockImpl pred : cur.getPredecessors()) { + switch (pred.getType()) { + case SPECIAL_BLOCK: + // add pred correctly to predecessor list + predecessors.add(getPredecessorHolder(pred, cur)); + break; + case CONDITIONAL_BLOCK: + // add pred correctly to predecessor list + predecessors.add(getPredecessorHolder(pred, cur)); + break; + case EXCEPTION_BLOCK: + // add pred correctly to predecessor list + predecessors.add(getPredecessorHolder(pred, cur)); + break; + case REGULAR_BLOCK: + RegularBlockImpl r = (RegularBlockImpl) pred; + if (r.isEmpty()) { + // recursively look backwards + if (!empty.contains(r)) { + computeNeighborhoodOfEmptyBlockBackwards(r, empty, + predecessors); + } + } else { + // add pred correctly to predecessor list + predecessors.add(getPredecessorHolder(pred, cur)); + } + break; + } + } + } + + /** + * Return a predecessor holder that can be used to set the successor of + * {@code pred} in the place where previously the edge pointed to + * {@code cur}. Additionally, the predecessor holder also takes care of + * unlinking (i.e., removing the {@code pred} from {@code cur's} + * predecessors). + */ + protected static PredecessorHolder getPredecessorHolder( + final BlockImpl pred, final BlockImpl cur) { + switch (pred.getType()) { + case SPECIAL_BLOCK: + SingleSuccessorBlockImpl s = (SingleSuccessorBlockImpl) pred; + return singleSuccessorHolder(s, cur); + case CONDITIONAL_BLOCK: + // add pred correctly to predecessor list + final ConditionalBlockImpl c = (ConditionalBlockImpl) pred; + if (c.getThenSuccessor() == cur) { + return new PredecessorHolder() { + @Override + public void setSuccessor(BlockImpl b) { + c.setThenSuccessor(b); + cur.removePredecessor(pred); + } + + @Override + public BlockImpl getBlock() { + return c; + } + }; + } else { + assert c.getElseSuccessor() == cur; + return new PredecessorHolder() { + @Override + public void setSuccessor(BlockImpl b) { + c.setElseSuccessor(b); + cur.removePredecessor(pred); + } + + @Override + public BlockImpl getBlock() { + return c; + } + }; + } + case EXCEPTION_BLOCK: + // add pred correctly to predecessor list + final ExceptionBlockImpl e = (ExceptionBlockImpl) pred; + if (e.getSuccessor() == cur) { + return singleSuccessorHolder(e, cur); + } else { + Set<Entry<TypeMirror, Set<Block>>> entrySet = e + .getExceptionalSuccessors().entrySet(); + for (final Entry<TypeMirror, Set<Block>> entry : entrySet) { + if (entry.getValue().contains(cur)) { + return new PredecessorHolder() { + @Override + public void setSuccessor(BlockImpl b) { + e.addExceptionalSuccessor(b, entry.getKey()); + cur.removePredecessor(pred); + } + + @Override + public BlockImpl getBlock() { + return e; + } + }; + } + } + } + assert false; + break; + case REGULAR_BLOCK: + RegularBlockImpl r = (RegularBlockImpl) pred; + return singleSuccessorHolder(r, cur); + } + return null; + } + + /** + * @return a {@link PredecessorHolder} that sets the successor of a + * single successor block {@code s}. + */ + protected static PredecessorHolder singleSuccessorHolder( + final SingleSuccessorBlockImpl s, final BlockImpl old) { + return new PredecessorHolder() { + @Override + public void setSuccessor(BlockImpl b) { + s.setSuccessor(b); + old.removePredecessor(s); + } + + @Override + public BlockImpl getBlock() { + return s; + } + }; + } + } + + /* --------------------------------------------------------- */ + /* Phase Two */ + /* --------------------------------------------------------- */ + + /** Tuple class with up to three members. */ + protected static class Tuple<A, B, C> { + public A a; + public B b; + public C c; + + public Tuple(A a, B b) { + this.a = a; + this.b = b; + } + + public Tuple(A a, B b, C c) { + this.a = a; + this.b = b; + this.c = c; + } + } + + /** + * Class that performs phase two of the translation process. + */ + public class CFGTranslationPhaseTwo { + + public CFGTranslationPhaseTwo() { + } + + /** + * Perform phase two of the translation. + * + * @param in + * The result of phase one. + * @return a control flow graph that might still contain degenerate + * basic block (such as empty regular basic blocks or + * conditional blocks with the same block as 'then' and 'else' + * sucessor) + */ + public ControlFlowGraph process(PhaseOneResult in) { + + Map<Label, Integer> bindings = in.bindings; + ArrayList<ExtendedNode> nodeList = in.nodeList; + Set<Integer> leaders = in.leaders; + + assert in.nodeList.size() > 0; + + // exit blocks + SpecialBlockImpl regularExitBlock = new SpecialBlockImpl( + SpecialBlockType.EXIT); + SpecialBlockImpl exceptionalExitBlock = new SpecialBlockImpl( + SpecialBlockType.EXCEPTIONAL_EXIT); + + // record missing edges that will be added later + Set<Tuple<? extends SingleSuccessorBlockImpl, Integer, ?>> missingEdges = new MostlySingleton<>(); + + // missing exceptional edges + Set<Tuple<ExceptionBlockImpl, Integer, TypeMirror>> missingExceptionalEdges = new HashSet<>(); + + // create start block + SpecialBlockImpl startBlock = new SpecialBlockImpl( + SpecialBlockType.ENTRY); + missingEdges.add(new Tuple<>(startBlock, 0)); + + // loop through all 'leaders' (while dynamically detecting the + // leaders) + RegularBlockImpl block = new RegularBlockImpl(); + int i = 0; + for (ExtendedNode node : nodeList) { + switch (node.getType()) { + case NODE: + if (leaders.contains(i)) { + RegularBlockImpl b = new RegularBlockImpl(); + block.setSuccessor(b); + block = b; + } + block.addNode(node.getNode()); + node.setBlock(block); + + // does this node end the execution (modeled as an edge to + // the exceptional exit block) + boolean terminatesExecution = node.getTerminatesExecution(); + if (terminatesExecution) { + block.setSuccessor(exceptionalExitBlock); + block = new RegularBlockImpl(); + } + break; + case CONDITIONAL_JUMP: { + ConditionalJump cj = (ConditionalJump) node; + // Exception nodes may fall through to conditional jumps, + // so we set the block which is required for the insertion + // of missing edges. + node.setBlock(block); + assert block != null; + final ConditionalBlockImpl cb = new ConditionalBlockImpl(); + if (cj.getTrueFlowRule() != null) { + cb.setThenFlowRule(cj.getTrueFlowRule()); + } + if (cj.getFalseFlowRule() != null) { + cb.setElseFlowRule(cj.getFalseFlowRule()); + } + block.setSuccessor(cb); + block = new RegularBlockImpl(); + // use two anonymous SingleSuccessorBlockImpl that set the + // 'then' and 'else' successor of the conditional block + final Label thenLabel = cj.getThenLabel(); + final Label elseLabel = cj.getElseLabel(); + missingEdges.add(new Tuple<>( + new SingleSuccessorBlockImpl() { + @Override + public void setSuccessor(BlockImpl successor) { + cb.setThenSuccessor(successor); + } + }, bindings.get(thenLabel))); + missingEdges.add(new Tuple<>( + new SingleSuccessorBlockImpl() { + @Override + public void setSuccessor(BlockImpl successor) { + cb.setElseSuccessor(successor); + } + }, bindings.get(elseLabel))); + break; + } + case UNCONDITIONAL_JUMP: + if (leaders.contains(i)) { + RegularBlockImpl b = new RegularBlockImpl(); + block.setSuccessor(b); + block = b; + } + node.setBlock(block); + if (node.getLabel() == regularExitLabel) { + block.setSuccessor(regularExitBlock); + } else if (node.getLabel() == exceptionalExitLabel) { + block.setSuccessor(exceptionalExitBlock); + } else { + missingEdges.add(new Tuple<>(block, bindings.get(node + .getLabel()))); + } + block = new RegularBlockImpl(); + break; + case EXCEPTION_NODE: + NodeWithExceptionsHolder en = (NodeWithExceptionsHolder) node; + // create new exception block and link with previous block + ExceptionBlockImpl e = new ExceptionBlockImpl(); + Node nn = en.getNode(); + e.setNode(nn); + node.setBlock(e); + block.setSuccessor(e); + block = new RegularBlockImpl(); + + // ensure linking between e and next block (normal edge) + // Note: do not link to the next block for throw statements + // (these throw exceptions for sure) + if (!node.getTerminatesExecution()) { + missingEdges.add(new Tuple<>(e, i + 1)); + } + + // exceptional edges + for (Entry<TypeMirror, Set<Label>> entry : en.getExceptions() + .entrySet()) { + TypeMirror cause = entry.getKey(); + for (Label label : entry.getValue()) { + Integer target = bindings.get(label); + missingExceptionalEdges + .add(new Tuple<ExceptionBlockImpl, Integer, TypeMirror>( + e, target, cause)); + } + } + break; + } + i++; + } + + // add missing edges + for (Tuple<? extends SingleSuccessorBlockImpl, Integer, ?> p : missingEdges) { + Integer index = p.b; + ExtendedNode extendedNode = nodeList.get(index); + BlockImpl target = extendedNode.getBlock(); + SingleSuccessorBlockImpl source = p.a; + source.setSuccessor(target); + } + + // add missing exceptional edges + for (Tuple<ExceptionBlockImpl, Integer, ?> p : missingExceptionalEdges) { + Integer index = p.b; + TypeMirror cause = (TypeMirror) p.c; + ExceptionBlockImpl source = p.a; + if (index == null) { + // edge to exceptional exit + source.addExceptionalSuccessor(exceptionalExitBlock, cause); + } else { + // edge to specific target + ExtendedNode extendedNode = nodeList.get(index); + BlockImpl target = extendedNode.getBlock(); + source.addExceptionalSuccessor(target, cause); + } + } + + return new ControlFlowGraph(startBlock, regularExitBlock, exceptionalExitBlock, in.underlyingAST, + in.treeLookupMap, in.convertedTreeLookupMap, in.returnNodes); + } + } + + /* --------------------------------------------------------- */ + /* Phase One */ + /* --------------------------------------------------------- */ + + /** + * A wrapper object to pass around the result of phase one. For a + * documentation of the fields see {@link CFGTranslationPhaseOne}. + */ + protected static class PhaseOneResult { + + private final IdentityHashMap<Tree, Node> treeLookupMap; + private final IdentityHashMap<Tree, Node> convertedTreeLookupMap; + private final UnderlyingAST underlyingAST; + private final Map<Label, Integer> bindings; + private final ArrayList<ExtendedNode> nodeList; + private final Set<Integer> leaders; + private final List<ReturnNode> returnNodes; + + public PhaseOneResult(UnderlyingAST underlyingAST, + IdentityHashMap<Tree, Node> treeLookupMap, + IdentityHashMap<Tree, Node> convertedTreeLookupMap, + ArrayList<ExtendedNode> nodeList, Map<Label, Integer> bindings, + Set<Integer> leaders, List<ReturnNode> returnNodes) { + this.underlyingAST = underlyingAST; + this.treeLookupMap = treeLookupMap; + this.convertedTreeLookupMap = convertedTreeLookupMap; + this.nodeList = nodeList; + this.bindings = bindings; + this.leaders = leaders; + this.returnNodes = returnNodes; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (ExtendedNode n : nodeList) { + sb.append(nodeToString(n)); + sb.append("\n"); + } + return sb.toString(); + } + + protected String nodeToString(ExtendedNode n) { + if (n.getType() == ExtendedNodeType.CONDITIONAL_JUMP) { + ConditionalJump t = (ConditionalJump) n; + return "TwoTargetConditionalJump(" + + resolveLabel(t.getThenLabel()) + "," + + resolveLabel(t.getElseLabel()) + ")"; + } else if (n.getType() == ExtendedNodeType.UNCONDITIONAL_JUMP) { + return "UnconditionalJump(" + resolveLabel(n.getLabel()) + ")"; + } else { + return n.toString(); + } + } + + private String resolveLabel(Label label) { + Integer index = bindings.get(label); + if (index == null) { + return "null"; + } + return nodeToString(nodeList.get(index)); + } + + } + + /** + * Class that performs phase one of the translation process. It generates + * the following information: + * <ul> + * <li>A sequence of extended nodes.</li> + * <li>A set of bindings from {@link Label}s to positions in the node + * sequence.</li> + * <li>A set of leader nodes that give rise to basic blocks in phase two.</li> + * <li>A lookup map that gives the mapping from AST tree nodes to + * {@link Node}s.</li> + * </ul> + * + * <p> + * + * The return type of this scanner is {@link Node}. For expressions, the + * corresponding node is returned to allow linking between different nodes. + * + * However, for statements there is usually no single {@link Node} that is + * created, and thus no node is returned (rather, null is returned). + * + * <p> + * + * Every {@code visit*} method is assumed to add at least one extended node + * to the list of nodes (which might only be a jump). + * + */ + public class CFGTranslationPhaseOne extends TreePathScanner<Node, Void> { + + public CFGTranslationPhaseOne() { + } + + /** + * Annotation processing environment and its associated type and tree + * utilities. + */ + protected ProcessingEnvironment env; + protected Elements elements; + protected Types types; + protected Trees trees; + protected TreeBuilder treeBuilder; + protected AnnotationProvider annotationProvider; + + /** + * Current {@link Label} to which a break statement with no label should + * jump, or null if there is no valid destination. + */ + protected /*@Nullable*/ Label breakTargetL; + + /** + * Map from AST label Names to CFG {@link Label}s for breaks. Each + * labeled statement creates two CFG {@link Label}s, one for break and + * one for continue. + */ + protected Map<Name, Label> breakLabels; + + /** + * Current {@link Label} to which a continue statement with no label + * should jump, or null if there is no valid destination. + */ + protected /*@Nullable*/ Label continueTargetL; + + /** + * Map from AST label Names to CFG {@link Label}s for continues. Each + * labeled statement creates two CFG {@link Label}s, one for break and + * one for continue. + */ + protected Map<Name, Label> continueLabels; + + /** + * Maps from AST {@link Tree}s to {@link Node}s. Every Tree that produces + * a value will have at least one corresponding Node. Trees + * that undergo conversions, such as boxing or unboxing, can map to two + * distinct Nodes. The Node for the pre-conversion value is stored + * in the treeLookupMap, while the Node for the post-conversion value + * is stored in the convertedTreeLookupMap. + */ + protected IdentityHashMap<Tree, Node> treeLookupMap; + + /** Map from AST {@link Tree}s to post-conversion {@link Node}s. */ + protected IdentityHashMap<Tree, Node> convertedTreeLookupMap; + + /** The list of extended nodes. */ + protected ArrayList<ExtendedNode> nodeList; + + /** + * The bindings of labels to positions (i.e., indices) in the + * {@code nodeList}. + */ + protected Map<Label, Integer> bindings; + + /** The set of leaders (represented as indices into {@code nodeList}). */ + protected Set<Integer> leaders; + + /** + * All return nodes (if any) encountered. Only includes return + * statements that actually return something + */ + private List<ReturnNode> returnNodes; + + /** + * Nested scopes of try-catch blocks in force at the current + * program point. + */ + private TryStack tryStack; + + /** + * Performs the actual work of phase one. + * + * @param bodyPath + * path to the body of the underlying AST's method + * @param env + * annotation processing environment containing type + * utilities + * @param underlyingAST + * the AST for which the CFG is to be built + * @param exceptionalExitLabel + * the label for exceptional exits from the CFG + * @param treeBuilder + * builder for new AST nodes + * @param annotationProvider + * extracts annotations from AST nodes + * @return the result of phase one + */ + public PhaseOneResult process( + TreePath bodyPath, ProcessingEnvironment env, + UnderlyingAST underlyingAST, Label exceptionalExitLabel, + TreeBuilder treeBuilder, AnnotationProvider annotationProvider) { + this.env = env; + this.tryStack = new TryStack(exceptionalExitLabel); + this.treeBuilder = treeBuilder; + this.annotationProvider = annotationProvider; + elements = env.getElementUtils(); + types = env.getTypeUtils(); + + // initialize lists and maps + treeLookupMap = new IdentityHashMap<>(); + convertedTreeLookupMap = new IdentityHashMap<>(); + nodeList = new ArrayList<>(); + bindings = new HashMap<>(); + leaders = new HashSet<>(); + breakLabels = new HashMap<>(); + continueLabels = new HashMap<>(); + returnNodes = new ArrayList<>(); + + // traverse AST of the method body + scan(bodyPath, null); + + // add marker to indicate that the next block will be the exit block + // Note: if there is a return statement earlier in the method (which + // is always the case for non-void methods), then this is not + // strictly necessary. However, it is also not a problem, as it will + // just generate a degenerated control graph case that will be + // removed in a later phase. + nodeList.add(new UnconditionalJump(regularExitLabel)); + + return new PhaseOneResult(underlyingAST, treeLookupMap, + convertedTreeLookupMap, nodeList, + bindings, leaders, returnNodes); + } + + public PhaseOneResult process( + CompilationUnitTree root, ProcessingEnvironment env, + UnderlyingAST underlyingAST, Label exceptionalExitLabel, + TreeBuilder treeBuilder, AnnotationProvider annotationProvider) { + trees = Trees.instance(env); + TreePath bodyPath = trees.getPath(root, underlyingAST.getCode()); + return process(bodyPath, env, underlyingAST, exceptionalExitLabel, treeBuilder, annotationProvider); + } + + /** + * Perform any actions required when CFG translation creates a + * new Tree that is not part of the original AST. + * + * @param tree the newly created Tree + */ + public void handleArtificialTree(Tree tree) {} + + /* --------------------------------------------------------- */ + /* Nodes and Labels Management */ + /* --------------------------------------------------------- */ + + /** + * Add a node to the lookup map if it not already present. + * + * @param node + * The node to add to the lookup map. + */ + protected void addToLookupMap(Node node) { + Tree tree = node.getTree(); + if (tree == null) { + return; + } + if (!treeLookupMap.containsKey(tree)) { + treeLookupMap.put(tree, node); + } + + Tree enclosingParens = parenMapping.get(tree); + while (enclosingParens != null) { + treeLookupMap.put(enclosingParens, node); + enclosingParens = parenMapping.get(enclosingParens); + } + } + + /** + * Add a node in the post-conversion lookup map. The node + * should refer to a Tree and that Tree should already be in + * the pre-conversion lookup map. This method is used to + * update the Tree-Node mapping with conversion nodes. + * + * @param node + * The node to add to the lookup map. + */ + protected void addToConvertedLookupMap(Node node) { + Tree tree = node.getTree(); + addToConvertedLookupMap(tree, node); + } + + /** + * Add a node in the post-conversion lookup map. The tree + * argument should already be in the pre-conversion lookup + * map. This method is used to update the Tree-Node mapping + * with conversion nodes. + * + * @param tree + * The tree used as a key in the map. + * @param node + * The node to add to the lookup map. + */ + protected void addToConvertedLookupMap(Tree tree, Node node) { + assert tree != null; + assert treeLookupMap.containsKey(tree); + convertedTreeLookupMap.put(tree, node); + } + + /** + * Extend the list of extended nodes with a node. + * + * @param node + * The node to add. + * @return the same node (for convenience) + */ + protected <T extends Node> T extendWithNode(T node) { + addToLookupMap(node); + extendWithExtendedNode(new NodeHolder(node)); + return node; + } + + /** + * Extend the list of extended nodes with a node, where + * {@code node} might throw the exception {@code cause}. + * + * @param node + * The node to add. + * @param cause + * An exception that the node might throw. + * @return the node holder + */ + protected NodeWithExceptionsHolder extendWithNodeWithException(Node node, TypeMirror cause) { + addToLookupMap(node); + return extendWithNodeWithExceptions(node, Collections.singleton(cause)); + } + + /** + * Extend the list of extended nodes with a node, where + * {@code node} might throw any of the exception in + * {@code causes}. + * + * @param node + * The node to add. + * @param causes + * Set of exceptions that the node might throw. + * @return the node holder + */ + protected NodeWithExceptionsHolder extendWithNodeWithExceptions(Node node, + Set<TypeMirror> causes) { + addToLookupMap(node); + Map<TypeMirror, Set<Label>> exceptions = new HashMap<>(); + for (TypeMirror cause : causes) { + exceptions.put(cause, tryStack.possibleLabels(cause)); + } + NodeWithExceptionsHolder exNode = new NodeWithExceptionsHolder( + node, exceptions); + extendWithExtendedNode(exNode); + return exNode; + } + + /** + * Insert {@code node} after {@code pred} in + * the list of extended nodes, or append to the list if + * {@code pred} is not present. + * + * @param node + * The node to add. + * @param pred + * The desired predecessor of node. + * @return the node holder + */ + protected <T extends Node> T insertNodeAfter(T node, Node pred) { + addToLookupMap(node); + insertExtendedNodeAfter(new NodeHolder(node), pred); + return node; + } + + /** + * Insert a {@code node} that might throw the exception + * {@code cause} after {@code pred} in the list of + * extended nodes, or append to the list if {@code pred} + * is not present. + * + * @param node + * The node to add. + * @param causes + * Set of exceptions that the node might throw. + * @param pred + * The desired predecessor of node. + * @return the node holder + */ + protected NodeWithExceptionsHolder insertNodeWithExceptionsAfter(Node node, + Set<TypeMirror> causes, Node pred) { + addToLookupMap(node); + Map<TypeMirror, Set<Label>> exceptions = new HashMap<>(); + for (TypeMirror cause : causes) { + exceptions.put(cause, tryStack.possibleLabels(cause)); + } + NodeWithExceptionsHolder exNode = new NodeWithExceptionsHolder( + node, exceptions); + insertExtendedNodeAfter(exNode, pred); + return exNode; + } + + /** + * Extend the list of extended nodes with an extended node. + * + * @param n + * The extended node. + */ + protected void extendWithExtendedNode(ExtendedNode n) { + nodeList.add(n); + } + + /** + * Insert {@code n} after the node {@code pred} in the + * list of extended nodes, or append {@code n} if {@code pred} + * is not present. + * + * @param n + * The extended node. + * @param pred + * The desired predecessor. + */ + protected void insertExtendedNodeAfter(ExtendedNode n, Node pred) { + int index = -1; + for (int i = 0; i < nodeList.size(); i++) { + ExtendedNode inList = nodeList.get(i); + if (inList instanceof NodeHolder || + inList instanceof NodeWithExceptionsHolder) { + if (inList.getNode() == pred) { + index = i; + break; + } + } + } + if (index != -1) { + nodeList.add(index + 1, n); + // update bindings + for (Entry<Label, Integer> e : bindings.entrySet()) { + if (e.getValue() >= index+1) { + bindings.put(e.getKey(), e.getValue() + 1); + } + } + // update leaders + Set<Integer> newLeaders = new HashSet<>(); + for (Integer l : leaders) { + if (l >= index+1) { + newLeaders.add(l+1); + } else { + newLeaders.add(l); + } + } + leaders = newLeaders; + } else { + nodeList.add(n); + } + } + + /** + * Add the label {@code l} to the extended node that will be placed next + * in the sequence. + */ + protected void addLabelForNextNode(Label l) { + leaders.add(nodeList.size()); + bindings.put(l, nodeList.size()); + } + + /* --------------------------------------------------------- */ + /* Utility Methods */ + /* --------------------------------------------------------- */ + + protected long uid = 0; + protected String uniqueName(String prefix) { + return prefix + "#num" + uid++; + } + + /** + * If the input node is an unboxed primitive type, insert a call to the + * appropriate valueOf method, otherwise leave it alone. + * + * @param node + * in input node + * @return a Node representing the boxed version of the input, which may + * simply be the input node + */ + protected Node box(Node node) { + // For boxing conversion, see JLS 5.1.7 + if (TypesUtils.isPrimitive(node.getType())) { + PrimitiveType primitive = types.getPrimitiveType(node.getType() + .getKind()); + TypeMirror boxedType = types.getDeclaredType(types + .boxedClass(primitive)); + + TypeElement boxedElement = (TypeElement)((DeclaredType)boxedType).asElement(); + IdentifierTree classTree = treeBuilder.buildClassUse(boxedElement); + handleArtificialTree(classTree); + ClassNameNode className = new ClassNameNode(classTree); + className.setInSource(false); + insertNodeAfter(className, node); + + MemberSelectTree valueOfSelect = treeBuilder.buildValueOfMethodAccess(classTree); + handleArtificialTree(valueOfSelect); + MethodAccessNode valueOfAccess = new MethodAccessNode(valueOfSelect, className); + valueOfAccess.setInSource(false); + insertNodeAfter(valueOfAccess, className); + + MethodInvocationTree valueOfCall = + treeBuilder.buildMethodInvocation(valueOfSelect, (ExpressionTree)node.getTree()); + handleArtificialTree(valueOfCall); + Node boxed = new MethodInvocationNode(valueOfCall, valueOfAccess, + Collections.singletonList(node), + getCurrentPath()); + boxed.setInSource(false); + // Add Throwable to account for unchecked exceptions + TypeElement throwableElement = elements + .getTypeElement("java.lang.Throwable"); + addToConvertedLookupMap(node.getTree(), boxed); + insertNodeWithExceptionsAfter(boxed, + Collections.singleton(throwableElement.asType()), valueOfAccess); + return boxed; + } else { + return node; + } + } + + /** + * If the input node is a boxed type, unbox it, otherwise leave it + * alone. + * + * @param node + * in input node + * @return a Node representing the unboxed version of the input, which + * may simply be the input node + */ + protected Node unbox(Node node) { + if (TypesUtils.isBoxedPrimitive(node.getType())) { + + MemberSelectTree primValueSelect = + treeBuilder.buildPrimValueMethodAccess(node.getTree()); + handleArtificialTree(primValueSelect); + MethodAccessNode primValueAccess = new MethodAccessNode(primValueSelect, node); + primValueAccess.setInSource(false); + // Method access may throw NullPointerException + TypeElement npeElement = elements + .getTypeElement("java.lang.NullPointerException"); + insertNodeWithExceptionsAfter(primValueAccess, + Collections.singleton(npeElement.asType()), node); + + MethodInvocationTree primValueCall = + treeBuilder.buildMethodInvocation(primValueSelect); + handleArtificialTree(primValueCall); + Node unboxed = new MethodInvocationNode(primValueCall, primValueAccess, + Collections.<Node>emptyList(), + getCurrentPath()); + unboxed.setInSource(false); + + // Add Throwable to account for unchecked exceptions + TypeElement throwableElement = elements + .getTypeElement("java.lang.Throwable"); + addToConvertedLookupMap(node.getTree(), unboxed); + insertNodeWithExceptionsAfter(unboxed, + Collections.singleton(throwableElement.asType()), primValueAccess); + return unboxed; + } else { + return node; + } + } + + private TreeInfo getTreeInfo(Tree tree) { + final TypeMirror type = InternalUtils.typeOf(tree); + final boolean boxed = TypesUtils.isBoxedPrimitive(type); + final TypeMirror unboxedType = boxed ? types.unboxedType(type) : type; + + final boolean bool = TypesUtils.isBooleanType(type); + final boolean numeric = TypesUtils.isNumeric(unboxedType); + + return new TreeInfo() { + @Override + public boolean isNumeric() { + return numeric; + } + + @Override + public boolean isBoxed() { + return boxed; + } + + @Override + public boolean isBoolean() { + return bool; + } + + @Override + public TypeMirror unboxedType() { + return unboxedType; + } + }; + } + + /** + * @return the unboxed tree if necessary, as described in JLS 5.1.8 + */ + private Node unboxAsNeeded(Node node, boolean boxed) { + return boxed ? unbox(node) : node; + } + + /** + * Convert the input node to String type, if it isn't already. + * + * @param node + * an input node + * @return a Node with the value promoted to String, which may be the + * input node + */ + protected Node stringConversion(Node node) { + // For string conversion, see JLS 5.1.11 + TypeElement stringElement = + elements.getTypeElement("java.lang.String"); + if (!TypesUtils.isString(node.getType())) { + Node converted = new StringConversionNode(node.getTree(), node, + stringElement.asType()); + addToConvertedLookupMap(converted); + insertNodeAfter(converted, node); + return converted; + } else { + return node; + } + } + + /** + * Perform unary numeric promotion on the input node. + * + * @param node + * a node producing a value of numeric primitive or boxed + * type + * @return a Node with the value promoted to the int, long float or + * double, which may be the input node + */ + protected Node unaryNumericPromotion(Node node) { + // For unary numeric promotion, see JLS 5.6.1 + node = unbox(node); + + switch (node.getType().getKind()) { + case BYTE: + case CHAR: + case SHORT: { + TypeMirror intType = types.getPrimitiveType(TypeKind.INT); + Node widened = new WideningConversionNode(node.getTree(), node, intType); + addToConvertedLookupMap(widened); + insertNodeAfter(widened, node); + return widened; + } + default: + // Nothing to do. + break; + } + + return node; + } + + /** + * Returns true if the argument type is a numeric primitive or + * a boxed numeric primitive and false otherwise. + */ + protected boolean isNumericOrBoxed(TypeMirror type) { + if (TypesUtils.isBoxedPrimitive(type)) { + type = types.unboxedType(type); + } + return TypesUtils.isNumeric(type); + } + + /** + * Compute the type to which two numeric types must be promoted + * before performing a binary numeric operation on them. The + * input types must both be numeric and the output type is primitive. + * + * @param left the type of the left operand + * @param right the type of the right operand + * @return a TypeMirror representing the binary numeric promoted type + */ + protected TypeMirror binaryPromotedType(TypeMirror left, TypeMirror right) { + if (TypesUtils.isBoxedPrimitive(left)) { + left = types.unboxedType(left); + } + if (TypesUtils.isBoxedPrimitive(right)) { + right = types.unboxedType(right); + } + TypeKind promotedTypeKind = TypesUtils.widenedNumericType(left, right); + return types.getPrimitiveType(promotedTypeKind); + } + + /** + * Perform binary numeric promotion on the input node to make it match + * the expression type. + * + * @param node + * a node producing a value of numeric primitive or boxed + * type + * @param exprType + * the type to promote the value to + * @return a Node with the value promoted to the exprType, which may be + * the input node + */ + protected Node binaryNumericPromotion(Node node, TypeMirror exprType) { + // For binary numeric promotion, see JLS 5.6.2 + node = unbox(node); + + if (!types.isSameType(node.getType(), exprType)) { + Node widened = new WideningConversionNode(node.getTree(), node, + exprType); + addToConvertedLookupMap(widened); + insertNodeAfter(widened, node); + return widened; + } else { + return node; + } + } + + /** + * Perform widening primitive conversion on the input node to make it + * match the destination type. + * + * @param node + * a node producing a value of numeric primitive type + * @param destType + * the type to widen the value to + * @return a Node with the value widened to the exprType, which may be + * the input node + */ + protected Node widen(Node node, TypeMirror destType) { + // For widening conversion, see JLS 5.1.2 + assert TypesUtils.isPrimitive(node.getType()) + && TypesUtils.isPrimitive(destType) : "widening must be applied to primitive types"; + if (types.isSubtype(node.getType(), destType) + && !types.isSameType(node.getType(), destType)) { + Node widened = new WideningConversionNode(node.getTree(), node, + destType); + addToConvertedLookupMap(widened); + insertNodeAfter(widened, node); + return widened; + } else { + return node; + } + } + + /** + * Perform narrowing conversion on the input node to make it match the + * destination type. + * + * @param node + * a node producing a value of numeric primitive type + * @param destType + * the type to narrow the value to + * @return a Node with the value narrowed to the exprType, which may be + * the input node + */ + protected Node narrow(Node node, TypeMirror destType) { + // For narrowing conversion, see JLS 5.1.3 + assert TypesUtils.isPrimitive(node.getType()) + && TypesUtils.isPrimitive(destType) : "narrowing must be applied to primitive types"; + if (types.isSubtype(destType, node.getType()) + && !types.isSameType(destType, node.getType())) { + Node narrowed = new NarrowingConversionNode(node.getTree(), node, + destType); + addToConvertedLookupMap(narrowed); + insertNodeAfter(narrowed, node); + return narrowed; + } else { + return node; + } + } + + /** + * Perform narrowing conversion and optionally boxing conversion on the + * input node to make it match the destination type. + * + * @param node + * a node producing a value of numeric primitive type + * @param destType + * the type to narrow the value to (possibly boxed) + * @return a Node with the value narrowed and boxed to the destType, + * which may be the input node + */ + protected Node narrowAndBox(Node node, TypeMirror destType) { + if (TypesUtils.isBoxedPrimitive(destType)) { + return box(narrow(node, types.unboxedType(destType))); + } else { + return narrow(node, destType); + } + } + + + /** + * Return whether a conversion from the type of the node to varType + * requires narrowing. + * + * @param varType the type of a variable (or general LHS) to be converted to + * @param node a node whose value is being converted + * @return whether this conversion requires narrowing to succeed + */ + protected boolean conversionRequiresNarrowing(TypeMirror varType, Node node) { + // Narrowing is restricted to cases where the left hand side + // is byte, char, short or Byte, Char, Short and the right + // hand side is a constant. + TypeMirror unboxedVarType = TypesUtils.isBoxedPrimitive(varType) ? types + .unboxedType(varType) : varType; + TypeKind unboxedVarKind = unboxedVarType.getKind(); + boolean isLeftNarrowableTo = unboxedVarKind == TypeKind.BYTE + || unboxedVarKind == TypeKind.SHORT + || unboxedVarKind == TypeKind.CHAR; + boolean isRightConstant = node instanceof ValueLiteralNode; + return isLeftNarrowableTo && isRightConstant; + } + + + /** + * Assignment conversion and method invocation conversion are almost + * identical, except that assignment conversion allows narrowing. We + * factor out the common logic here. + * + * @param node + * a Node producing a value + * @param varType + * the type of a variable + * @param contextAllowsNarrowing + * whether to allow narrowing (for assignment conversion) or + * not (for method invocation conversion) + * @return a Node with the value converted to the type of the variable, + * which may be the input node itself + */ + protected Node commonConvert(Node node, TypeMirror varType, + boolean contextAllowsNarrowing) { + // For assignment conversion, see JLS 5.2 + // For method invocation conversion, see JLS 5.3 + + // Check for identical types or "identity conversion" + TypeMirror nodeType = node.getType(); + boolean isSameType = types.isSameType(nodeType, varType); + if (isSameType) { + return node; + } + + boolean isRightNumeric = TypesUtils.isNumeric(nodeType); + boolean isRightPrimitive = TypesUtils.isPrimitive(nodeType); + boolean isRightBoxed = TypesUtils.isBoxedPrimitive(nodeType); + boolean isRightReference = nodeType instanceof ReferenceType; + boolean isLeftNumeric = TypesUtils.isNumeric(varType); + boolean isLeftPrimitive = TypesUtils.isPrimitive(varType); + // boolean isLeftBoxed = TypesUtils.isBoxedPrimitive(varType); + boolean isLeftReference = varType instanceof ReferenceType; + boolean isSubtype = types.isSubtype(nodeType, varType); + + if (isRightNumeric && isLeftNumeric && isSubtype) { + node = widen(node, varType); + nodeType = node.getType(); + } else if (isRightReference && isLeftReference && isSubtype) { + // widening reference conversion is a no-op, but if it + // applies, then later conversions do not. + } else if (isRightPrimitive && isLeftReference) { + if (contextAllowsNarrowing && conversionRequiresNarrowing(varType, node)) { + node = narrowAndBox(node, varType); + nodeType = node.getType(); + } else { + node = box(node); + nodeType = node.getType(); + } + } else if (isRightBoxed && isLeftPrimitive) { + node = unbox(node); + nodeType = node.getType(); + + if (types.isSubtype(nodeType, varType) + && !types.isSameType(nodeType, varType)) { + node = widen(node, varType); + nodeType = node.getType(); + } + } else if (isRightPrimitive && isLeftPrimitive) { + if (contextAllowsNarrowing && conversionRequiresNarrowing(varType, node)) { + node = narrow(node, varType); + nodeType = node.getType(); + } + } + + // TODO: if checkers need to know about null references of + // a particular type, add logic for them here. + + return node; + } + + /** + * Perform assignment conversion so that it can be assigned to a + * variable of the given type. + * + * @param node + * a Node producing a value + * @param varType + * the type of a variable + * @return a Node with the value converted to the type of the variable, + * which may be the input node itself + */ + protected Node assignConvert(Node node, TypeMirror varType) { + return commonConvert(node, varType, true); + } + + /** + * Perform method invocation conversion so that the node can be passed + * as a formal parameter of the given type. + * + * @param node + * a Node producing a value + * @param formalType + * the type of a formal parameter + * @return a Node with the value converted to the type of the formal, + * which may be the input node itself + */ + protected Node methodInvocationConvert(Node node, TypeMirror formalType) { + return commonConvert(node, formalType, false); + } + + /** + * Given a method element and as list of argument expressions, return a + * list of {@link Node}s representing the arguments converted for a call + * of the method. This method applies to both method invocations and + * constructor calls. + * + * @param method + * an ExecutableElement representing a method to be called + * @param actualExprs + * a List of argument expressions to a call + * @return a List of {@link Node}s representing arguments after + * conversions required by a call to this method. + */ + protected List<Node> convertCallArguments(ExecutableElement method, + List<? extends ExpressionTree> actualExprs) { + // NOTE: It is important to convert one method argument before + // generating CFG nodes for the next argument, since label binding + // expects nodes to be generated in execution order. Therefore, + // this method first determines which conversions need to be applied + // and then iterates over the actual arguments. + List<? extends VariableElement> formals = method.getParameters(); + + ArrayList<Node> convertedNodes = new ArrayList<Node>(); + + int numFormals = formals.size(); + int numActuals = actualExprs.size(); + if (method.isVarArgs()) { + // Create a new array argument if the actuals outnumber + // the formals, or if the last actual is not assignable + // to the last formal. + int lastArgIndex = numFormals - 1; + TypeMirror lastParamType = formals.get(lastArgIndex).asType(); + List<Node> dimensions = new ArrayList<>(); + List<Node> initializers = new ArrayList<>(); + + if (numActuals == numFormals - 1) { + // Apply method invocation conversion to all actual + // arguments, then create and append an empty array + for (int i = 0; i < numActuals; i++) { + Node actualVal = scan(actualExprs.get(i), null); + convertedNodes.add(methodInvocationConvert(actualVal, + formals.get(i).asType())); + } + + Node lastArgument = new ArrayCreationNode(null, + lastParamType, dimensions, initializers); + extendWithNode(lastArgument); + + convertedNodes.add(lastArgument); + } else { + TypeMirror actualType = InternalUtils.typeOf(actualExprs + .get(lastArgIndex)); + if (numActuals == numFormals + && types.isAssignable(actualType, lastParamType)) { + // Normal call with no array creation, apply method + // invocation conversion to all arguments. + for (int i = 0; i < numActuals; i++) { + Node actualVal = scan(actualExprs.get(i), null); + convertedNodes.add(methodInvocationConvert(actualVal, + formals.get(i).asType())); + } + } else { + assert lastParamType instanceof ArrayType : + "variable argument formal must be an array"; + // Apply method invocation conversion to lastArgIndex + // arguments and use the remaining ones to initialize + // an array. + for (int i = 0; i < lastArgIndex; i++) { + Node actualVal = scan(actualExprs.get(i), null); + convertedNodes.add(methodInvocationConvert(actualVal, + formals.get(i).asType())); + } + + TypeMirror elemType = + ((ArrayType)lastParamType).getComponentType(); + for (int i = lastArgIndex; i < numActuals; i++) { + Node actualVal = scan(actualExprs.get(i), null); + initializers.add(assignConvert(actualVal, elemType)); + } + + Node lastArgument = new ArrayCreationNode(null, + lastParamType, dimensions, initializers); + extendWithNode(lastArgument); + convertedNodes.add(lastArgument); + } + } + } else { + for (int i = 0; i < numActuals; i++) { + Node actualVal = scan(actualExprs.get(i), null); + convertedNodes.add(methodInvocationConvert(actualVal, + formals.get(i).asType())); + } + } + + return convertedNodes; + } + + /** + * Convert an operand of a conditional expression to the type of the + * whole expression. + * + * @param node + * a node occurring as the second or third operand of + * a conditional expression + * @param destType + * the type to promote the value to + * @return a Node with the value promoted to the destType, which may be + * the input node + */ + protected Node conditionalExprPromotion(Node node, TypeMirror destType) { + // For rules on converting operands of conditional expressions, + // JLS 15.25 + TypeMirror nodeType = node.getType(); + + // If the operand is already the same type as the whole + // expression, then do nothing. + if (types.isSameType(nodeType, destType)) { + return node; + } + + // If the operand is a primitive and the whole expression is + // boxed, then apply boxing. + if (TypesUtils.isPrimitive(nodeType) && + TypesUtils.isBoxedPrimitive(destType)) { + return box(node); + } + + // If the operand is byte or Byte and the whole expression is + // short, then convert to short. + boolean isBoxedPrimitive = TypesUtils.isBoxedPrimitive(nodeType); + TypeMirror unboxedNodeType = + isBoxedPrimitive ? types.unboxedType(nodeType) : nodeType; + TypeMirror unboxedDestType = + TypesUtils.isBoxedPrimitive(destType) ? + types.unboxedType(destType) : destType; + if (TypesUtils.isNumeric(unboxedNodeType) && + TypesUtils.isNumeric(unboxedDestType)) { + if (unboxedNodeType.getKind() == TypeKind.BYTE && + destType.getKind() == TypeKind.SHORT) { + if (isBoxedPrimitive) { + node = unbox(node); + } + return widen(node, destType); + } + + // If the operand is Byte, Short or Character and the whole expression + // is the unboxed version of it, then apply unboxing. + TypeKind destKind = destType.getKind(); + if (destKind == TypeKind.BYTE || destKind == TypeKind.CHAR || + destKind == TypeKind.SHORT) { + if (isBoxedPrimitive) { + return unbox(node); + } else if (nodeType.getKind() == TypeKind.INT) { + return narrow(node, destType); + } + } + + return binaryNumericPromotion(node, destType); + } + + // For the final case in JLS 15.25, apply boxing but not lub. + if (TypesUtils.isPrimitive(nodeType) && + (destType.getKind() == TypeKind.DECLARED || + destType.getKind() == TypeKind.UNION || + destType.getKind() == TypeKind.INTERSECTION)) { + return box(node); + } + + return node; + } + + /** + * Returns the label {@link Name} of the leaf in the argument path, or + * null if the leaf is not a labeled statement. + */ + protected /*@Nullable*/ Name getLabel(TreePath path) { + if (path.getParentPath() != null) { + Tree parent = path.getParentPath().getLeaf(); + if (parent.getKind() == Tree.Kind.LABELED_STATEMENT) { + return ((LabeledStatementTree) parent).getLabel(); + } + } + return null; + } + + /* --------------------------------------------------------- */ + /* Visitor Methods */ + /* --------------------------------------------------------- */ + + @Override + public Node visitAnnotatedType(AnnotatedTypeTree tree, Void p) { + return scan(tree.getUnderlyingType(), p); + } + + @Override + public Node visitAnnotation(AnnotationTree tree, Void p) { + assert false : "AnnotationTree is unexpected in AST to CFG translation"; + return null; + } + + @Override + public MethodInvocationNode visitMethodInvocation(MethodInvocationTree tree, Void p) { + + // see JLS 15.12.4 + + // First, compute the receiver, if any (15.12.4.1) + // Second, evaluate the actual arguments, left to right and + // possibly some arguments are stored into an array for variable + // arguments calls (15.12.4.2) + // Third, test the receiver, if any, for nullness (15.12.4.4) + // Fourth, convert the arguments to the type of the formal + // parameters (15.12.4.5) + // Fifth, if the method is synchronized, lock the receiving + // object or class (15.12.4.5) + ExecutableElement method = TreeUtils.elementFromUse(tree); + // TODO? Variable wasn't used. + // boolean isBooleanMethod = TypesUtils.isBooleanType(method.getReturnType()); + + ExpressionTree methodSelect = tree.getMethodSelect(); + assert TreeUtils.isMethodAccess(methodSelect) : "Expected a method access, but got: " + methodSelect; + + List<? extends ExpressionTree> actualExprs = tree.getArguments(); + + // Look up method to invoke and possibly throw NullPointerException + Node receiver = getReceiver(methodSelect, + TreeUtils.enclosingClass(getCurrentPath())); + + MethodAccessNode target = new MethodAccessNode(methodSelect, + receiver); + + ExecutableElement element = TreeUtils.elementFromUse(tree); + if (ElementUtils.isStatic(element) || + receiver instanceof ThisLiteralNode) { + // No NullPointerException can be thrown, use normal node + extendWithNode(target); + } else { + TypeElement npeElement = elements + .getTypeElement("java.lang.NullPointerException"); + extendWithNodeWithException(target, npeElement.asType()); + } + + List<Node> arguments = new ArrayList<>(); + + // Don't convert arguments for enum super calls. The AST contains + // no actual arguments, while the method element expects two arguments, + // leading to an exception in convertCallArguments. Since no actual + // arguments are present in the AST that is being checked, it shouldn't + // cause any harm to omit the conversions. + // See also BaseTypeVisitor.visitMethodInvocation and + // QualifierPolymorphism.annotate + if (!TreeUtils.isEnumSuper(tree)) { + arguments = convertCallArguments(method, actualExprs); + } + + // TODO: lock the receiver for synchronized methods + + MethodInvocationNode node = new MethodInvocationNode(tree, target, arguments, getCurrentPath()); + + Set<TypeMirror> thrownSet = new HashSet<>(); + // Add exceptions explicitly mentioned in the throws clause. + List<? extends TypeMirror> thrownTypes = element.getThrownTypes(); + thrownSet.addAll(thrownTypes); + // Add Throwable to account for unchecked exceptions + TypeElement throwableElement = elements + .getTypeElement("java.lang.Throwable"); + thrownSet.add(throwableElement.asType()); + + ExtendedNode extendedNode = extendWithNodeWithExceptions(node, thrownSet); + + /* Check for the TerminatesExecution annotation. */ + Element methodElement = InternalUtils.symbol(tree); + boolean terminatesExecution = annotationProvider.getDeclAnnotation( + methodElement, TerminatesExecution.class) != null; + if (terminatesExecution) { + extendedNode.setTerminatesExecution(true); + } + + return node; + } + + @Override + public Node visitAssert(AssertTree tree, Void p) { + + // see JLS 14.10 + + // If assertions are enabled, then we can just translate the + // assertion. + if (assumeAssertionsEnabled || assumeAssertionsEnabledFor(tree)) { + translateAssertWithAssertionsEnabled(tree); + return null; + } + + // If assertions are disabled, then nothing is executed. + if (assumeAssertionsDisabled) { + return null; + } + + + // Otherwise, we don't know if assertions are enabled, so we use a + // variable "ea" and case-split on it. One branch does execute the + // assertion, while the other assumes assertions are disabled. + VariableTree ea = getAssertionsEnabledVariable(); + + // all necessary labels + Label assertionEnabled = new Label(); + Label assertionDisabled = new Label(); + + extendWithNode(new LocalVariableNode(ea)); + extendWithExtendedNode(new ConditionalJump(assertionEnabled, + assertionDisabled)); + + // 'then' branch (i.e. check the assertion) + addLabelForNextNode(assertionEnabled); + + translateAssertWithAssertionsEnabled(tree); + + // 'else' branch + addLabelForNextNode(assertionDisabled); + + return null; + } + + /** + * Should assertions be assumed to be executed for a given + * {@link AssertTree}? False by default. + */ + protected boolean assumeAssertionsEnabledFor(AssertTree tree) { + return false; + } + + /** + * The {@link VariableTree} that indicates whether assertions are + * enabled or not. + */ + protected VariableTree ea = null; + + /** + * Get a synthetic {@link VariableTree} that indicates whether assertions are + * enabled or not. + */ + protected VariableTree getAssertionsEnabledVariable() { + if (ea == null) { + String name = uniqueName("assertionsEnabled"); + MethodTree enclosingMethod = TreeUtils + .enclosingMethod(getCurrentPath()); + Element owner; + if (enclosingMethod != null) { + owner = TreeUtils.elementFromDeclaration(enclosingMethod); + } else { + ClassTree enclosingClass = TreeUtils.enclosingClass(getCurrentPath()); + owner = TreeUtils.elementFromDeclaration(enclosingClass); + } + ExpressionTree initializer = null; + ea = treeBuilder.buildVariableDecl( + types.getPrimitiveType(TypeKind.BOOLEAN), name, owner, + initializer); + } + return ea; + } + + /** + * Translates an assertion statement to the correct CFG nodes. The + * translation assumes that assertions are enabled. + */ + protected void translateAssertWithAssertionsEnabled(AssertTree tree) { + + // all necessary labels + Label assertEnd = new Label(); + Label elseEntry = new Label(); + + // basic block for the condition + Node condition = unbox(scan(tree.getCondition(), null)); + ConditionalJump cjump = new ConditionalJump(assertEnd, elseEntry); + extendWithExtendedNode(cjump); + + // else branch + Node detail = null; + addLabelForNextNode(elseEntry); + if (tree.getDetail() != null) { + detail = scan(tree.getDetail(), null); + } + TypeElement assertException = elements + .getTypeElement("java.lang.AssertionError"); + AssertionErrorNode assertNode = new AssertionErrorNode(tree, + condition, detail, assertException.asType()); + extendWithNode(assertNode); + NodeWithExceptionsHolder exNode = extendWithNodeWithException( + new ThrowNode(null, assertNode, env.getTypeUtils()), assertException.asType()); + exNode.setTerminatesExecution(true); + + // then branch (nothing happens) + addLabelForNextNode(assertEnd); + } + + @Override + public Node visitAssignment(AssignmentTree tree, Void p) { + + // see JLS 15.26.1 + + AssignmentNode assignmentNode; + ExpressionTree variable = tree.getVariable(); + TypeMirror varType = InternalUtils.typeOf(variable); + + // case 1: field access + if (TreeUtils.isFieldAccess(variable)) { + // visit receiver + Node receiver = getReceiver(variable, + TreeUtils.enclosingClass(getCurrentPath())); + + // visit expression + Node expression = scan(tree.getExpression(), p); + expression = assignConvert(expression, varType); + + // visit field access (throws null-pointer exception) + FieldAccessNode target = new FieldAccessNode(variable, receiver); + target.setLValue(); + + Element element = TreeUtils.elementFromUse(variable); + if (ElementUtils.isStatic(element) || + receiver instanceof ThisLiteralNode) { + // No NullPointerException can be thrown, use normal node + extendWithNode(target); + } else { + TypeElement npeElement = elements + .getTypeElement("java.lang.NullPointerException"); + extendWithNodeWithException(target, npeElement.asType()); + } + + // add assignment node + assignmentNode = new AssignmentNode(tree, + target, expression); + extendWithNode(assignmentNode); + } + + // case 2: other cases + else { + Node target = scan(variable, p); + target.setLValue(); + + assignmentNode = translateAssignment(tree, target, + tree.getExpression()); + } + + return assignmentNode; + } + + /** + * Translate an assignment. + */ + protected AssignmentNode translateAssignment(Tree tree, Node target, + ExpressionTree rhs) { + Node expression = scan(rhs, null); + return translateAssignment(tree, target, expression); + } + + /** + * Translate an assignment where the RHS has already been scanned. + */ + protected AssignmentNode translateAssignment(Tree tree, Node target, + Node expression) { + assert tree instanceof AssignmentTree + || tree instanceof VariableTree; + target.setLValue(); + expression = assignConvert(expression, target.getType()); + AssignmentNode assignmentNode = new AssignmentNode(tree, target, + expression); + extendWithNode(assignmentNode); + return assignmentNode; + } + + /** + * Note 1: Requires {@code tree} to be a field or method access + * tree. + * <p> + * Note 2: Visits the receiver and adds all necessary blocks to the CFG. + * + * @param tree + * the field access tree containing the receiver + * @param classTree + * the ClassTree enclosing the field access + * @return the receiver of the field access + */ + private Node getReceiver(Tree tree, ClassTree classTree) { + assert TreeUtils.isFieldAccess(tree) + || TreeUtils.isMethodAccess(tree); + if (tree.getKind().equals(Tree.Kind.MEMBER_SELECT)) { + MemberSelectTree mtree = (MemberSelectTree) tree; + return scan(mtree.getExpression(), null); + } else { + TypeMirror classType = InternalUtils.typeOf(classTree); + Node node = new ImplicitThisLiteralNode(classType); + extendWithNode(node); + return node; + } + } + + /** + * Map an operation with assignment to the corresponding operation + * without assignment. + * + * @param kind a Tree.Kind representing an operation with assignment + * @return the Tree.Kind for the same operation without assignment + */ + protected Tree.Kind withoutAssignment(Tree.Kind kind) { + switch (kind) { + case DIVIDE_ASSIGNMENT: + return Tree.Kind.DIVIDE; + case MULTIPLY_ASSIGNMENT: + return Tree.Kind.MULTIPLY; + case REMAINDER_ASSIGNMENT: + return Tree.Kind.REMAINDER; + case MINUS_ASSIGNMENT: + return Tree.Kind.MINUS; + case PLUS_ASSIGNMENT: + return Tree.Kind.PLUS; + case LEFT_SHIFT_ASSIGNMENT: + return Tree.Kind.LEFT_SHIFT; + case RIGHT_SHIFT_ASSIGNMENT: + return Tree.Kind.RIGHT_SHIFT; + case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: + return Tree.Kind.UNSIGNED_RIGHT_SHIFT; + case AND_ASSIGNMENT: + return Tree.Kind.AND; + case OR_ASSIGNMENT: + return Tree.Kind.OR; + case XOR_ASSIGNMENT: + return Tree.Kind.XOR; + default: + return Tree.Kind.ERRONEOUS; + } + } + + + @Override + public Node visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { + // According the JLS 15.26.2, E1 op= E2 is equivalent to + // E1 = (T) ((E1) op (E2)), where T is the type of E1, + // except that E1 is evaluated only once. + // + + Tree.Kind kind = tree.getKind(); + switch (kind) { + case DIVIDE_ASSIGNMENT: + case MULTIPLY_ASSIGNMENT: + case REMAINDER_ASSIGNMENT: { + // see JLS 15.17 and 15.26.2 + Node targetLHS = scan(tree.getVariable(), p); + Node value = scan(tree.getExpression(), p); + + TypeMirror exprType = InternalUtils.typeOf(tree); + TypeMirror leftType = InternalUtils.typeOf(tree.getVariable()); + TypeMirror rightType = InternalUtils.typeOf(tree.getExpression()); + TypeMirror promotedType = binaryPromotedType(leftType, rightType); + Node targetRHS = binaryNumericPromotion(targetLHS, promotedType); + value = binaryNumericPromotion(value, promotedType); + + BinaryTree operTree = treeBuilder.buildBinary(promotedType, withoutAssignment(kind), + tree.getVariable(), tree.getExpression()); + handleArtificialTree(operTree); + Node operNode; + if (kind == Tree.Kind.MULTIPLY_ASSIGNMENT) { + operNode = new NumericalMultiplicationNode(operTree, targetRHS, value); + } else if (kind == Tree.Kind.DIVIDE_ASSIGNMENT) { + if (TypesUtils.isIntegral(exprType)) { + operNode = new IntegerDivisionNode(operTree, targetRHS, value); + } else { + operNode = new FloatingDivisionNode(operTree, targetRHS, value); + } + } else { + assert kind == Kind.REMAINDER_ASSIGNMENT; + if (TypesUtils.isIntegral(exprType)) { + operNode = new IntegerRemainderNode(operTree, targetRHS, value); + } else { + operNode = new FloatingRemainderNode(operTree, targetRHS, value); + } + } + extendWithNode(operNode); + + TypeCastTree castTree = treeBuilder.buildTypeCast(leftType, operTree); + handleArtificialTree(castTree); + TypeCastNode castNode = new TypeCastNode(castTree, operNode, leftType); + castNode.setInSource(false); + extendWithNode(castNode); + + AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode); + extendWithNode(assignNode); + return assignNode; + } + + case MINUS_ASSIGNMENT: + case PLUS_ASSIGNMENT: { + // see JLS 15.18 and 15.26.2 + + Node targetLHS = scan(tree.getVariable(), p); + Node value = scan(tree.getExpression(), p); + + TypeMirror leftType = InternalUtils.typeOf(tree.getVariable()); + TypeMirror rightType = InternalUtils.typeOf(tree.getExpression()); + + if (TypesUtils.isString(leftType) || TypesUtils.isString(rightType)) { + assert (kind == Tree.Kind.PLUS_ASSIGNMENT); + Node targetRHS = stringConversion(targetLHS); + value = stringConversion(value); + Node r = new StringConcatenateAssignmentNode(tree, targetRHS, value); + extendWithNode(r); + return r; + } else { + TypeMirror promotedType = binaryPromotedType(leftType, rightType); + Node targetRHS = binaryNumericPromotion(targetLHS, promotedType); + value = binaryNumericPromotion(value, promotedType); + + BinaryTree operTree = treeBuilder.buildBinary(promotedType, withoutAssignment(kind), + tree.getVariable(), tree.getExpression()); + handleArtificialTree(operTree); + Node operNode; + if (kind == Tree.Kind.PLUS_ASSIGNMENT) { + operNode = new NumericalAdditionNode(operTree, targetRHS, value); + } else { + assert kind == Kind.MINUS_ASSIGNMENT; + operNode = new NumericalSubtractionNode(operTree, targetRHS, value); + } + extendWithNode(operNode); + + TypeCastTree castTree = treeBuilder.buildTypeCast(leftType, operTree); + handleArtificialTree(castTree); + TypeCastNode castNode = new TypeCastNode(castTree, operNode, leftType); + castNode.setInSource(false); + extendWithNode(castNode); + + // Map the compound assignment tree to an assignment node, which + // will have the correct type. + AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode); + extendWithNode(assignNode); + return assignNode; + } + } + + case LEFT_SHIFT_ASSIGNMENT: + case RIGHT_SHIFT_ASSIGNMENT: + case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: { + // see JLS 15.19 and 15.26.2 + Node targetLHS = scan(tree.getVariable(), p); + Node value = scan(tree.getExpression(), p); + + TypeMirror leftType = InternalUtils.typeOf(tree.getVariable()); + + Node targetRHS = unaryNumericPromotion(targetLHS); + value = unaryNumericPromotion(value); + + BinaryTree operTree = treeBuilder.buildBinary(leftType, withoutAssignment(kind), + tree.getVariable(), tree.getExpression()); + handleArtificialTree(operTree); + Node operNode; + if (kind == Tree.Kind.LEFT_SHIFT_ASSIGNMENT) { + operNode = new LeftShiftNode(operTree, targetRHS, value); + } else if (kind == Tree.Kind.RIGHT_SHIFT_ASSIGNMENT) { + operNode = new SignedRightShiftNode(operTree, targetRHS, value); + } else { + assert kind == Kind.UNSIGNED_RIGHT_SHIFT_ASSIGNMENT; + operNode = new UnsignedRightShiftNode(operTree, targetRHS, value); + } + extendWithNode(operNode); + + TypeCastTree castTree = treeBuilder.buildTypeCast(leftType, operTree); + handleArtificialTree(castTree); + TypeCastNode castNode = new TypeCastNode(castTree, operNode, leftType); + castNode.setInSource(false); + extendWithNode(castNode); + + AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode); + extendWithNode(assignNode); + return assignNode; + } + + case AND_ASSIGNMENT: + case OR_ASSIGNMENT: + case XOR_ASSIGNMENT: + // see JLS 15.22 + Node targetLHS = scan(tree.getVariable(), p); + Node value = scan(tree.getExpression(), p); + + TypeMirror leftType = InternalUtils.typeOf(tree.getVariable()); + TypeMirror rightType = InternalUtils.typeOf(tree.getExpression()); + + Node targetRHS = null; + if (isNumericOrBoxed(leftType) && isNumericOrBoxed(rightType)) { + TypeMirror promotedType = binaryPromotedType(leftType, rightType); + targetRHS = binaryNumericPromotion(targetLHS, promotedType); + value = binaryNumericPromotion(value, promotedType); + } else if (TypesUtils.isBooleanType(leftType) && + TypesUtils.isBooleanType(rightType)) { + targetRHS = unbox(targetLHS); + value = unbox(value); + } else { + assert false : + "Both argument to logical operation must be numeric or boolean"; + } + + BinaryTree operTree = treeBuilder.buildBinary(leftType, withoutAssignment(kind), + tree.getVariable(), tree.getExpression()); + handleArtificialTree(operTree); + Node operNode; + if (kind == Tree.Kind.AND_ASSIGNMENT) { + operNode = new BitwiseAndNode(operTree, targetRHS, value); + } else if (kind == Tree.Kind.OR_ASSIGNMENT) { + operNode = new BitwiseOrNode(operTree, targetRHS, value); + } else { + assert kind == Kind.XOR_ASSIGNMENT; + operNode = new BitwiseXorNode(operTree, targetRHS, value); + } + extendWithNode(operNode); + + TypeCastTree castTree = treeBuilder.buildTypeCast(leftType, operTree); + handleArtificialTree(castTree); + TypeCastNode castNode = new TypeCastNode(castTree, operNode, leftType); + castNode.setInSource(false); + extendWithNode(castNode); + + AssignmentNode assignNode = new AssignmentNode(tree, targetLHS, castNode); + extendWithNode(assignNode); + return assignNode; + default: + assert false : "unexpected compound assignment type"; + break; + } + assert false : "unexpected compound assignment type"; + return null; + } + + @Override + public Node visitBinary(BinaryTree tree, Void p) { + // Note that for binary operations it is important to perform any required + // promotion on the left operand before generating any Nodes for the right + // operand, because labels must be inserted AFTER ALL preceding Nodes and + // BEFORE ALL following Nodes. + Node r = null; + Tree leftTree = tree.getLeftOperand(); + Tree rightTree = tree.getRightOperand(); + + Tree.Kind kind = tree.getKind(); + switch (kind) { + case DIVIDE: + case MULTIPLY: + case REMAINDER: { + // see JLS 15.17 + + TypeMirror exprType = InternalUtils.typeOf(tree); + TypeMirror leftType = InternalUtils.typeOf(leftTree); + TypeMirror rightType = InternalUtils.typeOf(rightTree); + TypeMirror promotedType = binaryPromotedType(leftType, rightType); + + Node left = binaryNumericPromotion(scan(leftTree, p), promotedType); + Node right = binaryNumericPromotion(scan(rightTree, p), promotedType); + + if (kind == Tree.Kind.MULTIPLY) { + r = new NumericalMultiplicationNode(tree, left, right); + } else if (kind == Tree.Kind.DIVIDE) { + if (TypesUtils.isIntegral(exprType)) { + r = new IntegerDivisionNode(tree, left, right); + } else { + r = new FloatingDivisionNode(tree, left, right); + } + } else { + assert kind == Kind.REMAINDER; + if (TypesUtils.isIntegral(exprType)) { + r = new IntegerRemainderNode(tree, left, right); + } else { + r = new FloatingRemainderNode(tree, left, right); + } + } + break; + } + + case MINUS: + case PLUS: { + // see JLS 15.18 + + // TypeMirror exprType = InternalUtils.typeOf(tree); + TypeMirror leftType = InternalUtils.typeOf(leftTree); + TypeMirror rightType = InternalUtils.typeOf(rightTree); + + if (TypesUtils.isString(leftType) || TypesUtils.isString(rightType)) { + assert (kind == Tree.Kind.PLUS); + Node left = stringConversion(scan(leftTree, p)); + Node right = stringConversion(scan(rightTree, p)); + r = new StringConcatenateNode(tree, left, right); + } else { + TypeMirror promotedType = binaryPromotedType(leftType, rightType); + Node left = binaryNumericPromotion(scan(leftTree, p), promotedType); + Node right = binaryNumericPromotion(scan(rightTree, p), promotedType); + + // TODO: Decide whether to deal with floating-point value + // set conversion. + if (kind == Tree.Kind.PLUS) { + r = new NumericalAdditionNode(tree, left, right); + } else { + assert kind == Kind.MINUS; + r = new NumericalSubtractionNode(tree, left, right); + } + } + break; + } + + case LEFT_SHIFT: + case RIGHT_SHIFT: + case UNSIGNED_RIGHT_SHIFT: { + // see JLS 15.19 + + Node left = unaryNumericPromotion(scan(leftTree, p)); + Node right = unaryNumericPromotion(scan(rightTree, p)); + + if (kind == Tree.Kind.LEFT_SHIFT) { + r = new LeftShiftNode(tree, left, right); + } else if (kind == Tree.Kind.RIGHT_SHIFT) { + r = new SignedRightShiftNode(tree, left, right); + } else { + assert kind == Kind.UNSIGNED_RIGHT_SHIFT; + r = new UnsignedRightShiftNode(tree, left, right); + } + break; + } + + case GREATER_THAN: + case GREATER_THAN_EQUAL: + case LESS_THAN: + case LESS_THAN_EQUAL: { + // see JLS 15.20.1 + TypeMirror leftType = InternalUtils.typeOf(leftTree); + if (TypesUtils.isBoxedPrimitive(leftType)) { + leftType = types.unboxedType(leftType); + } + + TypeMirror rightType = InternalUtils.typeOf(rightTree); + if (TypesUtils.isBoxedPrimitive(rightType)) { + rightType = types.unboxedType(rightType); + } + + TypeMirror promotedType = binaryPromotedType(leftType, rightType); + Node left = binaryNumericPromotion(scan(leftTree, p), promotedType); + Node right = binaryNumericPromotion(scan(rightTree, p), promotedType); + + Node node; + if (kind == Tree.Kind.GREATER_THAN) { + node = new GreaterThanNode(tree, left, right); + } else if (kind == Tree.Kind.GREATER_THAN_EQUAL) { + node = new GreaterThanOrEqualNode(tree, left, right); + } else if (kind == Tree.Kind.LESS_THAN) { + node = new LessThanNode(tree, left, right); + } else { + assert kind == Tree.Kind.LESS_THAN_EQUAL; + node = new LessThanOrEqualNode(tree, left, right); + } + + extendWithNode(node); + + return node; + } + + case EQUAL_TO: + case NOT_EQUAL_TO: { + // see JLS 15.21 + TreeInfo leftInfo = getTreeInfo(leftTree); + TreeInfo rightInfo = getTreeInfo(rightTree); + Node left = scan(leftTree, p); + Node right = scan(rightTree, p); + + if (leftInfo.isNumeric() && rightInfo.isNumeric() && + !(leftInfo.isBoxed() && rightInfo.isBoxed())) { + // JLS 15.21.1 numerical equality + TypeMirror promotedType = binaryPromotedType(leftInfo.unboxedType(), + rightInfo.unboxedType()); + left = binaryNumericPromotion(left, promotedType); + right = binaryNumericPromotion(right, promotedType); + } else if (leftInfo.isBoolean() && rightInfo.isBoolean() && + !(leftInfo.isBoxed() && rightInfo.isBoxed())) { + // JSL 15.21.2 boolean equality + left = unboxAsNeeded(left, leftInfo.isBoxed()); + right = unboxAsNeeded(right, rightInfo.isBoxed()); + } + + Node node; + if (kind == Tree.Kind.EQUAL_TO) { + node = new EqualToNode(tree, left, right); + } else { + assert kind == Kind.NOT_EQUAL_TO; + node = new NotEqualNode(tree, left, right); + } + extendWithNode(node); + + return node; + } + + case AND: + case OR: + case XOR: { + // see JLS 15.22 + TypeMirror leftType = InternalUtils.typeOf(leftTree); + TypeMirror rightType = InternalUtils.typeOf(rightTree); + boolean isBooleanOp = TypesUtils.isBooleanType(leftType) && + TypesUtils.isBooleanType(rightType); + + Node left; + Node right; + + if (isBooleanOp) { + left = unbox(scan(leftTree, p)); + right = unbox(scan(rightTree, p)); + } else if (isNumericOrBoxed(leftType) && isNumericOrBoxed(rightType)) { + TypeMirror promotedType = binaryPromotedType(leftType, rightType); + left = binaryNumericPromotion(scan(leftTree, p), promotedType); + right = binaryNumericPromotion(scan(rightTree, p), promotedType); + } else { + left = unbox(scan(leftTree, p)); + right = unbox(scan(rightTree, p)); + } + + Node node; + if (kind == Tree.Kind.AND) { + node = new BitwiseAndNode(tree, left, right); + } else if (kind == Tree.Kind.OR) { + node = new BitwiseOrNode(tree, left, right); + } else { + assert kind == Kind.XOR; + node = new BitwiseXorNode(tree, left, right); + } + + extendWithNode(node); + + return node; + } + + case CONDITIONAL_AND: + case CONDITIONAL_OR: { + // see JLS 15.23 and 15.24 + + // all necessary labels + Label rightStartL = new Label(); + Label shortCircuitL = new Label(); + + // left-hand side + Node left = scan(leftTree, p); + + ConditionalJump cjump; + if (kind == Tree.Kind.CONDITIONAL_AND) { + cjump = new ConditionalJump(rightStartL, shortCircuitL); + cjump.setFalseFlowRule(Store.FlowRule.ELSE_TO_ELSE); + } else { + cjump = new ConditionalJump(shortCircuitL, rightStartL); + cjump.setTrueFlowRule(Store.FlowRule.THEN_TO_THEN); + } + extendWithExtendedNode(cjump); + + // right-hand side + addLabelForNextNode(rightStartL); + Node right = scan(rightTree, p); + + // conditional expression itself + addLabelForNextNode(shortCircuitL); + Node node; + if (kind == Tree.Kind.CONDITIONAL_AND) { + node = new ConditionalAndNode(tree, left, right); + } else { + node = new ConditionalOrNode(tree, left, right); + } + extendWithNode(node); + return node; + } + default: + assert false : "unexpected binary tree: " + kind; + break; + } + assert r != null : "unexpected binary tree"; + return extendWithNode(r); + } + + @Override + public Node visitBlock(BlockTree tree, Void p) { + for (StatementTree n : tree.getStatements()) { + scan(n, null); + } + return null; + } + + @Override + public Node visitBreak(BreakTree tree, Void p) { + Name label = tree.getLabel(); + if (label == null) { + assert breakTargetL != null : "no target for break statement"; + + extendWithExtendedNode(new UnconditionalJump(breakTargetL)); + } else { + assert breakLabels.containsKey(label); + + extendWithExtendedNode(new UnconditionalJump( + breakLabels.get(label))); + } + + return null; + } + + @Override + public Node visitSwitch(SwitchTree tree, Void p) { + SwitchBuilder builder = new SwitchBuilder(tree, p); + builder.build(); + return null; + } + + private class SwitchBuilder { + final private SwitchTree switchTree; + final private Label[] caseBodyLabels; + final private Void p; + private Node switchExpr; + + private SwitchBuilder(SwitchTree tree, Void p) { + this.switchTree = tree; + this.caseBodyLabels = new Label[switchTree.getCases().size() + 1]; + this.p = p; + } + + public void build() { + Label oldBreakTargetL = breakTargetL; + breakTargetL = new Label(); + int cases = caseBodyLabels.length-1; + for (int i = 0; i < cases; ++i) { + caseBodyLabels[i] = new Label(); + } + caseBodyLabels[cases] = breakTargetL; + + switchExpr = unbox(scan(switchTree.getExpression(), p)); + extendWithNode(new MarkerNode(switchTree, "start of switch statement", env.getTypeUtils())); + + Integer defaultIndex = null; + for (int i = 0; i < cases; ++i) { + CaseTree caseTree = switchTree.getCases().get(i); + if (caseTree.getExpression() == null) { + defaultIndex = i; + } else { + buildCase(caseTree, i); + } + } + if (defaultIndex != null) { + // the checks of all cases must happen before the default case, + // therefore we build the default case last. + // fallthrough is still handled correctly with the caseBodyLabels. + buildCase(switchTree.getCases().get(defaultIndex), defaultIndex); + } + + addLabelForNextNode(breakTargetL); + breakTargetL = oldBreakTargetL; + } + + private void buildCase(CaseTree tree, int index) { + final Label thisBodyL = caseBodyLabels[index]; + final Label nextBodyL = caseBodyLabels[index+1]; + final Label nextCaseL = new Label(); + + ExpressionTree exprTree = tree.getExpression(); + if (exprTree != null) { + Node expr = scan(exprTree, p); + CaseNode test = new CaseNode(tree, switchExpr, expr, env.getTypeUtils()); + extendWithNode(test); + extendWithExtendedNode(new ConditionalJump(thisBodyL, nextCaseL)); + } + addLabelForNextNode(thisBodyL); + for (StatementTree stmt : tree.getStatements()) { + scan(stmt, p); + } + extendWithExtendedNode(new UnconditionalJump(nextBodyL)); + addLabelForNextNode(nextCaseL); + } + } + + @Override + public Node visitCase(CaseTree tree, Void p) { + throw new AssertionError("case visitor is implemented in SwitchBuilder"); + } + + @Override + public Node visitCatch(CatchTree tree, Void p) { + scan(tree.getParameter(), p); + scan(tree.getBlock(), p); + return null; + } + + @Override + public Node visitClass(ClassTree tree, Void p) { + declaredClasses.add(tree); + return null; + } + + @Override + public Node visitConditionalExpression(ConditionalExpressionTree tree, + Void p) { + // see JLS 15.25 + TypeMirror exprType = InternalUtils.typeOf(tree); + + Label trueStart = new Label(); + Label falseStart = new Label(); + Label merge = new Label(); + + Node condition = unbox(scan(tree.getCondition(), p)); + ConditionalJump cjump = new ConditionalJump(trueStart, falseStart); + extendWithExtendedNode(cjump); + + addLabelForNextNode(trueStart); + Node trueExpr = scan(tree.getTrueExpression(), p); + trueExpr = conditionalExprPromotion(trueExpr, exprType); + extendWithExtendedNode(new UnconditionalJump(merge)); + + addLabelForNextNode(falseStart); + Node falseExpr = scan(tree.getFalseExpression(), p); + falseExpr = conditionalExprPromotion(falseExpr, exprType); + + addLabelForNextNode(merge); + Node node = new TernaryExpressionNode(tree, condition, trueExpr, falseExpr); + extendWithNode(node); + + return node; + } + + @Override + public Node visitContinue(ContinueTree tree, Void p) { + Name label = tree.getLabel(); + if (label == null) { + assert continueTargetL != null : "no target for continue statement"; + + extendWithExtendedNode(new UnconditionalJump(continueTargetL)); + } else { + assert continueLabels.containsKey(label); + + extendWithExtendedNode(new UnconditionalJump( + continueLabels.get(label))); + } + + return null; + } + + @Override + public Node visitDoWhileLoop(DoWhileLoopTree tree, Void p) { + Name parentLabel = getLabel(getCurrentPath()); + + Label loopEntry = new Label(); + Label loopExit = new Label(); + + // If the loop is a labeled statement, then its continue + // target is identical for continues with no label and + // continues with the loop's label. + Label conditionStart; + if (parentLabel != null) { + conditionStart = continueLabels.get(parentLabel); + } else { + conditionStart = new Label(); + } + + Label oldBreakTargetL = breakTargetL; + breakTargetL = loopExit; + + Label oldContinueTargetL = continueTargetL; + continueTargetL = conditionStart; + + // Loop body + addLabelForNextNode(loopEntry); + if (tree.getStatement() != null) { + scan(tree.getStatement(), p); + } + + // Condition + addLabelForNextNode(conditionStart); + if (tree.getCondition() != null) { + unbox(scan(tree.getCondition(), p)); + ConditionalJump cjump = new ConditionalJump(loopEntry, loopExit); + extendWithExtendedNode(cjump); + } + + // Loop exit + addLabelForNextNode(loopExit); + + breakTargetL = oldBreakTargetL; + continueTargetL = oldContinueTargetL; + + return null; + } + + @Override + public Node visitErroneous(ErroneousTree tree, Void p) { + assert false : "ErroneousTree is unexpected in AST to CFG translation"; + return null; + } + + @Override + public Node visitExpressionStatement(ExpressionStatementTree tree, + Void p) { + return scan(tree.getExpression(), p); + } + + @Override + public Node visitEnhancedForLoop(EnhancedForLoopTree tree, Void p) { + // see JLS 14.14.2 + Name parentLabel = getLabel(getCurrentPath()); + + Label conditionStart = new Label(); + Label loopEntry = new Label(); + Label loopExit = new Label(); + + // If the loop is a labeled statement, then its continue + // target is identical for continues with no label and + // continues with the loop's label. + Label updateStart; + if (parentLabel != null) { + updateStart = continueLabels.get(parentLabel); + } else { + updateStart = new Label(); + } + + Label oldBreakTargetL = breakTargetL; + breakTargetL = loopExit; + + Label oldContinueTargetL = continueTargetL; + continueTargetL = updateStart; + + // Distinguish loops over Iterables from loops over arrays. + + TypeElement iterableElement = elements.getTypeElement("java.lang.Iterable"); + TypeMirror iterableType = types.erasure(iterableElement.asType()); + + VariableTree variable = tree.getVariable(); + VariableElement variableElement = + TreeUtils.elementFromDeclaration(variable); + ExpressionTree expression = tree.getExpression(); + StatementTree statement = tree.getStatement(); + + TypeMirror exprType = InternalUtils.typeOf(expression); + + if (types.isSubtype(exprType, iterableType)) { + // Take the upper bound of a type variable or wildcard + exprType = TypesUtils.upperBound(exprType); + + assert (exprType instanceof DeclaredType) : "an Iterable must be a DeclaredType"; + DeclaredType declaredExprType = (DeclaredType) exprType; + declaredExprType.getTypeArguments(); + + MemberSelectTree iteratorSelect = + treeBuilder.buildIteratorMethodAccess(expression); + handleArtificialTree(iteratorSelect); + + MethodInvocationTree iteratorCall = + treeBuilder.buildMethodInvocation(iteratorSelect); + handleArtificialTree(iteratorCall); + + VariableTree iteratorVariable = createEnhancedForLoopIteratorVariable(iteratorCall, variableElement); + handleArtificialTree(iteratorVariable); + + VariableDeclarationNode iteratorVariableDecl = + new VariableDeclarationNode(iteratorVariable); + iteratorVariableDecl.setInSource(false); + + extendWithNode(iteratorVariableDecl); + + Node expressionNode = scan(expression, p); + + MethodAccessNode iteratorAccessNode = + new MethodAccessNode(iteratorSelect, expressionNode); + iteratorAccessNode.setInSource(false); + extendWithNode(iteratorAccessNode); + MethodInvocationNode iteratorCallNode = + new MethodInvocationNode(iteratorCall, iteratorAccessNode, + Collections.<Node>emptyList(), getCurrentPath()); + iteratorCallNode.setInSource(false); + extendWithNode(iteratorCallNode); + + translateAssignment(iteratorVariable, + new LocalVariableNode(iteratorVariable), + iteratorCallNode); + + // Test the loop ending condition + addLabelForNextNode(conditionStart); + IdentifierTree iteratorUse1 = + treeBuilder.buildVariableUse(iteratorVariable); + handleArtificialTree(iteratorUse1); + + LocalVariableNode iteratorReceiverNode = + new LocalVariableNode(iteratorUse1); + iteratorReceiverNode.setInSource(false); + extendWithNode(iteratorReceiverNode); + + MemberSelectTree hasNextSelect = + treeBuilder.buildHasNextMethodAccess(iteratorUse1); + handleArtificialTree(hasNextSelect); + + MethodAccessNode hasNextAccessNode = + new MethodAccessNode(hasNextSelect, iteratorReceiverNode); + hasNextAccessNode.setInSource(false); + extendWithNode(hasNextAccessNode); + + MethodInvocationTree hasNextCall = + treeBuilder.buildMethodInvocation(hasNextSelect); + handleArtificialTree(hasNextCall); + + MethodInvocationNode hasNextCallNode = + new MethodInvocationNode(hasNextCall, hasNextAccessNode, + Collections.<Node>emptyList(), getCurrentPath()); + hasNextCallNode.setInSource(false); + extendWithNode(hasNextCallNode); + extendWithExtendedNode(new ConditionalJump(loopEntry, loopExit)); + + // Loop body, starting with declaration of the loop iteration variable + addLabelForNextNode(loopEntry); + extendWithNode(new VariableDeclarationNode(variable)); + + IdentifierTree iteratorUse2 = + treeBuilder.buildVariableUse(iteratorVariable); + handleArtificialTree(iteratorUse2); + + LocalVariableNode iteratorReceiverNode2 = + new LocalVariableNode(iteratorUse2); + iteratorReceiverNode2.setInSource(false); + extendWithNode(iteratorReceiverNode2); + + MemberSelectTree nextSelect = + treeBuilder.buildNextMethodAccess(iteratorUse2); + handleArtificialTree(nextSelect); + + MethodAccessNode nextAccessNode = + new MethodAccessNode(nextSelect, iteratorReceiverNode2); + nextAccessNode.setInSource(false); + extendWithNode(nextAccessNode); + + MethodInvocationTree nextCall = + treeBuilder.buildMethodInvocation(nextSelect); + handleArtificialTree(nextCall); + + MethodInvocationNode nextCallNode = + new MethodInvocationNode(nextCall, nextAccessNode, + Collections.<Node>emptyList(), getCurrentPath()); + nextCallNode.setInSource(false); + extendWithNode(nextCallNode); + + translateAssignment(variable, + new LocalVariableNode(variable), + nextCall); + + if (statement != null) { + scan(statement, p); + } + + // Loop back edge + addLabelForNextNode(updateStart); + extendWithExtendedNode(new UnconditionalJump(conditionStart)); + + } else { + // TODO: Shift any labels after the initialization of the + // temporary array variable. + + VariableTree arrayVariable = createEnhancedForLoopArrayVariable(expression, variableElement); + handleArtificialTree(arrayVariable); + + VariableDeclarationNode arrayVariableNode = + new VariableDeclarationNode(arrayVariable); + arrayVariableNode.setInSource(false); + extendWithNode(arrayVariableNode); + Node expressionNode = scan(expression, p); + + translateAssignment(arrayVariable, + new LocalVariableNode(arrayVariable), + expressionNode); + + // Declare and initialize the loop index variable + TypeMirror intType = types.getPrimitiveType(TypeKind.INT); + + LiteralTree zero = + treeBuilder.buildLiteral(new Integer(0)); + handleArtificialTree(zero); + + VariableTree indexVariable = + treeBuilder.buildVariableDecl(intType, + uniqueName("index"), + variableElement.getEnclosingElement(), + zero); + handleArtificialTree(indexVariable); + VariableDeclarationNode indexVariableNode = + new VariableDeclarationNode(indexVariable); + indexVariableNode.setInSource(false); + extendWithNode(indexVariableNode); + IntegerLiteralNode zeroNode = + extendWithNode(new IntegerLiteralNode(zero)); + + translateAssignment(indexVariable, + new LocalVariableNode(indexVariable), + zeroNode); + + // Compare index to array length + addLabelForNextNode(conditionStart); + IdentifierTree indexUse1 = + treeBuilder.buildVariableUse(indexVariable); + handleArtificialTree(indexUse1); + LocalVariableNode indexNode1 = + new LocalVariableNode(indexUse1); + indexNode1.setInSource(false); + extendWithNode(indexNode1); + + IdentifierTree arrayUse1 = + treeBuilder.buildVariableUse(arrayVariable); + handleArtificialTree(arrayUse1); + LocalVariableNode arrayNode1 = + extendWithNode(new LocalVariableNode(arrayUse1)); + + MemberSelectTree lengthSelect = + treeBuilder.buildArrayLengthAccess(arrayUse1); + handleArtificialTree(lengthSelect); + FieldAccessNode lengthAccessNode = + new FieldAccessNode(lengthSelect, arrayNode1); + lengthAccessNode.setInSource(false); + extendWithNode(lengthAccessNode); + + BinaryTree lessThan = + treeBuilder.buildLessThan(indexUse1, lengthSelect); + handleArtificialTree(lessThan); + + LessThanNode lessThanNode = + new LessThanNode(lessThan, indexNode1, lengthAccessNode); + lessThanNode.setInSource(false); + extendWithNode(lessThanNode); + extendWithExtendedNode(new ConditionalJump(loopEntry, loopExit)); + + // Loop body, starting with declaration of the loop iteration variable + addLabelForNextNode(loopEntry); + extendWithNode(new VariableDeclarationNode(variable)); + + IdentifierTree arrayUse2 = + treeBuilder.buildVariableUse(arrayVariable); + handleArtificialTree(arrayUse2); + LocalVariableNode arrayNode2 = + new LocalVariableNode(arrayUse2); + arrayNode2.setInSource(false); + extendWithNode(arrayNode2); + + IdentifierTree indexUse2 = + treeBuilder.buildVariableUse(indexVariable); + handleArtificialTree(indexUse2); + LocalVariableNode indexNode2 = + new LocalVariableNode(indexUse2); + indexNode2.setInSource(false); + extendWithNode(indexNode2); + + ArrayAccessTree arrayAccess = + treeBuilder.buildArrayAccess(arrayUse2, indexUse2); + handleArtificialTree(arrayAccess); + ArrayAccessNode arrayAccessNode = + new ArrayAccessNode(arrayAccess, arrayNode2, indexNode2); + arrayAccessNode.setInSource(false); + extendWithNode(arrayAccessNode); + translateAssignment(variable, + new LocalVariableNode(variable), + arrayAccessNode); + + if (statement != null) { + scan(statement, p); + } + + // Loop back edge + addLabelForNextNode(updateStart); + + IdentifierTree indexUse3 = + treeBuilder.buildVariableUse(indexVariable); + handleArtificialTree(indexUse3); + LocalVariableNode indexNode3 = + new LocalVariableNode(indexUse3); + indexNode3.setInSource(false); + extendWithNode(indexNode3); + + LiteralTree oneTree = treeBuilder.buildLiteral(Integer.valueOf(1)); + handleArtificialTree(oneTree); + Node one = new IntegerLiteralNode(oneTree); + one.setInSource(false); + extendWithNode(one); + + BinaryTree addOneTree = treeBuilder.buildBinary(intType, Tree.Kind.PLUS, + indexUse3, oneTree); + handleArtificialTree(addOneTree); + Node addOneNode = new NumericalAdditionNode(addOneTree, indexNode3, one); + addOneNode.setInSource(false); + extendWithNode(addOneNode); + + AssignmentTree assignTree = treeBuilder.buildAssignment(indexUse3, addOneTree); + handleArtificialTree(assignTree); + Node assignNode = new AssignmentNode(assignTree, indexNode3, addOneNode); + assignNode.setInSource(false); + extendWithNode(assignNode); + + extendWithExtendedNode(new UnconditionalJump(conditionStart)); + } + + // Loop exit + addLabelForNextNode(loopExit); + + breakTargetL = oldBreakTargetL; + continueTargetL = oldContinueTargetL; + + return null; + } + + protected VariableTree createEnhancedForLoopIteratorVariable(MethodInvocationTree iteratorCall, VariableElement variableElement) { + TypeMirror iteratorType = InternalUtils.typeOf(iteratorCall); + + // Declare and initialize a new, unique iterator variable + VariableTree iteratorVariable = + treeBuilder.buildVariableDecl(iteratorType, // annotatedIteratorTypeTree, + uniqueName("iter"), + variableElement.getEnclosingElement(), + iteratorCall); + return iteratorVariable; + } + + protected VariableTree createEnhancedForLoopArrayVariable(ExpressionTree expression, VariableElement variableElement) { + TypeMirror arrayType = InternalUtils.typeOf(expression); + + // Declare and initialize a temporary array variable + VariableTree arrayVariable = + treeBuilder.buildVariableDecl(arrayType, + uniqueName("array"), + variableElement.getEnclosingElement(), + expression); + return arrayVariable; + } + + @Override + public Node visitForLoop(ForLoopTree tree, Void p) { + Name parentLabel = getLabel(getCurrentPath()); + + Label conditionStart = new Label(); + Label loopEntry = new Label(); + Label loopExit = new Label(); + + // If the loop is a labeled statement, then its continue + // target is identical for continues with no label and + // continues with the loop's label. + Label updateStart; + if (parentLabel != null) { + updateStart = continueLabels.get(parentLabel); + } else { + updateStart = new Label(); + } + + Label oldBreakTargetL = breakTargetL; + breakTargetL = loopExit; + + Label oldContinueTargetL = continueTargetL; + continueTargetL = updateStart; + + // Initializer + for (StatementTree init : tree.getInitializer()) { + scan(init, p); + } + + // Condition + addLabelForNextNode(conditionStart); + if (tree.getCondition() != null) { + unbox(scan(tree.getCondition(), p)); + ConditionalJump cjump = new ConditionalJump(loopEntry, loopExit); + extendWithExtendedNode(cjump); + } + + // Loop body + addLabelForNextNode(loopEntry); + if (tree.getStatement() != null) { + scan(tree.getStatement(), p); + } + + // Update + addLabelForNextNode(updateStart); + for (ExpressionStatementTree update : tree.getUpdate()) { + scan(update, p); + } + + extendWithExtendedNode(new UnconditionalJump(conditionStart)); + + // Loop exit + addLabelForNextNode(loopExit); + + breakTargetL = oldBreakTargetL; + continueTargetL = oldContinueTargetL; + + return null; + } + + @Override + public Node visitIdentifier(IdentifierTree tree, Void p) { + Node node; + if (TreeUtils.isFieldAccess(tree)) { + Node receiver = getReceiver(tree, + TreeUtils.enclosingClass(getCurrentPath())); + node = new FieldAccessNode(tree, receiver); + } else { + Element element = TreeUtils.elementFromUse(tree); + switch (element.getKind()) { + case ANNOTATION_TYPE: + case CLASS: + case ENUM: + case INTERFACE: + case TYPE_PARAMETER: + node = new ClassNameNode(tree); + break; + case FIELD: + // Note that "this"/"super" is a field, but not a field access. + if (element.getSimpleName().contentEquals("this")) { + node = new ExplicitThisLiteralNode(tree); + } else { + node = new SuperNode(tree); + } + break; + case EXCEPTION_PARAMETER: + case LOCAL_VARIABLE: + case RESOURCE_VARIABLE: + case PARAMETER: + node = new LocalVariableNode(tree); + break; + case PACKAGE: + node = new PackageNameNode(tree); + break; + default: + throw new IllegalArgumentException( + "unexpected element kind : " + element.getKind()); + } + } + extendWithNode(node); + return node; + } + + @Override + public Node visitIf(IfTree tree, Void p) { + // all necessary labels + Label thenEntry = new Label(); + Label elseEntry = new Label(); + Label endIf = new Label(); + + // basic block for the condition + unbox(scan(tree.getCondition(), p)); + + ConditionalJump cjump = new ConditionalJump(thenEntry, elseEntry); + extendWithExtendedNode(cjump); + + // then branch + addLabelForNextNode(thenEntry); + StatementTree thenStatement = tree.getThenStatement(); + scan(thenStatement, p); + extendWithExtendedNode(new UnconditionalJump(endIf)); + + // else branch + addLabelForNextNode(elseEntry); + StatementTree elseStatement = tree.getElseStatement(); + if (elseStatement != null) { + scan(elseStatement, p); + } + + // label the end of the if statement + addLabelForNextNode(endIf); + + return null; + } + + @Override + public Node visitImport(ImportTree tree, Void p) { + assert false : "ImportTree is unexpected in AST to CFG translation"; + return null; + } + + @Override + public Node visitArrayAccess(ArrayAccessTree tree, Void p) { + Node array = scan(tree.getExpression(), p); + Node index = unaryNumericPromotion(scan(tree.getIndex(), p)); + return extendWithNode(new ArrayAccessNode(tree, array, index)); + } + + @Override + public Node visitLabeledStatement(LabeledStatementTree tree, Void p) { + // This method can set the break target after generating all Nodes + // in the contained statement, but it can't set the continue target, + // which may be in the middle of a sequence of nodes. Labeled loops + // must look up and use the continue Labels. + Name labelName = tree.getLabel(); + + Label breakL = new Label(labelName + "_break"); + Label continueL = new Label(labelName + "_continue"); + + breakLabels.put(labelName, breakL); + continueLabels.put(labelName, continueL); + + scan(tree.getStatement(), p); + + addLabelForNextNode(breakL); + + breakLabels.remove(labelName); + continueLabels.remove(labelName); + + return null; + } + + @Override + public Node visitLiteral(LiteralTree tree, Void p) { + Node r = null; + switch (tree.getKind()) { + case BOOLEAN_LITERAL: + r = new BooleanLiteralNode(tree); + break; + case CHAR_LITERAL: + r = new CharacterLiteralNode(tree); + break; + case DOUBLE_LITERAL: + r = new DoubleLiteralNode(tree); + break; + case FLOAT_LITERAL: + r = new FloatLiteralNode(tree); + break; + case INT_LITERAL: + r = new IntegerLiteralNode(tree); + break; + case LONG_LITERAL: + r = new LongLiteralNode(tree); + break; + case NULL_LITERAL: + r = new NullLiteralNode(tree); + break; + case STRING_LITERAL: + r = new StringLiteralNode(tree); + break; + default: + assert false : "unexpected literal tree"; + break; + } + assert r != null : "unexpected literal tree"; + Node result = extendWithNode(r); + return result; + } + + @Override + public Node visitMethod(MethodTree tree, Void p) { + assert false : "MethodTree is unexpected in AST to CFG translation"; + return null; + } + + @Override + public Node visitModifiers(ModifiersTree tree, Void p) { + assert false : "ModifiersTree is unexpected in AST to CFG translation"; + return null; + } + + @Override + public Node visitNewArray(NewArrayTree tree, Void p) { + // see JLS 15.10 + + ArrayType type = (ArrayType)InternalUtils.typeOf(tree); + TypeMirror elemType = type.getComponentType(); + + List<? extends ExpressionTree> dimensions = tree.getDimensions(); + List<? extends ExpressionTree> initializers = tree + .getInitializers(); + + List<Node> dimensionNodes = new ArrayList<Node>(); + if (dimensions != null) { + for (ExpressionTree dim : dimensions) { + dimensionNodes.add(unaryNumericPromotion(scan(dim, p))); + } + } + + List<Node> initializerNodes = new ArrayList<Node>(); + if (initializers != null) { + for (ExpressionTree init : initializers) { + initializerNodes.add(assignConvert(scan(init, p), elemType)); + } + } + + Node node = new ArrayCreationNode(tree, type, dimensionNodes, + initializerNodes); + return extendWithNode(node); + } + + @Override + public Node visitNewClass(NewClassTree tree, Void p) { + // see JLS 15.9 + + Tree enclosingExpr = tree.getEnclosingExpression(); + if (enclosingExpr != null) { + scan(enclosingExpr, p); + } + + // We ignore any class body because its methods should + // be visited separately. + + // Convert constructor arguments + ExecutableElement constructor = TreeUtils.elementFromUse(tree); + + List<? extends ExpressionTree> actualExprs = tree.getArguments(); + + List<Node> arguments = convertCallArguments(constructor, + actualExprs); + + Node constructorNode = scan(tree.getIdentifier(), p); + + Node node = new ObjectCreationNode(tree, constructorNode, arguments); + + Set<TypeMirror> thrownSet = new HashSet<>(); + // Add exceptions explicitly mentioned in the throws clause. + List<? extends TypeMirror> thrownTypes = constructor.getThrownTypes(); + thrownSet.addAll(thrownTypes); + // Add Throwable to account for unchecked exceptions + TypeElement throwableElement = elements + .getTypeElement("java.lang.Throwable"); + thrownSet.add(throwableElement.asType()); + + extendWithNodeWithExceptions(node, thrownSet); + + return node; + } + + /** + * Maps a {@code Tree} its directly enclosing {@code ParenthesizedTree} if one exists. + * + * This map is used by {@link CFGTranslationPhaseOne#addToLookupMap(Node)} to + * associate a {@code ParenthesizedTree} with the dataflow {@code Node} that was used + * during inference. This map is necessary because dataflow does + * not create a {@code Node} for a {@code ParenthesizedTree.} + */ + private final Map<Tree, ParenthesizedTree> parenMapping = new HashMap<>(); + + @Override + public Node visitParenthesized(ParenthesizedTree tree, Void p) { + parenMapping.put(tree.getExpression(), tree); + return scan(tree.getExpression(), p); + } + + @Override + public Node visitReturn(ReturnTree tree, Void p) { + ExpressionTree ret = tree.getExpression(); + // TODO: also have a return-node if nothing is returned + ReturnNode result = null; + if (ret != null) { + Node node = scan(ret, p); + Tree enclosing = TreeUtils.enclosingOfKind(getCurrentPath(), new HashSet<Kind>(Arrays.asList(Kind.METHOD, Kind.LAMBDA_EXPRESSION))); + if (enclosing.getKind() == Kind.LAMBDA_EXPRESSION) { + LambdaExpressionTree lambdaTree = (LambdaExpressionTree) enclosing; + TreePath lambdaTreePath = TreePath.getPath(getCurrentPath().getCompilationUnit(), lambdaTree); + Context ctx = ((JavacProcessingEnvironment)env).getContext(); + Element overriddenElement = com.sun.tools.javac.code.Types.instance(ctx).findDescriptorSymbol( + ((Type)trees.getTypeMirror(lambdaTreePath)).tsym); + + result = new ReturnNode(tree, node, env.getTypeUtils(), lambdaTree, (MethodSymbol)overriddenElement); + } else { + result = new ReturnNode(tree, node, env.getTypeUtils(), (MethodTree)enclosing); + } + returnNodes.add(result); + extendWithNode(result); + } + extendWithExtendedNode(new UnconditionalJump(regularExitLabel)); + // TODO: return statements should also flow to an enclosing finally block + return result; + } + + @Override + public Node visitMemberSelect(MemberSelectTree tree, Void p) { + Node expr = scan(tree.getExpression(), p); + if (!TreeUtils.isFieldAccess(tree)) { + // Could be a selector of a class or package + Node result = null; + Element element = TreeUtils.elementFromUse(tree); + switch (element.getKind()) { + case ANNOTATION_TYPE: + case CLASS: + case ENUM: + case INTERFACE: + result = extendWithNode(new ClassNameNode(tree, expr)); + break; + case PACKAGE: + result = extendWithNode(new PackageNameNode(tree, (PackageNameNode) expr)); + break; + default: + assert false : "Unexpected element kind: " + element.getKind(); + return null; + } + return result; + } + + Node node = new FieldAccessNode(tree, expr); + + Element element = TreeUtils.elementFromUse(tree); + if (ElementUtils.isStatic(element) || + expr instanceof ImplicitThisLiteralNode || + expr instanceof ExplicitThisLiteralNode) { + // No NullPointerException can be thrown, use normal node + extendWithNode(node); + } else { + TypeElement npeElement = elements + .getTypeElement("java.lang.NullPointerException"); + extendWithNodeWithException(node, npeElement.asType()); + } + + return node; + } + + @Override + public Node visitEmptyStatement(EmptyStatementTree tree, Void p) { + return null; + } + + @Override + public Node visitSynchronized(SynchronizedTree tree, Void p) { + // see JLS 14.19 + + Node synchronizedExpr = scan(tree.getExpression(), p); + SynchronizedNode synchronizedStartNode = new SynchronizedNode(tree, synchronizedExpr, true, env.getTypeUtils()); + extendWithNode(synchronizedStartNode); + scan(tree.getBlock(), p); + SynchronizedNode synchronizedEndNode = new SynchronizedNode(tree, synchronizedExpr, false, env.getTypeUtils()); + extendWithNode(synchronizedEndNode); + + return null; + } + + @Override + public Node visitThrow(ThrowTree tree, Void p) { + Node expression = scan(tree.getExpression(), p); + TypeMirror exception = expression.getType(); + ThrowNode throwsNode = new ThrowNode(tree, expression, env.getTypeUtils()); + NodeWithExceptionsHolder exNode = extendWithNodeWithException( + throwsNode, exception); + exNode.setTerminatesExecution(true); + return throwsNode; + } + + @Override + public Node visitCompilationUnit(CompilationUnitTree tree, Void p) { + assert false : "CompilationUnitTree is unexpected in AST to CFG translation"; + return null; + } + + @Override + public Node visitTry(TryTree tree, Void p) { + List<? extends CatchTree> catches = tree.getCatches(); + BlockTree finallyBlock = tree.getFinallyBlock(); + + extendWithNode(new MarkerNode(tree, "start of try statement", env.getTypeUtils())); + + // TODO: Should we handle try-with-resources blocks by also generating code + // for automatically closing the resources? + List<? extends Tree> resources = tree.getResources(); + for (Tree resource : resources) { + scan(resource, p); + } + + List<Pair<TypeMirror, Label>> catchLabels = new ArrayList<>(); + for (CatchTree c : catches) { + TypeMirror type = InternalUtils.typeOf(c.getParameter().getType()); + assert type != null : "exception parameters must have a type"; + catchLabels.add(Pair.of(type, new Label())); + } + + Label finallyLabel = null; + if (finallyBlock != null) { + finallyLabel = new Label(); + tryStack.pushFrame(new TryFinallyFrame(finallyLabel)); + } + + Label doneLabel = new Label(); + + tryStack.pushFrame(new TryCatchFrame(types, catchLabels)); + + scan(tree.getBlock(), p); + extendWithExtendedNode(new UnconditionalJump(firstNonNull(finallyLabel, doneLabel))); + + tryStack.popFrame(); + + int catchIndex = 0; + for (CatchTree c : catches) { + addLabelForNextNode(catchLabels.get(catchIndex).second); + scan(c, p); + catchIndex++; + extendWithExtendedNode(new UnconditionalJump(firstNonNull(finallyLabel, doneLabel))); + } + + if (finallyLabel != null) { + tryStack.popFrame(); + addLabelForNextNode(finallyLabel); + scan(finallyBlock, p); + + TypeMirror throwableType = + elements.getTypeElement("java.lang.Throwable").asType(); + extendWithNodeWithException(new MarkerNode(tree, "end of finally block", env.getTypeUtils()), + throwableType); + } + + addLabelForNextNode(doneLabel); + + return null; + } + + @Override + public Node visitParameterizedType(ParameterizedTypeTree tree, Void p) { + return extendWithNode(new ParameterizedTypeNode(tree)); + } + + @Override + public Node visitUnionType(UnionTypeTree tree, Void p) { + assert false : "UnionTypeTree is unexpected in AST to CFG translation"; + return null; + } + + @Override + public Node visitArrayType(ArrayTypeTree tree, Void p) { + return extendWithNode(new ArrayTypeNode(tree)); + } + + @Override + public Node visitTypeCast(TypeCastTree tree, Void p) { + final Node operand = scan(tree.getExpression(), p); + final TypeMirror type = InternalUtils.typeOf(tree.getType()); + final Node node = new TypeCastNode(tree, operand, type); + final TypeElement cceElement = elements.getTypeElement("java.lang.ClassCastException"); + + extendWithNodeWithException(node, cceElement.asType()); + return node; + } + + @Override + public Node visitPrimitiveType(PrimitiveTypeTree tree, Void p) { + return extendWithNode(new PrimitiveTypeNode(tree)); + } + + @Override + public Node visitTypeParameter(TypeParameterTree tree, Void p) { + assert false : "TypeParameterTree is unexpected in AST to CFG translation"; + return null; + } + + @Override + public Node visitInstanceOf(InstanceOfTree tree, Void p) { + Node operand = scan(tree.getExpression(), p); + TypeMirror refType = InternalUtils.typeOf(tree.getType()); + InstanceOfNode node = new InstanceOfNode(tree, operand, refType, + types); + extendWithNode(node); + return node; + } + + @Override + public Node visitUnary(UnaryTree tree, Void p) { + Node result = null; + Tree.Kind kind = tree.getKind(); + switch (kind) { + case BITWISE_COMPLEMENT: + case UNARY_MINUS: + case UNARY_PLUS: { + // see JLS 15.14 and 15.15 + Node expr = scan(tree.getExpression(), p); + expr = unaryNumericPromotion(expr); + + // TypeMirror exprType = InternalUtils.typeOf(tree); + + switch (kind) { + case BITWISE_COMPLEMENT: + result = extendWithNode(new BitwiseComplementNode(tree, + expr)); + break; + case UNARY_MINUS: + result = extendWithNode(new NumericalMinusNode(tree, expr)); + break; + case UNARY_PLUS: + result = extendWithNode(new NumericalPlusNode(tree, expr)); + break; + default: + assert false; + break; + } + break; + } + + case LOGICAL_COMPLEMENT: { + // see JLS 15.15.6 + Node expr = scan(tree.getExpression(), p); + result = extendWithNode(new ConditionalNotNode(tree, + unbox(expr))); + break; + } + + case POSTFIX_DECREMENT: + case POSTFIX_INCREMENT: { + ExpressionTree exprTree = tree.getExpression(); + TypeMirror exprType = InternalUtils.typeOf(exprTree); + TypeMirror oneType = types.getPrimitiveType(TypeKind.INT); + Node expr = scan(exprTree, p); + + TypeMirror promotedType = binaryPromotedType(exprType, oneType); + + LiteralTree oneTree = treeBuilder.buildLiteral(Integer.valueOf(1)); + handleArtificialTree(oneTree); + + Node exprRHS = binaryNumericPromotion(expr, promotedType); + Node one = new IntegerLiteralNode(oneTree); + one.setInSource(false); + extendWithNode(one); + one = binaryNumericPromotion(one, promotedType); + + BinaryTree operTree = treeBuilder.buildBinary(promotedType, + (kind == Tree.Kind.POSTFIX_INCREMENT ? Tree.Kind.PLUS : Tree.Kind.MINUS), + exprTree, oneTree); + handleArtificialTree(operTree); + Node operNode; + if (kind == Tree.Kind.POSTFIX_INCREMENT) { + operNode = new NumericalAdditionNode(operTree, exprRHS, one); + } else { + assert kind == Tree.Kind.POSTFIX_DECREMENT; + operNode = new NumericalSubtractionNode(operTree, exprRHS, one); + } + extendWithNode(operNode); + + Node narrowed = narrowAndBox(operNode, exprType); + // TODO: By using the assignment as the result of the expression, we + // act like a pre-increment/decrement. Fix this by saving the initial + // value of the expression in a temporary. + AssignmentNode assignNode = new AssignmentNode(tree, expr, narrowed); + extendWithNode(assignNode); + result = assignNode; + break; + } + case PREFIX_DECREMENT: + case PREFIX_INCREMENT: { + ExpressionTree exprTree = tree.getExpression(); + TypeMirror exprType = InternalUtils.typeOf(exprTree); + TypeMirror oneType = types.getPrimitiveType(TypeKind.INT); + Node expr = scan(exprTree, p); + + TypeMirror promotedType = binaryPromotedType(exprType, oneType); + + LiteralTree oneTree = treeBuilder.buildLiteral(Integer.valueOf(1)); + handleArtificialTree(oneTree); + + Node exprRHS = binaryNumericPromotion(expr, promotedType); + Node one = new IntegerLiteralNode(oneTree); + one.setInSource(false); + extendWithNode(one); + one = binaryNumericPromotion(one, promotedType); + + BinaryTree operTree = treeBuilder.buildBinary(promotedType, + (kind == Tree.Kind.PREFIX_INCREMENT ? Tree.Kind.PLUS : Tree.Kind.MINUS), + exprTree, oneTree); + handleArtificialTree(operTree); + Node operNode; + if (kind == Tree.Kind.PREFIX_INCREMENT) { + operNode = new NumericalAdditionNode(operTree, exprRHS, one); + } else { + assert kind == Tree.Kind.PREFIX_DECREMENT; + operNode = new NumericalSubtractionNode(operTree, exprRHS, one); + } + extendWithNode(operNode); + + Node narrowed = narrowAndBox(operNode, exprType); + AssignmentNode assignNode = new AssignmentNode(tree, expr, narrowed); + extendWithNode(assignNode); + result = assignNode; + break; + } + + case OTHER: + default: + // special node NLLCHK + if (tree.toString().startsWith("<*nullchk*>")) { + Node expr = scan(tree.getExpression(), p); + result = extendWithNode(new NullChkNode(tree, expr)); + break; + } + + assert false : "Unknown kind (" + kind + + ") of unary expression: " + tree; + } + + return result; + } + + @Override + public Node visitVariable(VariableTree tree, Void p) { + + // see JLS 14.4 + + boolean isField = getCurrentPath().getParentPath() != null + && getCurrentPath().getParentPath().getLeaf().getKind() == Kind.CLASS; + Node node = null; + + ClassTree enclosingClass = TreeUtils + .enclosingClass(getCurrentPath()); + TypeElement classElem = TreeUtils + .elementFromDeclaration(enclosingClass); + Node receiver = new ImplicitThisLiteralNode(classElem.asType()); + + if (isField) { + ExpressionTree initializer = tree.getInitializer(); + assert initializer != null; + node = translateAssignment( + tree, + new FieldAccessNode(tree, TreeUtils.elementFromDeclaration(tree), receiver), + initializer); + } else { + // local variable definition + VariableDeclarationNode decl = new VariableDeclarationNode(tree); + extendWithNode(decl); + + // initializer + + ExpressionTree initializer = tree.getInitializer(); + if (initializer != null) { + node = translateAssignment(tree, new LocalVariableNode(tree, receiver), + initializer); + } + } + + return node; + } + + @Override + public Node visitWhileLoop(WhileLoopTree tree, Void p) { + Name parentLabel = getLabel(getCurrentPath()); + + Label loopEntry = new Label(); + Label loopExit = new Label(); + + // If the loop is a labeled statement, then its continue + // target is identical for continues with no label and + // continues with the loop's label. + Label conditionStart; + if (parentLabel != null) { + conditionStart = continueLabels.get(parentLabel); + } else { + conditionStart = new Label(); + } + + Label oldBreakTargetL = breakTargetL; + breakTargetL = loopExit; + + Label oldContinueTargetL = continueTargetL; + continueTargetL = conditionStart; + + // Condition + addLabelForNextNode(conditionStart); + if (tree.getCondition() != null) { + unbox(scan(tree.getCondition(), p)); + ConditionalJump cjump = new ConditionalJump(loopEntry, loopExit); + extendWithExtendedNode(cjump); + } + + // Loop body + addLabelForNextNode(loopEntry); + if (tree.getStatement() != null) { + scan(tree.getStatement(), p); + } + extendWithExtendedNode(new UnconditionalJump(conditionStart)); + + // Loop exit + addLabelForNextNode(loopExit); + + breakTargetL = oldBreakTargetL; + continueTargetL = oldContinueTargetL; + + return null; + } + + @Override + public Node visitLambdaExpression(LambdaExpressionTree tree, Void p) { + declaredLambdas.add(tree); + Node node = new FunctionalInterfaceNode(tree); + extendWithNode(node); + return node; + } + + @Override + public Node visitMemberReference(MemberReferenceTree tree, Void p) { + Tree enclosingExpr = tree.getQualifierExpression(); + if (enclosingExpr != null) { + scan(enclosingExpr, p); + } + + Node node = new FunctionalInterfaceNode(tree); + extendWithNode(node); + + return node; + } + + @Override + public Node visitWildcard(WildcardTree tree, Void p) { + assert false : "WildcardTree is unexpected in AST to CFG translation"; + return null; + } + + @Override + public Node visitOther(Tree tree, Void p) { + assert false : "Unknown AST element encountered in AST to CFG translation."; + return null; + } + } + + /** + * A tuple with 4 named elements. + */ + private interface TreeInfo { + boolean isBoxed(); + boolean isNumeric(); + boolean isBoolean(); + TypeMirror unboxedType(); + } + + private static <A> A firstNonNull(A first, A second) { + if (first != null) { + return first; + } else if (second != null) { + return second; + } else { + throw new NullPointerException(); + } + } + + /* --------------------------------------------------------- */ + /* Utility routines for debugging CFG building */ + /* --------------------------------------------------------- */ + + /** + * Print a set of {@link Block}s and the edges between them. This is useful + * for examining the results of phase two. + */ + protected static void printBlocks(Set<Block> blocks) { + for (Block b : blocks) { + System.out.print(b.hashCode() + ": " + b); + switch (b.getType()) { + case REGULAR_BLOCK: + case SPECIAL_BLOCK: { + Block succ = ((SingleSuccessorBlockImpl) b).getSuccessor(); + System.out.println(" -> " + + (succ != null ? succ.hashCode() : "||")); + break; + } + case EXCEPTION_BLOCK: { + Block succ = ((SingleSuccessorBlockImpl) b).getSuccessor(); + System.out.print(" -> " + + (succ != null ? succ.hashCode() : "||") + " {"); + for (Map.Entry<TypeMirror, Set<Block>> entry : ((ExceptionBlockImpl) b).getExceptionalSuccessors().entrySet()) { + System.out.print(entry.getKey() + " : " + entry.getValue() + ", "); + } + System.out.println("}"); + break; + } + case CONDITIONAL_BLOCK: { + Block tSucc = ((ConditionalBlockImpl) b).getThenSuccessor(); + Block eSucc = ((ConditionalBlockImpl) b).getElseSuccessor(); + System.out.println(" -> T " + + (tSucc != null ? tSucc.hashCode() : "||") + " F " + + (eSucc != null ? eSucc.hashCode() : "||")); + break; + } + } + } + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/CFGVisualizer.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/CFGVisualizer.java new file mode 100644 index 0000000000..8156f92942 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/CFGVisualizer.java @@ -0,0 +1,181 @@ +package org.checkerframework.dataflow.cfg; + +/*>>> +import org.checkerframework.checker.nullness.qual.Nullable; +*/ + +import org.checkerframework.dataflow.analysis.AbstractValue; +import org.checkerframework.dataflow.analysis.Analysis; +import org.checkerframework.dataflow.analysis.FlowExpressions; +import org.checkerframework.dataflow.analysis.Store; +import org.checkerframework.dataflow.analysis.TransferFunction; +import org.checkerframework.dataflow.cfg.block.Block; +import org.checkerframework.dataflow.cfg.block.SpecialBlock; +import org.checkerframework.dataflow.cfg.node.Node; + +import java.util.Map; + +/** + * Perform some visualization on a control flow graph. + * The particular operations depend on the implementation. + */ +public interface CFGVisualizer<A extends AbstractValue<A>, + S extends Store<S>, T extends TransferFunction<A, S>> { + /** + * Initialization method guaranteed to be called once before the + * first invocation of {@link visualize}. + * + * @param args implementation-dependent options + */ + void init(Map<String, Object> args); + + /** + * Output a visualization representing the control flow graph starting + * at {@code entry}. + * The concrete actions are implementation dependent. + * + * An invocation {@code visualize(cfg, entry, null);} does not + * output stores at the beginning of basic blocks. + * + * @param cfg + * The CFG to visualize. + * @param entry + * The entry node of the control flow graph to be represented. + * @param analysis + * An analysis containing information about the program + * represented by the CFG. The information includes {@link Store}s + * that are valid at the beginning of basic blocks reachable + * from {@code entry} and per-node information for value + * producing {@link Node}s. Can also be {@code null} to + * indicate that this information should not be output. + * @return possible analysis results, e.g. generated file names. + */ + /*@Nullable*/ Map<String, Object> visualize(ControlFlowGraph cfg, Block entry, + /*@Nullable*/ Analysis<A, S, T> analysis); + + /** + * Delegate the visualization responsibility + * to the passed {@link Store} instance, which will call back to this + * visualizer instance for sub-components. + * + * @param store the store to visualize + */ + void visualizeStore(S store); + + /** + * Called by a {@code CFAbstractStore} to visualize + * the class name before calling the + * {@code CFAbstractStore#internalVisualize()} method. + * + * @param classCanonicalName the canonical name of the class + */ + void visualizeStoreHeader(String classCanonicalName); + + /** + * Called by {@code CFAbstractStore#internalVisualize()} to visualize + * a local variable. + * + * @param localVar the local variable + * @param value the value of the local variable + */ + void visualizeStoreLocalVar(FlowExpressions.LocalVariable localVar, A value); + + /** + * Called by {@code CFAbstractStore#internalVisualize()} to visualize + * the value of the current object {@code this} in this Store. + * + * @param value the value of the current object this + */ + void visualizeStoreThisVal(A value); + + /** + * Called by {@code CFAbstractStore#internalVisualize()} to visualize + * the value of fields collected by this Store. + * + * @param fieldAccess the field + * @param value the value of the field + */ + void visualizeStoreFieldVals(FlowExpressions.FieldAccess fieldAccess, A value); + + /** + * Called by {@code CFAbstractStore#internalVisualize()} to visualize + * the value of arrays collected by this Store. + * + * @param arrayValue the array + * @param value the value of the array + */ + void visualizeStoreArrayVal(FlowExpressions.ArrayAccess arrayValue, A value); + + /** + * Called by {@code CFAbstractStore#internalVisualize()} to visualize + * the value of pure method calls collected by this Store. + * + * @param methodCall the pure method call + * @param value the value of the pure method call + */ + void visualizeStoreMethodVals(FlowExpressions.MethodCall methodCall, A value); + + /** + * Called by {@code CFAbstractStore#internalVisualize()} to visualize + * the value of class names collected by this Store. + * + * @param className the class name + * @param value the value of the class name + */ + void visualizeStoreClassVals(FlowExpressions.ClassName className, A value); + + /** + * Called by {@code CFAbstractStore#internalVisualize()} to visualize + * the specific information collected according to the specific kind of Store. + * Currently, these Stores call this method: {@code LockStore}, + * {@code NullnessStore}, and {@code InitializationStore} to visualize additional + * information. + * + * @param keyName the name of the specific information to be visualized + * @param value the value of the specific information to be visualized + */ + void visualizeStoreKeyVal(String keyName, Object value); + + /** + * Called by {@code CFAbstractStore} to visualize + * any information after the invocation of {@code CFAbstractStore#internalVisualize()}. + */ + void visualizeStoreFooter(); + + /** + * Visualize a block based on the analysis. + * + * @param bb the block + * @param analysis the current analysis + */ + void visualizeBlock(Block bb, /*@Nullable*/ Analysis<A, S, T> analysis); + + /** + * Visualize a SpecialBlock. + * + * @param sbb the special block + */ + void visualizeSpecialBlock(SpecialBlock sbb); + + /** + * Visualize the transferInput of a Block based on the analysis. + * + * @param bb the block + * @param analysis the current analysis + */ + void visualizeBlockTransferInput(Block bb, Analysis<A, S, T> analysis); + + /** + * Visualize a Node based on the analysis. + * + * @param t the node + * @param analysis the current analysis + */ + void visualizeBlockNode(Node t, /*@Nullable*/ Analysis<A, S, T> analysis); + + /** + * Shutdown method called once from the shutdown hook of the + * {@code BaseTypeChecker}. + */ + void shutdown(); +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/ControlFlowGraph.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/ControlFlowGraph.java new file mode 100644 index 0000000000..58f354e6ae --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/ControlFlowGraph.java @@ -0,0 +1,254 @@ +package org.checkerframework.dataflow.cfg; + +/*>>> +import org.checkerframework.checker.nullness.qual.Nullable; +*/ + +import org.checkerframework.dataflow.cfg.block.Block; +import org.checkerframework.dataflow.cfg.block.Block.BlockType; +import org.checkerframework.dataflow.cfg.block.ConditionalBlock; +import org.checkerframework.dataflow.cfg.block.ExceptionBlock; +import org.checkerframework.dataflow.cfg.block.SingleSuccessorBlock; +import org.checkerframework.dataflow.cfg.block.SpecialBlock; +import org.checkerframework.dataflow.cfg.block.SpecialBlockImpl; +import org.checkerframework.dataflow.cfg.node.Node; +import org.checkerframework.dataflow.cfg.node.ReturnNode; + +import java.util.Collections; +import java.util.Deque; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.Set; + +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.Tree; + +/** + * A control flow graph (CFG for short) of a single method. + * + * @author Stefan Heule + * + */ +public class ControlFlowGraph { + + /** The entry block of the control flow graph. */ + protected final SpecialBlock entryBlock; + + /** The regular exit block of the control flow graph. */ + protected final SpecialBlock regularExitBlock; + + /** The exceptional exit block of the control flow graph. */ + protected final SpecialBlock exceptionalExitBlock; + + /** The AST this CFG corresponds to. */ + protected UnderlyingAST underlyingAST; + + /** + * Maps from AST {@link Tree}s to {@link Node}s. Every Tree that produces + * a value will have at least one corresponding Node. Trees + * that undergo conversions, such as boxing or unboxing, can map to two + * distinct Nodes. The Node for the pre-conversion value is stored + * in treeLookup, while the Node for the post-conversion value + * is stored in convertedTreeLookup. + */ + protected IdentityHashMap<Tree, Node> treeLookup; + + /** Map from AST {@link Tree}s to post-conversion {@link Node}s. */ + protected IdentityHashMap<Tree, Node> convertedTreeLookup; + + /** + * All return nodes (if any) encountered. Only includes return + * statements that actually return something + */ + protected final List<ReturnNode> returnNodes; + + public ControlFlowGraph(SpecialBlock entryBlock, SpecialBlockImpl regularExitBlock, SpecialBlockImpl exceptionalExitBlock, UnderlyingAST underlyingAST, + IdentityHashMap<Tree, Node> treeLookup, + IdentityHashMap<Tree, Node> convertedTreeLookup, + List<ReturnNode> returnNodes) { + super(); + this.entryBlock = entryBlock; + this.underlyingAST = underlyingAST; + this.treeLookup = treeLookup; + this.convertedTreeLookup = convertedTreeLookup; + this.regularExitBlock = regularExitBlock; + this.exceptionalExitBlock = exceptionalExitBlock; + this.returnNodes = returnNodes; + } + + /** + * @return the {@link Node} to which the {@link Tree} {@code t} + * corresponds. + */ + public Node getNodeCorrespondingToTree(Tree t) { + if (convertedTreeLookup.containsKey(t)) { + return convertedTreeLookup.get(t); + } else { + return treeLookup.get(t); + } + } + + /** @return the entry block of the control flow graph. */ + public SpecialBlock getEntryBlock() { + return entryBlock; + } + + public List<ReturnNode> getReturnNodes() { + return returnNodes; + } + + public SpecialBlock getRegularExitBlock() { + return regularExitBlock; + } + + public SpecialBlock getExceptionalExitBlock() { + return exceptionalExitBlock; + } + + /** @return the AST this CFG corresponds to. */ + public UnderlyingAST getUnderlyingAST() { + return underlyingAST; + } + + /** + * @return the set of all basic block in this control flow graph + */ + public Set<Block> getAllBlocks() { + Set<Block> visited = new HashSet<>(); + Queue<Block> worklist = new LinkedList<>(); + Block cur = entryBlock; + visited.add(entryBlock); + + // traverse the whole control flow graph + while (true) { + if (cur == null) { + break; + } + + Queue<Block> succs = new LinkedList<>(); + if (cur.getType() == BlockType.CONDITIONAL_BLOCK) { + ConditionalBlock ccur = ((ConditionalBlock) cur); + succs.add(ccur.getThenSuccessor()); + succs.add(ccur.getElseSuccessor()); + } else { + assert cur instanceof SingleSuccessorBlock; + Block b = ((SingleSuccessorBlock) cur).getSuccessor(); + if (b != null) { + succs.add(b); + } + } + + if (cur.getType() == BlockType.EXCEPTION_BLOCK) { + ExceptionBlock ecur = (ExceptionBlock) cur; + for (Set<Block> exceptionSuccSet : ecur.getExceptionalSuccessors().values()) { + succs.addAll(exceptionSuccSet); + } + } + + for (Block b : succs) { + if (!visited.contains(b)) { + visited.add(b); + worklist.add(b); + } + } + + cur = worklist.poll(); + } + + return visited; + } + + /** + * @return the list of all basic block in this control flow graph + * in reversed depth-first postorder sequence. + * + * Blocks may appear more than once in the sequence. + */ + public List<Block> getDepthFirstOrderedBlocks() { + List<Block> dfsOrderResult = new LinkedList<>(); + Set<Block> visited = new HashSet<>(); + Deque<Block> worklist = new LinkedList<>(); + worklist.add(entryBlock); + while (!worklist.isEmpty()) { + Block cur = worklist.getLast(); + if (visited.contains(cur)) { + dfsOrderResult.add(cur); + worklist.removeLast(); + } else { + visited.add(cur); + Deque<Block> successors = getSuccessors(cur); + successors.removeAll(visited); + worklist.addAll(successors); + } + } + + Collections.reverse(dfsOrderResult); + return dfsOrderResult; + } + + /** + * Get a list of all successor Blocks for cur + * @return a Deque of successor Blocks + */ + private Deque<Block> getSuccessors(Block cur) { + Deque<Block> succs = new LinkedList<>(); + if (cur.getType() == BlockType.CONDITIONAL_BLOCK) { + ConditionalBlock ccur = ((ConditionalBlock) cur); + succs.add(ccur.getThenSuccessor()); + succs.add(ccur.getElseSuccessor()); + } else { + assert cur instanceof SingleSuccessorBlock; + Block b = ((SingleSuccessorBlock) cur).getSuccessor(); + if (b != null) { + succs.add(b); + } + } + + if (cur.getType() == BlockType.EXCEPTION_BLOCK) { + ExceptionBlock ecur = (ExceptionBlock) cur; + for (Set<Block> exceptionSuccSet : ecur.getExceptionalSuccessors().values()) { + succs.addAll(exceptionSuccSet); + } + } + return succs; + } + + /** + * @return the tree-lookup map + */ + public IdentityHashMap<Tree, Node> getTreeLookup() { + return new IdentityHashMap<>(treeLookup); + } + + /** + * Get the {@link MethodTree} of the CFG if the argument {@link Tree} maps + * to a {@link Node} in the CFG or null otherwise. + */ + public /*@Nullable*/ MethodTree getContainingMethod(Tree t) { + if (treeLookup.containsKey(t)) { + if (underlyingAST.getKind() == UnderlyingAST.Kind.METHOD) { + UnderlyingAST.CFGMethod cfgMethod = (UnderlyingAST.CFGMethod) underlyingAST; + return cfgMethod.getMethod(); + } + } + return null; + } + + /** + * Get the {@link ClassTree} of the CFG if the argument {@link Tree} maps + * to a {@link Node} in the CFG or null otherwise. + */ + public /*@Nullable*/ ClassTree getContainingClass(Tree t) { + if (treeLookup.containsKey(t)) { + if (underlyingAST.getKind() == UnderlyingAST.Kind.METHOD) { + UnderlyingAST.CFGMethod cfgMethod = (UnderlyingAST.CFGMethod) underlyingAST; + return cfgMethod.getClassTree(); + } + } + return null; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/DOTCFGVisualizer.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/DOTCFGVisualizer.java new file mode 100644 index 0000000000..3810b30765 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/DOTCFGVisualizer.java @@ -0,0 +1,508 @@ +package org.checkerframework.dataflow.cfg; + +/*>>> +import org.checkerframework.checker.nullness.qual.Nullable; +*/ + +import org.checkerframework.dataflow.analysis.AbstractValue; +import org.checkerframework.dataflow.analysis.Analysis; +import org.checkerframework.dataflow.analysis.FlowExpressions; +import org.checkerframework.dataflow.analysis.Store; +import org.checkerframework.dataflow.analysis.TransferFunction; +import org.checkerframework.dataflow.analysis.TransferInput; +import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGMethod; +import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGStatement; +import org.checkerframework.dataflow.cfg.block.Block; +import org.checkerframework.dataflow.cfg.block.Block.BlockType; +import org.checkerframework.dataflow.cfg.block.ConditionalBlock; +import org.checkerframework.dataflow.cfg.block.ExceptionBlock; +import org.checkerframework.dataflow.cfg.block.RegularBlock; +import org.checkerframework.dataflow.cfg.block.SingleSuccessorBlock; +import org.checkerframework.dataflow.cfg.block.SpecialBlock; +import org.checkerframework.dataflow.cfg.node.Node; +import org.checkerframework.javacutil.ErrorReporter; + +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Queue; +import java.util.Set; + +import javax.lang.model.type.TypeMirror; + +import com.sun.tools.javac.tree.JCTree; + +/** + * Generate a graph description in the DOT language of a control graph. + * + * @author Stefan Heule + * + */ +public class DOTCFGVisualizer<A extends AbstractValue<A>, + S extends Store<S>, T extends TransferFunction<A, S>> + implements CFGVisualizer<A, S, T> { + + protected String outdir; + protected boolean verbose; + protected String checkerName; + + protected StringBuilder sbDigraph; + protected StringBuilder sbStore; + protected StringBuilder sbBlock; + + /** Mapping from class/method representation to generated dot file. */ + protected Map<String, String> generated; + + public void init(Map<String, Object> args) { + this.outdir = (String) args.get("outdir"); + { + Object verb = args.get("verbose"); + this.verbose = verb == null ? false : + verb instanceof String ? Boolean.getBoolean((String) verb) : + (boolean) verb; + } + this.checkerName = (String) args.get("checkerName"); + + this.generated = new HashMap<>(); + + this.sbDigraph = new StringBuilder(); + + this.sbStore = new StringBuilder(); + + this.sbBlock = new StringBuilder(); + } + + /** + * {@inheritDoc} + */ + public /*@Nullable*/ Map<String, Object> visualize(ControlFlowGraph cfg, Block entry, + /*@Nullable*/ Analysis<A, S, T> analysis) { + + String dotgraph = generateDotGraph(cfg, entry, analysis); + + String dotfilename = dotOutputFileName(cfg.underlyingAST); + // System.err.println("Output to DOT file: " + dotfilename); + + try { + FileWriter fstream = new FileWriter(dotfilename); + BufferedWriter out = new BufferedWriter(fstream); + out.write(dotgraph); + out.close(); + } catch (IOException e) { + ErrorReporter.errorAbort("Error creating dot file: " + dotfilename + + "; ensure the path is valid", e); + } + + Map<String, Object> res = new HashMap<>(); + res.put("dotFileName", dotfilename); + + return res; + } + + /** + * Generate the dot representation as String. + */ + protected String generateDotGraph(ControlFlowGraph cfg, Block entry, + /*@Nullable*/ Analysis<A, S, T> analysis) { + this.sbDigraph.setLength(0); + Set<Block> visited = new HashSet<>(); + + // header + this.sbDigraph.append("digraph {\n"); + + Block cur = entry; + Queue<Block> worklist = new LinkedList<>(); + visited.add(entry); + // traverse control flow graph and define all arrows + while (true) { + if (cur == null) { + break; + } + + if (cur.getType() == BlockType.CONDITIONAL_BLOCK) { + ConditionalBlock ccur = ((ConditionalBlock) cur); + Block thenSuccessor = ccur.getThenSuccessor(); + addDotEdge(ccur.getId(), thenSuccessor.getId(), "then\\n" + ccur.getThenFlowRule()); + if (!visited.contains(thenSuccessor)) { + visited.add(thenSuccessor); + worklist.add(thenSuccessor); + } + Block elseSuccessor = ccur.getElseSuccessor(); + addDotEdge(ccur.getId(), elseSuccessor.getId(), "else\\n" + ccur.getElseFlowRule()); + if (!visited.contains(elseSuccessor)) { + visited.add(elseSuccessor); + worklist.add(elseSuccessor); + } + } else { + assert cur instanceof SingleSuccessorBlock; + Block b = ((SingleSuccessorBlock) cur).getSuccessor(); + if (b != null) { + addDotEdge(cur.getId(), b.getId(), ((SingleSuccessorBlock) cur).getFlowRule().name()); + if (!visited.contains(b)) { + visited.add(b); + worklist.add(b); + } + } + } + + // exceptional edges + if (cur.getType() == BlockType.EXCEPTION_BLOCK) { + ExceptionBlock ecur = (ExceptionBlock) cur; + for (Entry<TypeMirror, Set<Block>> e : ecur + .getExceptionalSuccessors().entrySet()) { + Set<Block> blocks = e.getValue(); + TypeMirror cause = e.getKey(); + String exception = cause.toString(); + if (exception.startsWith("java.lang.")) { + exception = exception.replace("java.lang.", ""); + } + + for (Block b : blocks) { + addDotEdge(cur.getId(), b.getId(), exception); + if (!visited.contains(b)) { + visited.add(b); + worklist.add(b); + } + } + } + } + + cur = worklist.poll(); + } + + generateDotNodes(visited, cfg, analysis); + + // footer + this.sbDigraph.append("}\n"); + + return this.sbDigraph.toString(); + } + + protected void generateDotNodes(Set<Block> visited, ControlFlowGraph cfg, Analysis<A, S, T> analysis) { + IdentityHashMap<Block, List<Integer>> processOrder = getProcessOrder(cfg); + this.sbDigraph.append(" node [shape=rectangle];\n\n"); + // definition of all nodes including their labels + for (Block v : visited) { + this.sbDigraph.append(" " + v.getId() + " ["); + if (v.getType() == BlockType.CONDITIONAL_BLOCK) { + this.sbDigraph.append("shape=polygon sides=8 "); + } else if (v.getType() == BlockType.SPECIAL_BLOCK) { + this.sbDigraph.append("shape=oval "); + } + this.sbDigraph.append("label=\""); + if (verbose) { + this.sbDigraph.append("Process order: " + processOrder.get(v).toString().replaceAll("[\\[\\]]", "") + "\\n"); + } + visualizeBlock(v, analysis); + } + + this.sbDigraph.append("\n"); + } + + /** @return the file name used for DOT output. */ + protected String dotOutputFileName(UnderlyingAST ast) { + StringBuilder srcloc = new StringBuilder(); + + StringBuilder outfile = new StringBuilder(outdir); + outfile.append('/'); + if (ast.getKind() == UnderlyingAST.Kind.ARBITRARY_CODE) { + CFGStatement cfgs = (CFGStatement) ast; + String clsname = cfgs.getClassTree().getSimpleName().toString(); + outfile.append(clsname); + outfile.append("-initializer-"); + outfile.append(ast.hashCode()); + + srcloc.append('<'); + srcloc.append(clsname); + srcloc.append("::initializer::"); + srcloc.append(((JCTree)cfgs.getCode()).pos); + srcloc.append('>'); + } else if (ast.getKind() == UnderlyingAST.Kind.METHOD) { + CFGMethod cfgm = (CFGMethod) ast; + String clsname = cfgm.getClassTree().getSimpleName().toString(); + String methname = cfgm.getMethod().getName().toString(); + outfile.append(clsname); + outfile.append('-'); + outfile.append(methname); + + srcloc.append('<'); + srcloc.append(clsname); + srcloc.append("::"); + srcloc.append(methname); + srcloc.append('('); + srcloc.append(cfgm.getMethod().getParameters()); + srcloc.append(")::"); + srcloc.append(((JCTree)cfgm.getMethod()).pos); + srcloc.append('>'); + } else { + ErrorReporter.errorAbort("Unexpected AST kind: " + ast.getKind() + + " value: " + ast.toString()); + return null; + } + outfile.append('-'); + outfile.append(checkerName); + outfile.append(".dot"); + + // make path safe for Windows + String out = outfile.toString().replace("<", "_").replace(">", ""); + + generated.put(srcloc.toString(), out); + + return out; + } + + protected IdentityHashMap<Block, List<Integer>> getProcessOrder(ControlFlowGraph cfg) { + IdentityHashMap<Block, List<Integer>> depthFirstOrder = new IdentityHashMap<>(); + int count = 1; + for (Block b : cfg.getDepthFirstOrderedBlocks()) { + if (depthFirstOrder.get(b) == null) { + depthFirstOrder.put(b, new ArrayList<Integer>()); + } + depthFirstOrder.get(b).add(count++); + } + return depthFirstOrder; + } + + /** + * Produce a representation of the contests of a basic block. + * + * @param bb basic block to visualize + */ + @Override + public void visualizeBlock(Block bb, + /*@Nullable*/ Analysis<A, S, T> analysis) { + + this.sbBlock.setLength(0); + + // loop over contents + List<Node> contents = new LinkedList<>(); + switch (bb.getType()) { + case REGULAR_BLOCK: + contents.addAll(((RegularBlock) bb).getContents()); + break; + case EXCEPTION_BLOCK: + contents.add(((ExceptionBlock) bb).getNode()); + break; + case CONDITIONAL_BLOCK: + break; + case SPECIAL_BLOCK: + break; + default: + assert false : "All types of basic blocks covered"; + } + boolean notFirst = false; + for (Node t : contents) { + if (notFirst) { + this.sbBlock.append("\\n"); + } + notFirst = true; + visualizeBlockNode(t, analysis); + } + + // handle case where no contents are present + boolean centered = false; + if (this.sbBlock.length() == 0) { + centered = true; + if (bb.getType() == BlockType.SPECIAL_BLOCK) { + visualizeSpecialBlock((SpecialBlock) bb); + } else if (bb.getType() == BlockType.CONDITIONAL_BLOCK) { + this.sbDigraph.append(" \",];\n"); + return; + } else { + this.sbDigraph.append("?? empty ?? \",];\n"); + return; + } + } + + // visualize transfer input if necessary + if (analysis != null) { + visualizeBlockTransferInput(bb, analysis); + } + + this.sbDigraph.append((this.sbBlock.toString() + (centered ? "" : "\\n")).replace("\\n", "\\l") + " \",];\n"); + } + + @Override + public void visualizeSpecialBlock(SpecialBlock sbb) { + switch (sbb.getSpecialType()) { + case ENTRY: + this.sbBlock.append("<entry>"); + break; + case EXIT: + this.sbBlock.append("<exit>"); + break; + case EXCEPTIONAL_EXIT: + this.sbBlock.append("<exceptional-exit>"); + break; + } + } + + @Override + public void visualizeBlockTransferInput(Block bb, Analysis<A, S, T> analysis) { + TransferInput<A, S> input = analysis.getInput(bb); + this.sbStore.setLength(0); + + // split input representation to two lines + this.sbStore.append("Before:"); + S thenStore = input.getThenStore(); + if (thenStore == null) { + S regularStore = input.getRegularStore(); + this.sbStore.append('['); + visualizeStore(regularStore); + this.sbStore.append(']'); + } else { + S elseStore = input.getElseStore(); + this.sbStore.append("[then="); + visualizeStore(thenStore); + this.sbStore.append(", else="); + visualizeStore(elseStore); + this.sbStore.append("]"); + } + // separator + this.sbStore.append("\\n~~~~~~~~~\\n"); + + // the transfer input before this block is added before the block content + this.sbBlock.insert(0, this.sbStore); + + if (verbose) { + Node lastNode; + switch (bb.getType()) { + case REGULAR_BLOCK: + List<Node> blockContents = ((RegularBlock) bb).getContents(); + lastNode = blockContents.get(blockContents.size() - 1); + break; + case EXCEPTION_BLOCK: + lastNode = ((ExceptionBlock) bb).getNode(); + break; + default: + lastNode = null; + } + if (lastNode != null) { + this.sbStore.setLength(0); + this.sbStore.append("\\n~~~~~~~~~\\n"); + this.sbStore.append("After:"); + visualizeStore(analysis.getResult().getStoreAfter(lastNode.getTree())); + this.sbBlock.append(this.sbStore); + } + } + } + + @Override + public void visualizeBlockNode(Node t, /*@Nullable*/ Analysis<A, S, T> analysis) { + A value = analysis.getValue(t); + String valueInfo = ""; + if (value != null) { + valueInfo = " > " + prepareString(value.toString()); + } + this.sbBlock.append(prepareString(t.toString()) + " [ " + prepareNodeType(t) + " ]" + valueInfo); + } + + protected String prepareNodeType(Node t) { + String name = t.getClass().getSimpleName(); + return name.replace("Node", ""); + } + + protected String prepareString(String s) { + return s.replace("\"", "\\\""); + } + + protected void addDotEdge(long sId, long eId, String labelContent) { + this.sbDigraph.append(" " + sId + " -> "+ eId + " [label=\""+ labelContent + "\"];\n"); + } + + @Override + public void visualizeStore(S store) { + store.visualize(this); + } + + @Override + public void visualizeStoreThisVal(A value) { + this.sbStore.append(" this > " + value + + "\\n"); + } + + @Override + public void visualizeStoreLocalVar(FlowExpressions.LocalVariable localVar, A value) { + this.sbStore.append(" " + localVar + " > " + + toStringEscapeDoubleQuotes(value) + + "\\n"); + } + + @Override + public void visualizeStoreFieldVals(FlowExpressions.FieldAccess fieldAccess, A value) { + this.sbStore.append(" " + fieldAccess + " > " + + toStringEscapeDoubleQuotes(value) + + "\\n"); + } + + @Override + public void visualizeStoreArrayVal(FlowExpressions.ArrayAccess arrayValue, A value) { + this.sbStore.append(" " + arrayValue + " > " + + toStringEscapeDoubleQuotes(value) + "\\n"); + } + + @Override + public void visualizeStoreMethodVals(FlowExpressions.MethodCall methodCall, A value) { + this.sbStore.append(" " + methodCall.toString().replace("\"", "\\\"") + " > " + + value + "\\n"); + } + + @Override + public void visualizeStoreClassVals(FlowExpressions.ClassName className, A value) { + this.sbStore.append(" " + className + " > " + toStringEscapeDoubleQuotes(value) + "\\n"); + } + + @Override + public void visualizeStoreKeyVal(String keyName, Object value) { + this.sbStore.append(" "+keyName+" = "+value+"\\n"); + } + + protected String escapeDoubleQuotes(final String str) { + return str.replace("\"", "\\\""); + } + + protected String toStringEscapeDoubleQuotes(final Object obj) { + return escapeDoubleQuotes(String.valueOf(obj)); + } + + @Override + public void visualizeStoreHeader(String classCanonicalName) { + this.sbStore.append(classCanonicalName + " (\\n"); + } + + @Override + public void visualizeStoreFooter() { + this.sbStore.append(")"); + } + + /** + * Write a file {@code methods.txt} that contains a mapping from + * source code location to generated dot file. + */ + @Override + public void shutdown() { + try { + // Open for append, in case of multiple sub-checkers. + FileWriter fstream = new FileWriter(outdir + "/methods.txt", true); + BufferedWriter out = new BufferedWriter(fstream); + for (Map.Entry<String, String> kv : generated.entrySet()) { + out.write(kv.getKey()); + out.append('\t'); + out.write(kv.getValue()); + out.append('\n'); + } + out.close(); + } catch (IOException e) { + ErrorReporter.errorAbort("Error creating methods.txt file in: " + outdir + + "; ensure the path is valid", e); + } + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/JavaSource2CFGDOT.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/JavaSource2CFGDOT.java new file mode 100644 index 0000000000..241c087cbc --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/JavaSource2CFGDOT.java @@ -0,0 +1,275 @@ +package org.checkerframework.dataflow.cfg; + +/*>>> +import org.checkerframework.checker.nullness.qual.Nullable; +*/ + +import org.checkerframework.dataflow.analysis.AbstractValue; +import org.checkerframework.dataflow.analysis.Analysis; +import org.checkerframework.dataflow.analysis.Store; +import org.checkerframework.dataflow.analysis.TransferFunction; +import org.checkerframework.javacutil.BasicTypeProcessor; +import org.checkerframework.javacutil.TreeUtils; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import javax.lang.model.element.ExecutableElement; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; +import javax.xml.ws.Holder; + +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.util.TreePathScanner; +import com.sun.tools.javac.file.JavacFileManager; +import com.sun.tools.javac.main.JavaCompiler; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.List; + +/** + * Class to generate the DOT representation of the control flow graph of a given + * method. + * + * @author Stefan Heule + */ +public class JavaSource2CFGDOT { + + /** Main method. */ + public static void main(String[] args) { + if (args.length < 2) { + printUsage(); + System.exit(1); + } + String input = args[0]; + String output = args[1]; + File file = new File(input); + if (!file.canRead()) { + printError("Cannot read input file: " + file.getAbsolutePath()); + printUsage(); + System.exit(1); + } + + String method = "test"; + String clas = "Test"; + boolean pdf = false; + boolean error = false; + + for (int i = 2; i < args.length; i++) { + if (args[i].equals("-pdf")) { + pdf = true; + } else if (args[i].equals("-method")) { + if (i >= args.length - 1) { + printError("Did not find <name> after -method."); + continue; + } + i++; + method = args[i]; + } else if (args[i].equals("-class")) { + if (i >= args.length - 1) { + printError("Did not find <name> after -class."); + continue; + } + i++; + clas = args[i]; + } else { + printError("Unknown command line argument: " + args[i]); + error = true; + } + } + + if (error) { + System.exit(1); + } + + generateDOTofCFG(input, output, method, clas, pdf); + } + + /** Print an error message. */ + protected static void printError(String string) { + System.err.println("ERROR: " + string); + } + + /** Print usage information. */ + protected static void printUsage() { + System.out + .println("Generate the control flow graph of a Java method, represented as a DOT graph."); + System.out + .println("Parameters: <inputfile> <outputdir> [-method <name>] [-class <name>] [-pdf]"); + System.out + .println(" -pdf: Also generate the PDF by invoking 'dot'."); + System.out + .println(" -method: The method to generate the CFG for (defaults to 'test')."); + System.out + .println(" -class: The class in which to find the method (defaults to 'Test')."); + } + + /** Just like method above but without analysis. */ + public static void generateDOTofCFG(String inputFile, String outputDir, + String method, String clas, boolean pdf) { + generateDOTofCFG(inputFile, outputDir, method, clas, pdf, null); + } + + /** + * Generate the DOT representation of the CFG for a method. + * + * @param inputFile + * Java source input file. + * @param outputDir + * Source output directory. + * @param method + * Method name to generate the CFG for. + * @param pdf + * Also generate a PDF? + * @param analysis + * Analysis to perform befor the visualization (or + * {@code null} if no analysis is to be performed). + */ + public static + <A extends AbstractValue<A>, S extends Store<S>, T extends TransferFunction<A, S>> + void generateDOTofCFG( + String inputFile, String outputDir, String method, String clas, + boolean pdf, /*@Nullable*/ Analysis<A, S, T> analysis) { + Entry<MethodTree, CompilationUnitTree> m = getMethodTreeAndCompilationUnit(inputFile, method, clas); + generateDOTofCFG(inputFile, outputDir, method, clas, pdf, analysis, m.getKey(), m.getValue()); + } + + public static + <A extends AbstractValue<A>, S extends Store<S>, T extends TransferFunction<A, S>> + void generateDOTofCFG( + String inputFile, String outputDir, String method, String clas, + boolean pdf, /*@Nullable*/ Analysis<A, S, T> analysis, MethodTree m, + CompilationUnitTree r) { + String fileName = (new File(inputFile)).getName(); + System.out.println("Working on " + fileName + "..."); + + if (m == null) { + printError("Method not found."); + System.exit(1); + } + + ControlFlowGraph cfg = CFGBuilder.build(r, null, m, null); + if (analysis != null) { + analysis.performAnalysis(cfg); + } + + Map<String, Object> args = new HashMap<>(); + args.put("outdir", outputDir); + args.put("checkerName", ""); + + CFGVisualizer<A, S, T> viz = new DOTCFGVisualizer<A, S, T>(); + viz.init(args); + Map<String, Object> res = viz.visualize(cfg, cfg.getEntryBlock(), analysis); + viz.shutdown(); + + if (pdf) { + producePDF((String) res.get("dotFileName")); + } + } + + /** + * Invoke DOT to generate a PDF. + */ + protected static void producePDF(String file) { + try { + String command = "dot -Tpdf \"" + file + ".txt\" -o \"" + file + + ".pdf\""; + Process child = Runtime.getRuntime().exec(command); + child.waitFor(); + } catch (InterruptedException | IOException e) { + e.printStackTrace(); + System.exit(1); + } + } + + /** + * @return the AST of a specific method in a specific class in a specific + * file (or null if no such method exists) + */ + public static /*@Nullable*/ MethodTree getMethodTree(String file, + final String method, String clas) { + return getMethodTreeAndCompilationUnit(file, method, clas).getKey(); + } + + /** + * @return the AST of a specific method in a specific class as well as the + * {@link CompilationUnitTree} in a specific file (or null they do + * not exist). + */ + public static Entry</*@Nullable*/ MethodTree, /*@Nullable*/ CompilationUnitTree> getMethodTreeAndCompilationUnit( + String file, final String method, String clas) { + final Holder<MethodTree> m = new Holder<>(); + final Holder<CompilationUnitTree> c = new Holder<>(); + BasicTypeProcessor typeProcessor = new BasicTypeProcessor() { + @Override + protected TreePathScanner<?, ?> createTreePathScanner( + CompilationUnitTree root) { + c.value = root; + return new TreePathScanner<Void, Void>() { + @Override + public Void visitMethod(MethodTree node, Void p) { + ExecutableElement el = TreeUtils + .elementFromDeclaration(node); + if (el.getSimpleName().contentEquals(method)) { + m.value = node; + // stop execution by throwing an exception. this + // makes sure that compilation does not proceed, and + // thus the AST is not modified by further phases of + // the compilation (and we save the work to do the + // compilation). + throw new RuntimeException(); + } + return null; + } + }; + } + }; + + Context context = new Context(); + JavaCompiler javac = new JavaCompiler(context); + javac.attrParseOnly = true; + JavacFileManager fileManager = (JavacFileManager) context + .get(JavaFileManager.class); + + JavaFileObject l = fileManager + .getJavaFileObjectsFromStrings(List.of(file)).iterator().next(); + + PrintStream err = System.err; + try { + // redirect syserr to nothing (and prevent the compiler from issuing + // warnings about our exception. + System.setErr(new PrintStream(new OutputStream() { + @Override + public void write(int b) throws IOException { + } + })); + javac.compile(List.of(l), List.of(clas), List.of(typeProcessor)); + } catch (Throwable e) { + // ok + } finally { + System.setErr(err); + } + return new Entry<MethodTree, CompilationUnitTree>() { + @Override + public CompilationUnitTree setValue(CompilationUnitTree value) { + return null; + } + + @Override + public CompilationUnitTree getValue() { + return c.value; + } + + @Override + public MethodTree getKey() { + return m.value; + } + }; + } + +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/UnderlyingAST.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/UnderlyingAST.java new file mode 100644 index 0000000000..ce4c267832 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/UnderlyingAST.java @@ -0,0 +1,136 @@ +package org.checkerframework.dataflow.cfg; + +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.LambdaExpressionTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.Tree; + +/** + * Represents an abstract syntax tree of type {@link Tree} that underlies a + * given control flow graph. + * + * @author Stefan Heule + * + */ +public abstract class UnderlyingAST { + public enum Kind { + /** The underlying code is a whole method */ + METHOD, + /** The underlying code is a lambda expression */ + LAMBDA, + + /** + * The underlying code is an arbitrary Java statement or expression + */ + ARBITRARY_CODE, + } + + protected final Kind kind; + + public UnderlyingAST(Kind kind) { + this.kind = kind; + } + + /** + * @return the code that corresponds to the CFG + */ + abstract public Tree getCode(); + + public Kind getKind() { + return kind; + } + + /** + * If the underlying AST is a method. + */ + public static class CFGMethod extends UnderlyingAST { + + /** The method declaration */ + protected final MethodTree method; + + /** The class tree this method belongs to. */ + protected final ClassTree classTree; + + public CFGMethod(MethodTree method, ClassTree classTree) { + super(Kind.METHOD); + this.method = method; + this.classTree = classTree; + } + + @Override + public Tree getCode() { + return method.getBody(); + } + + public MethodTree getMethod() { + return method; + } + + public ClassTree getClassTree() { + return classTree; + } + + @Override + public String toString() { + return "CFGMethod(\n" + method + "\n)"; + } + } + + /** + * If the underlying AST is a lambda. + */ + public static class CFGLambda extends UnderlyingAST { + + private final LambdaExpressionTree lambda; + + public CFGLambda(LambdaExpressionTree lambda) { + super(Kind.LAMBDA); + this.lambda = lambda; + } + + @Override + public Tree getCode() { + return lambda.getBody(); + } + + public LambdaExpressionTree getLambdaTree() { + return lambda; + } + + @Override + public String toString() { + return "CFGLambda(\n" + lambda + "\n)"; + } + } + + /** + * If the underlying AST is a statement or expression. + */ + public static class CFGStatement extends UnderlyingAST { + + protected final Tree code; + + /** The class tree this method belongs to. */ + protected final ClassTree classTree; + + public CFGStatement(Tree code, ClassTree classTree) { + super(Kind.ARBITRARY_CODE); + this.code = code; + this.classTree = classTree; + } + + @Override + public Tree getCode() { + return code; + } + + public ClassTree getClassTree() { + return classTree; + } + + @Override + public String toString() { + return "CFGStatement(\n" + code + "\n)"; + } + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/Block.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/Block.java new file mode 100644 index 0000000000..09aa2d4180 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/Block.java @@ -0,0 +1,37 @@ +package org.checkerframework.dataflow.cfg.block; + +/** + * Represents a basic block in a control flow graph. + * + * @author Stefan Heule + * + */ +public interface Block { + + /** The types of basic blocks */ + public static enum BlockType { + + /** A regular basic block. */ + REGULAR_BLOCK, + + /** A conditional basic block. */ + CONDITIONAL_BLOCK, + + /** A special basic block. */ + SPECIAL_BLOCK, + + /** A basic block that can throw an exception. */ + EXCEPTION_BLOCK, + } + + /** + * @return the type of this basic block + */ + BlockType getType(); + + /** + * @return the unique identifier of this block + */ + long getId(); + +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/BlockImpl.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/BlockImpl.java new file mode 100644 index 0000000000..568b62b071 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/BlockImpl.java @@ -0,0 +1,63 @@ +package org.checkerframework.dataflow.cfg.block; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * Base class of the {@link Block} implementation hierarchy. + * + * @author Stefan Heule + * + */ +public abstract class BlockImpl implements Block { + + /** A unique ID for this node. */ + protected long id = BlockImpl.uniqueID(); + + /** The last ID that has already been used. */ + protected static long lastId = 0; + + /** The type of this basic block. */ + protected BlockType type; + + /** The set of predecessors. */ + protected Set<BlockImpl> predecessors; + + /** + * @return a fresh identifier + */ + private static long uniqueID() { + return lastId++; + } + + public BlockImpl() { + predecessors = new HashSet<>(); + } + + @Override + public long getId() { + return id; + } + + @Override + public BlockType getType() { + return type; + } + + /** + * @return the list of predecessors of this basic block + */ + public Set<BlockImpl> getPredecessors() { + return Collections.unmodifiableSet(predecessors); + } + + public void addPredecessor(BlockImpl pred) { + predecessors.add(pred); + } + + public void removePredecessor(BlockImpl pred) { + predecessors.remove(pred); + } + +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/ConditionalBlock.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/ConditionalBlock.java new file mode 100644 index 0000000000..2f1621ef2f --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/ConditionalBlock.java @@ -0,0 +1,48 @@ +package org.checkerframework.dataflow.cfg.block; + +import org.checkerframework.dataflow.analysis.Store; +import org.checkerframework.dataflow.cfg.node.Node; + +/** + * Represents a conditional basic block that contains exactly one boolean + * {@link Node}. + * + * @author Stefan Heule + * + */ +public interface ConditionalBlock extends Block { + + /** + * @return the entry block of the then branch + */ + Block getThenSuccessor(); + + /** + * @return the entry block of the else branch + */ + Block getElseSuccessor(); + + /** + * @return the flow rule for information flowing from + * this block to its then successor + */ + Store.FlowRule getThenFlowRule(); + + /** + * @return the flow rule for information flowing from + * this block to its else successor + */ + Store.FlowRule getElseFlowRule(); + + /** + * Set the flow rule for information flowing from this block to + * its then successor. + */ + void setThenFlowRule(Store.FlowRule rule); + + /** + * Set the flow rule for information flowing from this block to + * its else successor. + */ + void setElseFlowRule(Store.FlowRule rule); +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/ConditionalBlockImpl.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/ConditionalBlockImpl.java new file mode 100644 index 0000000000..15c3842bad --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/ConditionalBlockImpl.java @@ -0,0 +1,87 @@ +package org.checkerframework.dataflow.cfg.block; + +import org.checkerframework.dataflow.analysis.Store; + +/** + * Implementation of a conditional basic block. + * + * @author Stefan Heule + * + */ +public class ConditionalBlockImpl extends BlockImpl implements ConditionalBlock { + + /** Successor of the then branch. */ + protected BlockImpl thenSuccessor; + + /** Successor of the else branch. */ + protected BlockImpl elseSuccessor; + + /** + * The rules below say that the THEN store before a conditional + * block flows to BOTH of the stores of the then successor, while + * the ELSE store before a conditional block flows to BOTH of the + * stores of the else successor. + */ + protected Store.FlowRule thenFlowRule = Store.FlowRule.THEN_TO_BOTH; + + protected Store.FlowRule elseFlowRule = Store.FlowRule.ELSE_TO_BOTH; + + /** + * Initialize an empty conditional basic block to be filled with contents + * and linked to other basic blocks later. + */ + public ConditionalBlockImpl() { + type = BlockType.CONDITIONAL_BLOCK; + } + + /** + * Set the then branch successor. + */ + public void setThenSuccessor(BlockImpl b) { + thenSuccessor = b; + b.addPredecessor(this); + } + + /** + * Set the else branch successor. + */ + public void setElseSuccessor(BlockImpl b) { + elseSuccessor = b; + b.addPredecessor(this); + } + + @Override + public Block getThenSuccessor() { + return thenSuccessor; + } + + @Override + public Block getElseSuccessor() { + return elseSuccessor; + } + + @Override + public Store.FlowRule getThenFlowRule() { + return thenFlowRule; + } + + @Override + public Store.FlowRule getElseFlowRule() { + return elseFlowRule; + } + + @Override + public void setThenFlowRule(Store.FlowRule rule) { + thenFlowRule = rule; + } + + @Override + public void setElseFlowRule(Store.FlowRule rule) { + elseFlowRule = rule; + } + + @Override + public String toString() { + return "ConditionalBlock()"; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/ExceptionBlock.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/ExceptionBlock.java new file mode 100644 index 0000000000..da024d12ef --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/ExceptionBlock.java @@ -0,0 +1,38 @@ +package org.checkerframework.dataflow.cfg.block; + +import java.util.Map; +import java.util.Set; + +import javax.lang.model.type.TypeMirror; + +import org.checkerframework.dataflow.cfg.node.Node; + +/** + * Represents a basic block that contains exactly one {@link Node} which can + * throw an exception. This block has exactly one non-exceptional successor, and + * one or more exceptional successors. + * + * <p> + * + * The following invariant holds. + * + * <pre> + * getNode().getBlock() == this + * </pre> + * + * @author Stefan Heule + * + */ +public interface ExceptionBlock extends SingleSuccessorBlock { + + /** + * @return the node of this block + */ + Node getNode(); + + /** + * @return the list of exceptional successor blocks as an unmodifiable map + */ + Map<TypeMirror, Set<Block>> getExceptionalSuccessors(); + +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/ExceptionBlockImpl.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/ExceptionBlockImpl.java new file mode 100644 index 0000000000..2148e060cd --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/ExceptionBlockImpl.java @@ -0,0 +1,76 @@ +package org.checkerframework.dataflow.cfg.block; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.lang.model.type.TypeMirror; + +import org.checkerframework.dataflow.cfg.node.Node; + +/** + * Base class of the {@link Block} implementation hierarchy. + * + * @author Stefan Heule + * + */ +public class ExceptionBlockImpl extends SingleSuccessorBlockImpl implements + ExceptionBlock { + + /** Set of exceptional successors. */ + protected Map<TypeMirror, Set<Block>> exceptionalSuccessors; + + public ExceptionBlockImpl() { + type = BlockType.EXCEPTION_BLOCK; + exceptionalSuccessors = new HashMap<>(); + } + + /** The node of this block. */ + protected Node node; + + /** + * Set the node. + */ + public void setNode(Node c) { + node = c; + c.setBlock(this); + } + + @Override + public Node getNode() { + return node; + } + + /** + * Add an exceptional successor. + */ + public void addExceptionalSuccessor(BlockImpl b, + TypeMirror cause) { + if (exceptionalSuccessors == null) { + exceptionalSuccessors = new HashMap<>(); + } + Set<Block> blocks = exceptionalSuccessors.get(cause); + if (blocks == null) { + blocks = new HashSet<Block>(); + exceptionalSuccessors.put(cause, blocks); + } + blocks.add(b); + b.addPredecessor(this); + } + + @Override + public Map<TypeMirror, Set<Block>> getExceptionalSuccessors() { + if (exceptionalSuccessors == null) { + return Collections.emptyMap(); + } + return Collections.unmodifiableMap(exceptionalSuccessors); + } + + @Override + public String toString() { + return "ExceptionBlock(" + node + ")"; + } + +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/RegularBlock.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/RegularBlock.java new file mode 100644 index 0000000000..d29d7f1ec0 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/RegularBlock.java @@ -0,0 +1,38 @@ +package org.checkerframework.dataflow.cfg.block; + +import java.util.List; + +import org.checkerframework.dataflow.cfg.node.Node; + +/** + * A regular basic block that contains a sequence of {@link Node}s. + * + * <p> + * + * The following invariant holds. + * + * <pre> + * forall n in getContents() :: n.getBlock() == this + * </pre> + * + * @author Stefan Heule + * + */ +public interface RegularBlock extends SingleSuccessorBlock { + + /** + * @return the unmodifiable sequence of {@link Node}s. + */ + List<Node> getContents(); + + /** + * @return the regular successor block + */ + Block getRegularSuccessor(); + + /** + * Is this block empty (i.e., does it not contain any contents). + */ + boolean isEmpty(); + +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/RegularBlockImpl.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/RegularBlockImpl.java new file mode 100644 index 0000000000..853f2c94f0 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/RegularBlockImpl.java @@ -0,0 +1,67 @@ +package org.checkerframework.dataflow.cfg.block; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import org.checkerframework.dataflow.cfg.node.Node; + +/** + * Implementation of a regular basic block. + * + * @author Stefan Heule + * + */ +public class RegularBlockImpl extends SingleSuccessorBlockImpl implements + RegularBlock { + + /** Internal representation of the contents. */ + protected List<Node> contents; + + /** + * Initialize an empty basic block to be filled with contents and linked to + * other basic blocks later. + */ + public RegularBlockImpl() { + contents = new LinkedList<>(); + type = BlockType.REGULAR_BLOCK; + } + + /** + * Add a node to the contents of this basic block. + */ + public void addNode(Node t) { + contents.add(t); + t.setBlock(this); + } + + /** + * Add multiple nodes to the contents of this basic block. + */ + public void addNodes(List<? extends Node> ts) { + for (Node t : ts) { + addNode(t); + } + } + + @Override + public List<Node> getContents() { + return Collections.unmodifiableList(contents); + } + + @Override + public BlockImpl getRegularSuccessor() { + return successor; + } + + @Override + public String toString() { + return "RegularBlock(" + contents + ")"; + } + + @Override + public boolean isEmpty() { + return contents.isEmpty(); + } + +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/SingleSuccessorBlock.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/SingleSuccessorBlock.java new file mode 100644 index 0000000000..9afd8fd9cd --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/SingleSuccessorBlock.java @@ -0,0 +1,32 @@ +package org.checkerframework.dataflow.cfg.block; + +/*>>> +import org.checkerframework.checker.nullness.qual.Nullable; +*/ + +import org.checkerframework.dataflow.analysis.Store; + +/** + * A basic block that has at exactly one non-exceptional successor. + * + * @author Stefan Heule + * + */ +public interface SingleSuccessorBlock extends Block { + + /** + * @return the non-exceptional successor block, or {@code null} if there is + * no successor. + */ + /*@Nullable*/ Block getSuccessor(); + + /** + * @return the flow rule for information flowing from this block to its successor + */ + Store.FlowRule getFlowRule(); + + /** + * Set the flow rule for information flowing from this block to its successor. + */ + void setFlowRule(Store.FlowRule rule); +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/SingleSuccessorBlockImpl.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/SingleSuccessorBlockImpl.java new file mode 100644 index 0000000000..7e5988e2e7 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/SingleSuccessorBlockImpl.java @@ -0,0 +1,49 @@ +package org.checkerframework.dataflow.cfg.block; + +/*>>> +import org.checkerframework.checker.nullness.qual.Nullable; +*/ + +import org.checkerframework.dataflow.analysis.Store; + +/** + * Implementation of a non-special basic block. + * + * @author Stefan Heule + * + */ +public abstract class SingleSuccessorBlockImpl extends BlockImpl implements + SingleSuccessorBlock { + + /** Internal representation of the successor. */ + protected /*@Nullable*/ BlockImpl successor; + + /** + * The rule below say that EACH store at the end of a single + * successor block flow to the corresponding store of the successor. + */ + protected Store.FlowRule flowRule = Store.FlowRule.EACH_TO_EACH; + + @Override + public /*@Nullable*/ Block getSuccessor() { + return successor; + } + + /** + * Set a basic block as the successor of this block. + */ + public void setSuccessor(BlockImpl successor) { + this.successor = successor; + successor.addPredecessor(this); + } + + @Override + public Store.FlowRule getFlowRule() { + return flowRule; + } + + @Override + public void setFlowRule(Store.FlowRule rule) { + flowRule = rule; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/SpecialBlock.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/SpecialBlock.java new file mode 100644 index 0000000000..805a3b5135 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/SpecialBlock.java @@ -0,0 +1,34 @@ +package org.checkerframework.dataflow.cfg.block; + +/** + * Represents a special basic block; i.e., one of the following: + * <ul> + * <li>Entry block of a method.</li> + * <li>Regular exit block of a method.</li> + * <li>Exceptional exit block of a method.</li> + * </ul> + * + * @author Stefan Heule + * + */ +public interface SpecialBlock extends SingleSuccessorBlock { + + /** The types of special basic blocks */ + public static enum SpecialBlockType { + + /** The entry block of a method */ + ENTRY, + + /** The exit block of a method */ + EXIT, + + /** A special exit block of a method for exceptional termination */ + EXCEPTIONAL_EXIT, + } + + /** + * @return the type of this special basic block + */ + SpecialBlockType getSpecialType(); + +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/SpecialBlockImpl.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/SpecialBlockImpl.java new file mode 100644 index 0000000000..9b3f8fb8e3 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/block/SpecialBlockImpl.java @@ -0,0 +1,24 @@ +package org.checkerframework.dataflow.cfg.block; + +public class SpecialBlockImpl extends SingleSuccessorBlockImpl implements + SpecialBlock { + + /** The type of this special basic block. */ + protected SpecialBlockType specialType; + + public SpecialBlockImpl(SpecialBlockType type) { + this.specialType = type; + this.type = BlockType.SPECIAL_BLOCK; + } + + @Override + public SpecialBlockType getSpecialType() { + return specialType; + } + + @Override + public String toString() { + return "SpecialBlock(" + specialType + ")"; + } + +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/AbstractNodeVisitor.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/AbstractNodeVisitor.java new file mode 100644 index 0000000000..0f8a998efe --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/AbstractNodeVisitor.java @@ -0,0 +1,382 @@ +package org.checkerframework.dataflow.cfg.node; + + +/** + * A default implementation of the node visitor interface. The class introduces + * several 'summary' methods, that can be overridden to change the behavior of + * several related visit methods at once. An example is the + * {@code visitValueLiteral} method, that is called for every + * {@link ValueLiteralNode}. + * + * <p> + * + * This is useful to implement a visitor that performs the same operation (e.g., + * nothing) for most {@link Node}s and only has special behavior for a few. + * + * @author Stefan Heule + * + * @param <R> + * Return type of the visitor. + * @param <P> + * Parameter type of the visitor. + */ +public abstract class AbstractNodeVisitor<R, P> implements NodeVisitor<R, P> { + + abstract public R visitNode(Node n, P p); + + public R visitValueLiteral(ValueLiteralNode n, P p) { + return visitNode(n, p); + } + + // Literals + @Override + public R visitShortLiteral(ShortLiteralNode n, P p) { + return visitValueLiteral(n, p); + } + + @Override + public R visitIntegerLiteral(IntegerLiteralNode n, P p) { + return visitValueLiteral(n, p); + } + + @Override + public R visitLongLiteral(LongLiteralNode n, P p) { + return visitValueLiteral(n, p); + } + + @Override + public R visitFloatLiteral(FloatLiteralNode n, P p) { + return visitValueLiteral(n, p); + } + + @Override + public R visitDoubleLiteral(DoubleLiteralNode n, P p) { + return visitValueLiteral(n, p); + } + + @Override + public R visitBooleanLiteral(BooleanLiteralNode n, P p) { + return visitValueLiteral(n, p); + } + + @Override + public R visitCharacterLiteral(CharacterLiteralNode n, P p) { + return visitValueLiteral(n, p); + } + + @Override + public R visitStringLiteral(StringLiteralNode n, P p) { + return visitValueLiteral(n, p); + } + + @Override + public R visitNullLiteral(NullLiteralNode n, P p) { + return visitValueLiteral(n, p); + } + + // Unary operations + @Override + public R visitNumericalMinus(NumericalMinusNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitNumericalPlus(NumericalPlusNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitBitwiseComplement(BitwiseComplementNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitNullChk(NullChkNode n, P p) { + return visitNode(n, p); + } + + // Binary operations + @Override + public R visitStringConcatenate(StringConcatenateNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitNumericalAddition(NumericalAdditionNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitNumericalSubtraction(NumericalSubtractionNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitNumericalMultiplication(NumericalMultiplicationNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitIntegerDivision(IntegerDivisionNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitFloatingDivision(FloatingDivisionNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitIntegerRemainder(IntegerRemainderNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitFloatingRemainder(FloatingRemainderNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitLeftShift(LeftShiftNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitSignedRightShift(SignedRightShiftNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitUnsignedRightShift(UnsignedRightShiftNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitBitwiseAnd(BitwiseAndNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitBitwiseOr(BitwiseOrNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitBitwiseXor(BitwiseXorNode n, P p) { + return visitNode(n, p); + } + + // Compound assignments + @Override + public R visitStringConcatenateAssignment( + StringConcatenateAssignmentNode n, P p) { + return visitNode(n, p); + } + + // Comparison operations + @Override + public R visitLessThan(LessThanNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitLessThanOrEqual(LessThanOrEqualNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitGreaterThan(GreaterThanNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitGreaterThanOrEqual(GreaterThanOrEqualNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitEqualTo(EqualToNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitNotEqual(NotEqualNode n, P p) { + return visitNode(n, p); + } + + // Conditional operations + @Override + public R visitConditionalAnd(ConditionalAndNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitConditionalOr(ConditionalOrNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitConditionalNot(ConditionalNotNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitTernaryExpression(TernaryExpressionNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitAssignment(AssignmentNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitLocalVariable(LocalVariableNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitVariableDeclaration(VariableDeclarationNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitFieldAccess(FieldAccessNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitMethodAccess(MethodAccessNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitArrayAccess(ArrayAccessNode n, P p) { + return visitNode(n, p); + } + + public R visitThisLiteral(ThisLiteralNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitImplicitThisLiteral(ImplicitThisLiteralNode n, P p) { + return visitThisLiteral(n, p); + } + + @Override + public R visitExplicitThisLiteral(ExplicitThisLiteralNode n, P p) { + return visitThisLiteral(n, p); + } + + @Override + public R visitSuper(SuperNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitReturn(ReturnNode n, P p) { + return visitNode(n, p); + }; + + @Override + public R visitStringConversion(StringConversionNode n, P p) { + return visitNode(n, p); + }; + + @Override + public R visitNarrowingConversion(NarrowingConversionNode n, P p) { + return visitNode(n, p); + }; + + @Override + public R visitWideningConversion(WideningConversionNode n, P p) { + return visitNode(n, p); + }; + + @Override + public R visitInstanceOf(InstanceOfNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitTypeCast(TypeCastNode n, P p) { + return visitNode(n, p); + } + + // Statements + @Override + public R visitAssertionError(AssertionErrorNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitSynchronized(SynchronizedNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitThrow(ThrowNode n, P p) { + return visitNode(n, p); + } + + // Cases + @Override + public R visitCase(CaseNode n, P p) { + return visitNode(n, p); + } + + // Method and constructor invocations + @Override + public R visitMethodInvocation(MethodInvocationNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitObjectCreation(ObjectCreationNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitMemberReference(FunctionalInterfaceNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitArrayCreation(ArrayCreationNode n, P p) { + return visitNode(n, p); + } + + // Type, package and class names + @Override + public R visitArrayType(ArrayTypeNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitPrimitiveType(PrimitiveTypeNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitClassName(ClassNameNode n, P p) { + return visitNode(n, p); + } + + @Override + public R visitPackageName(PackageNameNode n, P p) { + return visitNode(n, p); + } + + // Parameterized types + @Override + public R visitParameterizedType(ParameterizedTypeNode n, P p) { + return visitNode(n, p); + } + + // Marker nodes + @Override + public R visitMarker(MarkerNode n, P p) { + return visitNode(n, p); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ArrayAccessNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ArrayAccessNode.java new file mode 100644 index 0000000000..c51d7c1fbf --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ArrayAccessNode.java @@ -0,0 +1,87 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.LinkedList; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.ArrayAccessTree; +import com.sun.source.tree.Tree; + +/** + * A node for an array access: + * + * <pre> + * <em>array ref</em> [ <em>index</em> ] + * </pre> + * + * We allow array accesses without corresponding AST {@link Tree}s. + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ + +public class ArrayAccessNode extends Node { + + protected Tree tree; + protected Node array; + protected Node index; + + public ArrayAccessNode(Tree t, Node array, Node index) { + super(InternalUtils.typeOf(t)); + assert t instanceof ArrayAccessTree; + this.tree = t; + this.array = array; + this.index = index; + } + + public Node getArray() { + return array; + } + + public Node getIndex() { + return index; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitArrayAccess(this, p); + } + + @Override + public String toString() { + String base = getArray().toString() + "[" + getIndex().toString() + "]"; + return base; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof ArrayAccessNode)) { + return false; + } + ArrayAccessNode other = (ArrayAccessNode) obj; + return getArray().equals(other.getArray()) + && getIndex().equals(other.getIndex()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getArray(), getIndex()); + } + + @Override + public Collection<Node> getOperands() { + LinkedList<Node> list = new LinkedList<Node>(); + list.add(getArray()); + list.add(getIndex()); + return list; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ArrayCreationNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ArrayCreationNode.java new file mode 100644 index 0000000000..4af69077d3 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ArrayCreationNode.java @@ -0,0 +1,135 @@ +package org.checkerframework.dataflow.cfg.node; + +/*>>> +import org.checkerframework.checker.nullness.qual.Nullable; +*/ + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import javax.lang.model.type.TypeMirror; + +import com.sun.source.tree.NewArrayTree; +import com.sun.source.tree.Tree; + +/** + * A node for new array creation + * + * <pre> + * <em>new type [1][2]</em> + * <em>new type [] = { expr1, expr2, ... }</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class ArrayCreationNode extends Node { + + // The tree is null when an array is created for + // variable arity method calls. + protected /*@Nullable*/ NewArrayTree tree; + protected List<Node> dimensions; + protected List<Node> initializers; + + public ArrayCreationNode(/*@Nullable*/ NewArrayTree tree, + TypeMirror type, + List<Node> dimensions, + List<Node> initializers) { + super(type); + this.tree = tree; + this.dimensions = dimensions; + this.initializers = initializers; + } + + public List<Node> getDimensions() { + return dimensions; + } + + public Node getDimension(int i) { + return dimensions.get(i); + } + + public List<Node> getInitializers() { + return initializers; + } + + public Node getInitializer(int i) { + return initializers.get(i); + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitArrayCreation(this, p); + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("new " + type); + if (!dimensions.isEmpty()) { + boolean needComma = false; + sb.append(" ("); + for (Node dim : dimensions) { + if (needComma) { + sb.append(", "); + } + sb.append(dim); + needComma = true; + } + sb.append(")"); + } + if (!initializers.isEmpty()) { + boolean needComma = false; + sb.append(" = {"); + for (Node init : initializers) { + if (needComma) { + sb.append(", "); + } + sb.append(init); + needComma = true; + } + sb.append("}"); + } + return sb.toString(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof ArrayCreationNode)) { + return false; + } + ArrayCreationNode other = (ArrayCreationNode) obj; + + return getDimensions().equals(other.getDimensions()) + && getInitializers().equals(other.getInitializers()); + } + + @Override + public int hashCode() { + int hash = 0; + for (Node dim : dimensions) { + hash = HashCodeUtils.hash(hash, dim.hashCode()); + } + for (Node init : initializers) { + hash = HashCodeUtils.hash(hash, init.hashCode()); + } + return hash; + } + + @Override + public Collection<Node> getOperands() { + LinkedList<Node> list = new LinkedList<Node>(); + list.addAll(dimensions); + list.addAll(initializers); + return list; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ArrayTypeNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ArrayTypeNode.java new file mode 100644 index 0000000000..d7e0cd7f82 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ArrayTypeNode.java @@ -0,0 +1,65 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.Collections; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.ArrayTypeTree; +import com.sun.source.tree.Tree; + +/** + * A node representing a array type used in an expression + * such as a field access + * + * <em>type</em> .class + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class ArrayTypeNode extends Node { + + protected final ArrayTypeTree tree; + + public ArrayTypeNode(ArrayTypeTree tree) { + super(InternalUtils.typeOf(tree)); + this.tree = tree; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitArrayType(this, p); + } + + @Override + public String toString() { + return tree.toString(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof ArrayTypeNode)) { + return false; + } + ArrayTypeNode other = (ArrayTypeNode) obj; + return getType().equals(other.getType()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getType()); + } + + @Override + public Collection<Node> getOperands() { + return Collections.emptyList(); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/AssertionErrorNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/AssertionErrorNode.java new file mode 100644 index 0000000000..efc066a5bf --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/AssertionErrorNode.java @@ -0,0 +1,85 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.LinkedList; + +import javax.lang.model.type.TypeMirror; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; + +/** + * A node for the {@link AssertionError} when an assertion fails. + * + * <pre> + * assert <em>condition</em> : <em>detail</em> ; + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class AssertionErrorNode extends Node { + + protected Tree tree; + protected Node condition; + protected Node detail; + + public AssertionErrorNode(Tree tree, Node condition, Node detail, TypeMirror type) { + // TODO: Find out the correct "type" for statements. + // Is it TypeKind.NONE? + super(type); + assert tree.getKind() == Kind.ASSERT; + this.tree = tree; + this.condition = condition; + this.detail = detail; + } + + public Node getCondition() { + return condition; + } + + public Node getDetail() { + return detail; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitAssertionError(this, p); + } + + @Override + public String toString() { + return "AssertionError(" + getDetail() + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof AssertionErrorNode)) { + return false; + } + AssertionErrorNode other = (AssertionErrorNode) obj; + return getCondition().equals(other.getCondition()) && + getDetail().equals(other.getDetail()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getCondition(), getDetail()); + } + + @Override + public Collection<Node> getOperands() { + LinkedList<Node> list = new LinkedList<Node>(); + list.add(getCondition()); + list.add(getDetail()); + return list; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/AssignmentContext.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/AssignmentContext.java new file mode 100644 index 0000000000..85d17e7c06 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/AssignmentContext.java @@ -0,0 +1,140 @@ +package org.checkerframework.dataflow.cfg.node; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; + +import org.checkerframework.javacutil.TreeUtils; + +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.VariableTree; + +/** + * An assignment context for a node, which represents the place to which the + * node with this context is 'assigned' to. An 'assignment' (as we use the term + * here) can occur for Java assignments, method calls (for all the actual + * parameters which get assigned to their formal parameters) or method return + * statements. + * + * <p> + * The main use of {@link AssignmentContext} is to be able to get the declared + * type of the left-hand side of the assignment for proper type-refinement. + * + * @author Stefan Heule + */ +public abstract class AssignmentContext { + + /** + * An assignment context for an assignment 'lhs = rhs'. + */ + public static class AssignmentLhsContext extends AssignmentContext { + + protected final Node node; + + public AssignmentLhsContext(Node node) { + this.node = node; + } + + @Override + public Element getElementForType() { + Tree tree = node.getTree(); + if (tree == null) { + return null; + } else if (tree instanceof ExpressionTree) { + return TreeUtils.elementFromUse((ExpressionTree) tree); + } else if (tree instanceof VariableTree) { + return TreeUtils.elementFromDeclaration((VariableTree) tree); + } else { + assert false : "unexpected tree"; + return null; + } + } + + @Override + public Tree getContextTree() { + return node.getTree(); + } + } + + /** + * An assignment context for a method parameter. + */ + public static class MethodParameterContext extends AssignmentContext { + + protected final ExecutableElement method; + protected final int paramNum; + + public MethodParameterContext(ExecutableElement method, int paramNum) { + this.method = method; + this.paramNum = paramNum; + } + + @Override + public Element getElementForType() { + return method.getParameters().get(paramNum); + } + + @Override + public Tree getContextTree() { + // TODO: what is the right assignment context? We might not have + // a tree for the invoked method. + return null; + } + } + + /** + * An assignment context for method return statements. + */ + public static class MethodReturnContext extends AssignmentContext { + + protected final ExecutableElement method; + protected final Tree ret; + + public MethodReturnContext(MethodTree method) { + this.method = TreeUtils.elementFromDeclaration(method); + this.ret = method.getReturnType(); + } + + @Override + public Element getElementForType() { + return method; + } + + @Override + public Tree getContextTree() { + return ret; + } + } + + /** + * An assignment context for lambda return statements. + */ + public static class LambdaReturnContext extends AssignmentContext { + + protected final ExecutableElement method; + + public LambdaReturnContext(ExecutableElement method) { + this.method = method; + } + + @Override + public Element getElementForType() { + return method; + } + + @Override + public Tree getContextTree() { + // TODO: what is the right assignment context? We might not have + // a tree for the invoked method. + return null; + } + } + + /** + * Returns an {@link Element} that has the type of this assignment context. + */ + public abstract Element getElementForType(); + + public abstract Tree getContextTree(); +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/AssignmentNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/AssignmentNode.java new file mode 100644 index 0000000000..d48666a1a6 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/AssignmentNode.java @@ -0,0 +1,95 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.LinkedList; + +import org.checkerframework.dataflow.cfg.node.AssignmentContext.AssignmentLhsContext; +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.AssignmentTree; +import com.sun.source.tree.CompoundAssignmentTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.UnaryTree; +import com.sun.source.tree.VariableTree; + +/** + * A node for an assignment: + * + * <pre> + * <em>variable</em> = <em>expression</em> + * <em>expression</em> . <em>field</em> = <em>expression</em> + * <em>expression</em> [ <em>index</em> ] = <em>expression</em> + * </pre> + * + * We allow assignments without corresponding AST {@link Tree}s. + * + * @author Stefan Heule + * + */ +public class AssignmentNode extends Node { + + protected Tree tree; + protected Node lhs; + protected Node rhs; + + public AssignmentNode(Tree tree, Node target, Node expression) { + super(InternalUtils.typeOf(tree)); + assert tree instanceof AssignmentTree || tree instanceof VariableTree + || tree instanceof CompoundAssignmentTree || tree instanceof UnaryTree; + assert target instanceof FieldAccessNode + || target instanceof LocalVariableNode + || target instanceof ArrayAccessNode; + this.tree = tree; + this.lhs = target; + this.rhs = expression; + rhs.setAssignmentContext(new AssignmentLhsContext(lhs)); + } + + public Node getTarget() { + return lhs; + } + + public Node getExpression() { + return rhs; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitAssignment(this, p); + } + + @Override + public String toString() { + return getTarget() + " = " + getExpression(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof AssignmentNode)) { + return false; + } + AssignmentNode other = (AssignmentNode) obj; + return getTarget().equals(other.getTarget()) + && getExpression().equals(other.getExpression()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getTarget(), getExpression()); + } + + @Override + public Collection<Node> getOperands() { + LinkedList<Node> list = new LinkedList<Node>(); + list.add(getTarget()); + list.add(getExpression()); + return list; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/BitwiseAndNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/BitwiseAndNode.java new file mode 100644 index 0000000000..82c5f5d5f8 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/BitwiseAndNode.java @@ -0,0 +1,83 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.LinkedList; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; + +/** + * A node for the bitwise or logical (single bit) and operation: + * + * <pre> + * <em>expression</em> & <em>expression</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class BitwiseAndNode extends Node { + + protected Tree tree; + protected Node left; + protected Node right; + + public BitwiseAndNode(Tree tree, Node left, Node right) { + super(InternalUtils.typeOf(tree)); + assert tree.getKind() == Kind.AND; + this.tree = tree; + this.left = left; + this.right = right; + } + + public Node getLeftOperand() { + return left; + } + + public Node getRightOperand() { + return right; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitBitwiseAnd(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " & " + getRightOperand() + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof BitwiseAndNode)) { + return false; + } + BitwiseAndNode other = (BitwiseAndNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getLeftOperand(), getRightOperand()); + } + + @Override + public Collection<Node> getOperands() { + LinkedList<Node> list = new LinkedList<Node>(); + list.add(getLeftOperand()); + list.add(getRightOperand()); + return list; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/BitwiseComplementNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/BitwiseComplementNode.java new file mode 100644 index 0000000000..1b8ad73175 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/BitwiseComplementNode.java @@ -0,0 +1,73 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.Collections; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; + +/** + * A node for the bitwise complement operation: + * + * <pre> + * ~ <em>expression</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class BitwiseComplementNode extends Node { + + protected Tree tree; + protected Node operand; + + public BitwiseComplementNode(Tree tree, Node operand) { + super(InternalUtils.typeOf(tree)); + assert tree.getKind() == Kind.BITWISE_COMPLEMENT; + this.tree = tree; + this.operand = operand; + } + + public Node getOperand() { + return operand; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitBitwiseComplement(this, p); + } + + @Override + public String toString() { + return "(~ " + getOperand() + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof BitwiseComplementNode)) { + return false; + } + BitwiseComplementNode other = (BitwiseComplementNode) obj; + return getOperand().equals(other.getOperand()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getOperand()); + } + + @Override + public Collection<Node> getOperands() { + return Collections.singletonList(getOperand()); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/BitwiseOrNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/BitwiseOrNode.java new file mode 100644 index 0000000000..f9763c9b14 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/BitwiseOrNode.java @@ -0,0 +1,83 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.LinkedList; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; + +/** + * A node for the bitwise or logical (single bit) or operation: + * + * <pre> + * <em>expression</em> | <em>expression</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class BitwiseOrNode extends Node { + + protected Tree tree; + protected Node left; + protected Node right; + + public BitwiseOrNode(Tree tree, Node left, Node right) { + super(InternalUtils.typeOf(tree)); + assert tree.getKind() == Kind.OR; + this.tree = tree; + this.left = left; + this.right = right; + } + + public Node getLeftOperand() { + return left; + } + + public Node getRightOperand() { + return right; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitBitwiseOr(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " | " + getRightOperand() + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof BitwiseOrNode)) { + return false; + } + BitwiseOrNode other = (BitwiseOrNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getLeftOperand(), getRightOperand()); + } + + @Override + public Collection<Node> getOperands() { + LinkedList<Node> list = new LinkedList<Node>(); + list.add(getLeftOperand()); + list.add(getRightOperand()); + return list; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/BitwiseXorNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/BitwiseXorNode.java new file mode 100644 index 0000000000..848b2f17ec --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/BitwiseXorNode.java @@ -0,0 +1,83 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.LinkedList; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; + +/** + * A node for the bitwise or logical (single bit) xor operation: + * + * <pre> + * <em>expression</em> ^ <em>expression</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class BitwiseXorNode extends Node { + + protected Tree tree; + protected Node left; + protected Node right; + + public BitwiseXorNode(Tree tree, Node left, Node right) { + super(InternalUtils.typeOf(tree)); + assert tree.getKind() == Kind.XOR; + this.tree = tree; + this.left = left; + this.right = right; + } + + public Node getLeftOperand() { + return left; + } + + public Node getRightOperand() { + return right; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitBitwiseXor(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " ^ " + getRightOperand() + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof BitwiseXorNode)) { + return false; + } + BitwiseXorNode other = (BitwiseXorNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getLeftOperand(), getRightOperand()); + } + + @Override + public Collection<Node> getOperands() { + LinkedList<Node> list = new LinkedList<Node>(); + list.add(getLeftOperand()); + list.add(getRightOperand()); + return list; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/BooleanLiteralNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/BooleanLiteralNode.java new file mode 100644 index 0000000000..8368c87793 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/BooleanLiteralNode.java @@ -0,0 +1,51 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.Collections; + +import com.sun.source.tree.LiteralTree; +import com.sun.source.tree.Tree; + +/** + * A node for a boolean literal: + * + * <pre> + * <em>true</em> + * <em>false</em> + * </pre> + * + * @author Stefan Heule + * + */ +public class BooleanLiteralNode extends ValueLiteralNode { + + public BooleanLiteralNode(LiteralTree t) { + super(t); + assert t.getKind().equals(Tree.Kind.BOOLEAN_LITERAL); + } + + @Override + public Boolean getValue() { + return (Boolean) tree.getValue(); + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitBooleanLiteral(this, p); + } + + @Override + public boolean equals(Object obj) { + // test that obj is a BooleanLiteralNode + if (!(obj instanceof BooleanLiteralNode)) { + return false; + } + // super method compares values + return super.equals(obj); + } + + @Override + public Collection<Node> getOperands() { + return Collections.emptyList(); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/CaseNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/CaseNode.java new file mode 100644 index 0000000000..f5a3d460a5 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/CaseNode.java @@ -0,0 +1,86 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.LinkedList; + +import javax.lang.model.type.TypeKind; +import javax.lang.model.util.Types; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import com.sun.source.tree.CaseTree; +import com.sun.source.tree.Tree.Kind; + +/** + * A node for a case in a switch statement. Although + * a case has no abstract value, it can imply facts about + * the abstract values of its operands. + * + * <pre> + * case <em>constant</em>: + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class CaseNode extends Node { + + protected CaseTree tree; + protected Node switchExpr; + protected Node caseExpr; + + public CaseNode(CaseTree tree, Node switchExpr, Node caseExpr, Types types) { + super(types.getNoType(TypeKind.NONE)); + assert tree.getKind().equals(Kind.CASE); + this.tree = tree; + this.switchExpr = switchExpr; + this.caseExpr = caseExpr; + } + + public Node getSwitchOperand() { + return switchExpr; + } + + public Node getCaseOperand() { + return caseExpr; + } + + @Override + public CaseTree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitCase(this, p); + } + + @Override + public String toString() { + return "case " + getCaseOperand() + ":"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof CaseNode)) { + return false; + } + CaseNode other = (CaseNode) obj; + return getSwitchOperand().equals(other.getSwitchOperand()) + && getCaseOperand().equals(other.getCaseOperand()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getSwitchOperand(), getCaseOperand()); + } + + @Override + public Collection<Node> getOperands() { + LinkedList<Node> list = new LinkedList<Node>(); + list.add(getSwitchOperand()); + list.add(getCaseOperand()); + return list; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/CharacterLiteralNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/CharacterLiteralNode.java new file mode 100644 index 0000000000..979a4177d8 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/CharacterLiteralNode.java @@ -0,0 +1,53 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.Collections; + +import com.sun.source.tree.LiteralTree; +import com.sun.source.tree.Tree; + +/** + * A node for a character literal. For example: + * + * <pre> + * <em>'a'</em> + * <em>'\t'</em> + * <em>'\u03a9'</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class CharacterLiteralNode extends ValueLiteralNode { + + public CharacterLiteralNode(LiteralTree t) { + super(t); + assert t.getKind().equals(Tree.Kind.CHAR_LITERAL); + } + + @Override + public Character getValue() { + return (Character) tree.getValue(); + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitCharacterLiteral(this, p); + } + + @Override + public boolean equals(Object obj) { + // test that obj is a CharacterLiteralNode + if (obj == null || !(obj instanceof CharacterLiteralNode)) { + return false; + } + // super method compares values + return super.equals(obj); + } + + @Override + public Collection<Node> getOperands() { + return Collections.emptyList(); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ClassNameNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ClassNameNode.java new file mode 100644 index 0000000000..af1a974b1f --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ClassNameNode.java @@ -0,0 +1,117 @@ +package org.checkerframework.dataflow.cfg.node; + +/*>>> +import org.checkerframework.checker.nullness.qual.Nullable; +*/ + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; +import org.checkerframework.javacutil.TreeUtils; + +import java.util.Collection; +import java.util.Collections; + +import javax.lang.model.element.Element; + +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.MemberSelectTree; +import com.sun.source.tree.Tree; + +/** + * A node representing a class name used in an expression + * such as a static method invocation. + * + * parent.<em>class</em> .forName(...) + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class ClassNameNode extends Node { + + protected final Tree tree; + /** The class named by this node */ + protected final Element element; + + /** The parent name, if any. */ + protected final /*@Nullable*/ Node parent; + + public ClassNameNode(IdentifierTree tree) { + super(InternalUtils.typeOf(tree)); + assert tree.getKind() == Tree.Kind.IDENTIFIER; + this.tree = tree; + this.element = TreeUtils.elementFromUse(tree); + this.parent = null; + } + + public ClassNameNode(ClassTree tree) { + super(InternalUtils.typeOf(tree)); + assert tree.getKind() == Tree.Kind.CLASS || tree.getKind() == Tree.Kind.ENUM || tree.getKind() == Tree.Kind.INTERFACE || tree.getKind() == Tree.Kind.ANNOTATION_TYPE; + this.tree = tree; + this.element = TreeUtils.elementFromDeclaration(tree); + this.parent = null; + } + + public ClassNameNode(MemberSelectTree tree, Node parent) { + super(InternalUtils.typeOf(tree)); + this.tree = tree; + this.element = TreeUtils.elementFromUse(tree); + this.parent = parent; + } + + public Element getElement() { + return element; + } + + public Node getParent() { + return parent; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitClassName(this, p); + } + + @Override + public String toString() { + return getElement().getSimpleName().toString(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof ClassNameNode)) { + return false; + } + ClassNameNode other = (ClassNameNode) obj; + if (getParent() == null) { + return other.getParent() == null + && getElement().equals(other.getElement()); + } else { + return getParent().equals(other.getParent()) + && getElement().equals(other.getElement()); + } + } + + @Override + public int hashCode() { + if (parent == null) { + return HashCodeUtils.hash(getElement()); + } + return HashCodeUtils.hash(getElement(), getParent()); + } + + @Override + public Collection<Node> getOperands() { + if (parent == null) { + return Collections.emptyList(); + } + return Collections.singleton(parent); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ConditionalAndNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ConditionalAndNode.java new file mode 100644 index 0000000000..dfa9f99c27 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ConditionalAndNode.java @@ -0,0 +1,83 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.LinkedList; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.BinaryTree; +import com.sun.source.tree.Tree.Kind; + +/** + * A node for a conditional and expression: + * + * <pre> + * <em>expression</em> && <em>expression</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class ConditionalAndNode extends Node { + + protected BinaryTree tree; + protected Node lhs; + protected Node rhs; + + public ConditionalAndNode(BinaryTree tree, Node lhs, Node rhs) { + super(InternalUtils.typeOf(tree)); + assert tree.getKind().equals(Kind.CONDITIONAL_AND); + this.tree = tree; + this.lhs = lhs; + this.rhs = rhs; + } + + public Node getLeftOperand() { + return lhs; + } + + public Node getRightOperand() { + return rhs; + } + + @Override + public BinaryTree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitConditionalAnd(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " && " + getRightOperand() + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof ConditionalAndNode)) { + return false; + } + ConditionalAndNode other = (ConditionalAndNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getLeftOperand(), getRightOperand()); + } + + @Override + public Collection<Node> getOperands() { + LinkedList<Node> list = new LinkedList<Node>(); + list.add(getLeftOperand()); + list.add(getRightOperand()); + return list; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ConditionalNotNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ConditionalNotNode.java new file mode 100644 index 0000000000..5143c69c50 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ConditionalNotNode.java @@ -0,0 +1,73 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.Collections; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.Tree.Kind; +import com.sun.source.tree.UnaryTree; + +/** + * A node for a conditional not expression: + * + * <pre> + * ! <em>expression</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class ConditionalNotNode extends Node { + + protected UnaryTree tree; + protected Node operand; + + public ConditionalNotNode(UnaryTree tree, Node operand) { + super(InternalUtils.typeOf(tree)); + assert tree.getKind().equals(Kind.LOGICAL_COMPLEMENT); + this.tree = tree; + this.operand = operand; + } + + public Node getOperand() { + return operand; + } + + @Override + public UnaryTree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitConditionalNot(this, p); + } + + @Override + public String toString() { + return "(!" + getOperand() + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof ConditionalNotNode)) { + return false; + } + ConditionalNotNode other = (ConditionalNotNode) obj; + return getOperand().equals(other.getOperand()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getOperand()); + } + + @Override + public Collection<Node> getOperands() { + return Collections.singletonList(getOperand()); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ConditionalOrNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ConditionalOrNode.java new file mode 100644 index 0000000000..2da33fadf6 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ConditionalOrNode.java @@ -0,0 +1,82 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.LinkedList; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.BinaryTree; +import com.sun.source.tree.Tree.Kind; + +/** + * A node for a conditional or expression: + * + * <pre> + * <em>expression</em> || <em>expression</em> + * </pre> + * + * @author Stefan Heule + * + */ +public class ConditionalOrNode extends Node { + + protected BinaryTree tree; + protected Node lhs; + protected Node rhs; + + public ConditionalOrNode(BinaryTree tree, Node lhs, Node rhs) { + super(InternalUtils.typeOf(tree)); + assert tree.getKind().equals(Kind.CONDITIONAL_OR); + this.tree = tree; + this.lhs = lhs; + this.rhs = rhs; + } + + public Node getLeftOperand() { + return lhs; + } + + public Node getRightOperand() { + return rhs; + } + + @Override + public BinaryTree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitConditionalOr(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " || " + getRightOperand() + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof ConditionalOrNode)) { + return false; + } + ConditionalOrNode other = (ConditionalOrNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getLeftOperand(), getRightOperand()); + } + + @Override + public Collection<Node> getOperands() { + LinkedList<Node> list = new LinkedList<Node>(); + list.add(getLeftOperand()); + list.add(getRightOperand()); + return list; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/DoubleLiteralNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/DoubleLiteralNode.java new file mode 100644 index 0000000000..855a95a7b5 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/DoubleLiteralNode.java @@ -0,0 +1,52 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.Collections; + +import com.sun.source.tree.LiteralTree; +import com.sun.source.tree.Tree; + +/** + * A node for a double literal. For example: + * + * <pre> + * <em>-9.</em> + * <em>3.14159D</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class DoubleLiteralNode extends ValueLiteralNode { + + public DoubleLiteralNode(LiteralTree t) { + super(t); + assert t.getKind().equals(Tree.Kind.DOUBLE_LITERAL); + } + + @Override + public Double getValue() { + return (Double) tree.getValue(); + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitDoubleLiteral(this, p); + } + + @Override + public boolean equals(Object obj) { + // test that obj is a DoubleLiteralNode + if (obj == null || !(obj instanceof DoubleLiteralNode)) { + return false; + } + // super method compares values + return super.equals(obj); + } + + @Override + public Collection<Node> getOperands() { + return Collections.emptyList(); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/EqualToNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/EqualToNode.java new file mode 100644 index 0000000000..ed56d4ab68 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/EqualToNode.java @@ -0,0 +1,82 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.LinkedList; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.BinaryTree; +import com.sun.source.tree.Tree.Kind; + +/** + * A node for an equality check: + * + * <pre> + * <em>expression</em> == <em>expression</em> + * </pre> + * + * @author Stefan Heule + * + */ +public class EqualToNode extends Node { + + protected BinaryTree tree; + protected Node lhs; + protected Node rhs; + + public EqualToNode(BinaryTree tree, Node lhs, Node rhs) { + super(InternalUtils.typeOf(tree)); + assert tree.getKind().equals(Kind.EQUAL_TO); + this.tree = tree; + this.lhs = lhs; + this.rhs = rhs; + } + + public Node getLeftOperand() { + return lhs; + } + + public Node getRightOperand() { + return rhs; + } + + @Override + public BinaryTree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitEqualTo(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " == " + getRightOperand() + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof EqualToNode)) { + return false; + } + EqualToNode other = (EqualToNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getLeftOperand(), getRightOperand()); + } + + @Override + public Collection<Node> getOperands() { + LinkedList<Node> list = new LinkedList<Node>(); + list.add(getLeftOperand()); + list.add(getRightOperand()); + return list; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ExplicitThisLiteralNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ExplicitThisLiteralNode.java new file mode 100644 index 0000000000..0c1069843c --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ExplicitThisLiteralNode.java @@ -0,0 +1,44 @@ +package org.checkerframework.dataflow.cfg.node; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.Tree; + +/** + * A node for a reference to 'this'. + * + * <pre> + * <em>this</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class ExplicitThisLiteralNode extends ThisLiteralNode { + + protected Tree tree; + + public ExplicitThisLiteralNode(Tree t) { + super(InternalUtils.typeOf(t)); + assert t instanceof IdentifierTree + && ((IdentifierTree) t).getName().contentEquals("this"); + tree = t; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitExplicitThisLiteral(this, p); + } + + @Override + public String toString() { + return getName(); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/FieldAccessNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/FieldAccessNode.java new file mode 100644 index 0000000000..db5bf51c5b --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/FieldAccessNode.java @@ -0,0 +1,113 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.Collections; + +import javax.lang.model.element.VariableElement; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.ElementUtils; +import org.checkerframework.javacutil.InternalUtils; +import org.checkerframework.javacutil.TreeUtils; + +import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.MemberSelectTree; +import com.sun.source.tree.Tree; + +/** + * A node for a field access, including a method accesses: + * + * <pre> + * <em>expression</em> . <em>field</em> + * </pre> + * + * @author Stefan Heule + * + */ +public class FieldAccessNode extends Node { + + protected Tree tree; + protected VariableElement element; + protected String field; + protected Node receiver; + + // TODO: add method to get modifiers (static, access level, ..) + + public FieldAccessNode(Tree tree, Node receiver) { + super(InternalUtils.typeOf(tree)); + assert TreeUtils.isFieldAccess(tree); + this.tree = tree; + this.receiver = receiver; + this.field = TreeUtils.getFieldName(tree); + + if (tree instanceof MemberSelectTree) { + this.element = (VariableElement) TreeUtils.elementFromUse((MemberSelectTree) tree); + } else { + assert tree instanceof IdentifierTree; + this.element = (VariableElement) TreeUtils.elementFromUse((IdentifierTree) tree); + } + } + + public FieldAccessNode(Tree tree, VariableElement element, Node receiver) { + super(element.asType()); + this.tree = tree; + this.element = element; + this.receiver = receiver; + this.field = element.getSimpleName().toString(); + } + + public VariableElement getElement() { + return element; + } + + public Node getReceiver() { + return receiver; + } + + public String getFieldName() { + return field; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitFieldAccess(this, p); + } + + @Override + public String toString() { + return getReceiver() + "." + field; + } + + /** + * Is this a static field? + */ + public boolean isStatic() { + return ElementUtils.isStatic(getElement()); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof FieldAccessNode)) { + return false; + } + FieldAccessNode other = (FieldAccessNode) obj; + return getReceiver().equals(other.getReceiver()) + && getFieldName().equals(other.getFieldName()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getReceiver(), getFieldName()); + } + + @Override + public Collection<Node> getOperands() { + return Collections.singletonList(receiver); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/FloatLiteralNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/FloatLiteralNode.java new file mode 100644 index 0000000000..518c974868 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/FloatLiteralNode.java @@ -0,0 +1,52 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.Collections; + +import com.sun.source.tree.LiteralTree; +import com.sun.source.tree.Tree; + +/** + * A node for a float literal. For example: + * + * <pre> + * <em>8.0f</em> + * <em>6.022137e+23F</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class FloatLiteralNode extends ValueLiteralNode { + + public FloatLiteralNode(LiteralTree t) { + super(t); + assert t.getKind().equals(Tree.Kind.FLOAT_LITERAL); + } + + @Override + public Float getValue() { + return (Float) tree.getValue(); + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitFloatLiteral(this, p); + } + + @Override + public boolean equals(Object obj) { + // test that obj is a FloatLiteralNode + if (obj == null || !(obj instanceof FloatLiteralNode)) { + return false; + } + // super method compares values + return super.equals(obj); + } + + @Override + public Collection<Node> getOperands() { + return Collections.emptyList(); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/FloatingDivisionNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/FloatingDivisionNode.java new file mode 100644 index 0000000000..cd495b7b01 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/FloatingDivisionNode.java @@ -0,0 +1,83 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.LinkedList; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; + +/** + * A node for the floating-point division: + * + * <pre> + * <em>expression</em> / <em>expression</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class FloatingDivisionNode extends Node { + + protected Tree tree; + protected Node left; + protected Node right; + + public FloatingDivisionNode(Tree tree, Node left, Node right) { + super(InternalUtils.typeOf(tree)); + assert tree.getKind() == Kind.DIVIDE; + this.tree = tree; + this.left = left; + this.right = right; + } + + public Node getLeftOperand() { + return left; + } + + public Node getRightOperand() { + return right; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitFloatingDivision(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " / " + getRightOperand() + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof FloatingDivisionNode)) { + return false; + } + FloatingDivisionNode other = (FloatingDivisionNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getLeftOperand(), getRightOperand()); + } + + @Override + public Collection<Node> getOperands() { + LinkedList<Node> list = new LinkedList<Node>(); + list.add(getLeftOperand()); + list.add(getRightOperand()); + return list; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/FloatingRemainderNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/FloatingRemainderNode.java new file mode 100644 index 0000000000..d3b3caa0ab --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/FloatingRemainderNode.java @@ -0,0 +1,83 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.LinkedList; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; + +/** + * A node for the floating-point remainder: + * + * <pre> + * <em>expression</em> % <em>expression</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class FloatingRemainderNode extends Node { + + protected Tree tree; + protected Node left; + protected Node right; + + public FloatingRemainderNode(Tree tree, Node left, Node right) { + super(InternalUtils.typeOf(tree)); + assert tree.getKind() == Kind.REMAINDER; + this.tree = tree; + this.left = left; + this.right = right; + } + + public Node getLeftOperand() { + return left; + } + + public Node getRightOperand() { + return right; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitFloatingRemainder(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " % " + getRightOperand() + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof FloatingRemainderNode)) { + return false; + } + FloatingRemainderNode other = (FloatingRemainderNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getLeftOperand(), getRightOperand()); + } + + @Override + public Collection<Node> getOperands() { + LinkedList<Node> list = new LinkedList<Node>(); + list.add(getLeftOperand()); + list.add(getRightOperand()); + return list; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/FunctionalInterfaceNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/FunctionalInterfaceNode.java new file mode 100644 index 0000000000..5446a0584c --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/FunctionalInterfaceNode.java @@ -0,0 +1,93 @@ +package org.checkerframework.dataflow.cfg.node; + +import org.checkerframework.javacutil.ErrorReporter; +import org.checkerframework.javacutil.InternalUtils; + +import java.util.Collection; +import java.util.LinkedList; + +import com.sun.source.tree.LambdaExpressionTree; +import com.sun.source.tree.MemberReferenceTree; +import com.sun.source.tree.Tree; + +/** + * A node for member references and lambdas. + * + * The {@link Node#type} of a FunctionalInterfaceNode is determined by the + * assignment context the member reference or lambda is used in. + * + * <pre> + * <em>FunctionalInterface func = param1, param2, ... → statement</em> + * </pre> + * + * <pre> + * <em>FunctionalInterface func = param1, param2, ... → { ... }</em> + * </pre> + * + * <pre> + * <em>FunctionalInterface func = member reference</em> + * </pre> + * + * @author David + * + */ +public class FunctionalInterfaceNode extends Node { + + protected Tree tree; + + public FunctionalInterfaceNode(MemberReferenceTree tree) { + super(InternalUtils.typeOf(tree)); + this.tree = tree; + } + + public FunctionalInterfaceNode(LambdaExpressionTree tree) { + super(InternalUtils.typeOf(tree)); + this.tree = tree; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitMemberReference(this, p); + } + + @Override + public String toString() { + if (tree instanceof LambdaExpressionTree) { + return "FunctionalInterfaceNode:" + ((LambdaExpressionTree) tree).getBodyKind(); + } else if (tree instanceof MemberReferenceTree) { + return "FunctionalInterfaceNode:" + ((MemberReferenceTree) tree).getName(); + } else { + // This should never happen. + ErrorReporter.errorAbort("Invalid tree in FunctionalInterfaceNode"); + return null; // Dead code + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + FunctionalInterfaceNode that = (FunctionalInterfaceNode) o; + + if (tree != null ? !tree.equals(that.tree) : that.tree != null) return false; + + return true; + } + + @Override + public int hashCode() { + return tree != null ? tree.hashCode() : 0; + } + + @Override + public Collection<Node> getOperands() { + LinkedList<Node> list = new LinkedList<Node>(); + return list; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/GreaterThanNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/GreaterThanNode.java new file mode 100644 index 0000000000..7e51ccd115 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/GreaterThanNode.java @@ -0,0 +1,83 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.LinkedList; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; + +/** + * A node for the greater than comparison: + * + * <pre> + * <em>expression</em> > <em>expression</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class GreaterThanNode extends Node { + + protected Tree tree; + protected Node left; + protected Node right; + + public GreaterThanNode(Tree tree, Node left, Node right) { + super(InternalUtils.typeOf(tree)); + assert tree.getKind() == Kind.GREATER_THAN; + this.tree = tree; + this.left = left; + this.right = right; + } + + public Node getLeftOperand() { + return left; + } + + public Node getRightOperand() { + return right; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitGreaterThan(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " > " + getRightOperand() + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof GreaterThanNode)) { + return false; + } + GreaterThanNode other = (GreaterThanNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getLeftOperand(), getRightOperand()); + } + + @Override + public Collection<Node> getOperands() { + LinkedList<Node> list = new LinkedList<Node>(); + list.add(getLeftOperand()); + list.add(getRightOperand()); + return list; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/GreaterThanOrEqualNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/GreaterThanOrEqualNode.java new file mode 100644 index 0000000000..e23c824512 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/GreaterThanOrEqualNode.java @@ -0,0 +1,83 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.LinkedList; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; + +/** + * A node for the greater than or equal comparison: + * + * <pre> + * <em>expression</em> >= <em>expression</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class GreaterThanOrEqualNode extends Node { + + protected Tree tree; + protected Node left; + protected Node right; + + public GreaterThanOrEqualNode(Tree tree, Node left, Node right) { + super(InternalUtils.typeOf(tree)); + assert tree.getKind() == Kind.GREATER_THAN_EQUAL; + this.tree = tree; + this.left = left; + this.right = right; + } + + public Node getLeftOperand() { + return left; + } + + public Node getRightOperand() { + return right; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitGreaterThanOrEqual(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " >= " + getRightOperand() + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof GreaterThanOrEqualNode)) { + return false; + } + GreaterThanOrEqualNode other = (GreaterThanOrEqualNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getLeftOperand(), getRightOperand()); + } + + @Override + public Collection<Node> getOperands() { + LinkedList<Node> list = new LinkedList<Node>(); + list.add(getLeftOperand()); + list.add(getRightOperand()); + return list; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ImplicitThisLiteralNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ImplicitThisLiteralNode.java new file mode 100644 index 0000000000..c2ba16f18f --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ImplicitThisLiteralNode.java @@ -0,0 +1,33 @@ +package org.checkerframework.dataflow.cfg.node; + +import javax.lang.model.type.TypeMirror; + +import com.sun.source.tree.Tree; + +/** + * A node to model the implicit {@code this}, e.g., in a field access. + * + * @author Stefan Heule + * + */ +public class ImplicitThisLiteralNode extends ThisLiteralNode { + + public ImplicitThisLiteralNode(TypeMirror type) { + super(type); + } + + @Override + public Tree getTree() { + return null; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitImplicitThisLiteral(this, p); + } + + @Override + public String toString() { + return "(" + getName() + ")"; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/InstanceOfNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/InstanceOfNode.java new file mode 100644 index 0000000000..2f49436da2 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/InstanceOfNode.java @@ -0,0 +1,92 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.Collections; + +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import com.sun.source.tree.InstanceOfTree; +import com.sun.source.tree.Tree; + +/** + * A node for the instanceof operator: + * + * <em>x</em> instanceof <em>Point</em> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class InstanceOfNode extends Node { + + /** The value being tested. */ + protected Node operand; + + /** The reference type being tested against. */ + protected TypeMirror refType; + + /** The tree associated with this node. */ + protected final InstanceOfTree tree; + + public InstanceOfNode(Tree tree, Node operand, TypeMirror refType, Types types) { + super(types.getPrimitiveType(TypeKind.BOOLEAN)); + assert tree.getKind() == Tree.Kind.INSTANCE_OF; + this.tree = (InstanceOfTree) tree; + this.operand = operand; + this.refType = refType; + } + + public Node getOperand() { + return operand; + } + + @Override + public TypeMirror getType() { + return type; + } + + public TypeMirror getRefType() { + return refType; + } + + @Override + public InstanceOfTree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitInstanceOf(this, p); + } + + @Override + public String toString() { + return "(" + getOperand() + " instanceof " + getRefType() + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof InstanceOfNode)) { + return false; + } + InstanceOfNode other = (InstanceOfNode) obj; + // TODO: TypeMirror.equals may be too restrictive. + // Check whether Types.isSameType is the better comparison. + return getOperand().equals(other.getOperand()) + && getRefType().equals(other.getRefType()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getOperand()); + } + + @Override + public Collection<Node> getOperands() { + return Collections.singletonList(getOperand()); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/IntegerDivisionNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/IntegerDivisionNode.java new file mode 100644 index 0000000000..0b5701413b --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/IntegerDivisionNode.java @@ -0,0 +1,83 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.LinkedList; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; + +/** + * A node for the integer division: + * + * <pre> + * <em>expression</em> / <em>expression</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class IntegerDivisionNode extends Node { + + protected Tree tree; + protected Node left; + protected Node right; + + public IntegerDivisionNode(Tree tree, Node left, Node right) { + super(InternalUtils.typeOf(tree)); + assert tree.getKind() == Kind.DIVIDE; + this.tree = tree; + this.left = left; + this.right = right; + } + + public Node getLeftOperand() { + return left; + } + + public Node getRightOperand() { + return right; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitIntegerDivision(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " / " + getRightOperand() + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof IntegerDivisionNode)) { + return false; + } + IntegerDivisionNode other = (IntegerDivisionNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getLeftOperand(), getRightOperand()); + } + + @Override + public Collection<Node> getOperands() { + LinkedList<Node> list = new LinkedList<Node>(); + list.add(getLeftOperand()); + list.add(getRightOperand()); + return list; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/IntegerLiteralNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/IntegerLiteralNode.java new file mode 100644 index 0000000000..b23a9cb512 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/IntegerLiteralNode.java @@ -0,0 +1,53 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.Collections; + +import com.sun.source.tree.LiteralTree; +import com.sun.source.tree.Tree; + +/** + * A node for an integer literal. For example: + * + * <pre> + * <em>42</em> + * </pre> + * + * @author Stefan Heule + * + */ +public class IntegerLiteralNode extends ValueLiteralNode { + + int value; + + public IntegerLiteralNode(LiteralTree t) { + super(t); + assert t.getKind().equals(Tree.Kind.INT_LITERAL); + value = (Integer) tree.getValue(); + } + + @Override + public Integer getValue() { + return value; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitIntegerLiteral(this, p); + } + + @Override + public boolean equals(Object obj) { + // test that obj is a IntegerLiteralNode + if (!(obj instanceof IntegerLiteralNode)) { + return false; + } + // super method compares values + return super.equals(obj); + } + + @Override + public Collection<Node> getOperands() { + return Collections.emptyList(); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/IntegerRemainderNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/IntegerRemainderNode.java new file mode 100644 index 0000000000..39021cd2ab --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/IntegerRemainderNode.java @@ -0,0 +1,83 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.LinkedList; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; + +/** + * A node for the integer remainder: + * + * <pre> + * <em>expression</em> % <em>expression</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class IntegerRemainderNode extends Node { + + protected Tree tree; + protected Node left; + protected Node right; + + public IntegerRemainderNode(Tree tree, Node left, Node right) { + super(InternalUtils.typeOf(tree)); + assert tree.getKind() == Kind.REMAINDER; + this.tree = tree; + this.left = left; + this.right = right; + } + + public Node getLeftOperand() { + return left; + } + + public Node getRightOperand() { + return right; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitIntegerRemainder(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " % " + getRightOperand() + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof IntegerRemainderNode)) { + return false; + } + IntegerRemainderNode other = (IntegerRemainderNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getLeftOperand(), getRightOperand()); + } + + @Override + public Collection<Node> getOperands() { + LinkedList<Node> list = new LinkedList<Node>(); + list.add(getLeftOperand()); + list.add(getRightOperand()); + return list; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/LeftShiftNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/LeftShiftNode.java new file mode 100644 index 0000000000..60652e6100 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/LeftShiftNode.java @@ -0,0 +1,83 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.LinkedList; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; + +/** + * A node for bitwise left shift operations: + * + * <pre> + * <em>expression</em> << <em>expression</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class LeftShiftNode extends Node { + + protected Tree tree; + protected Node left; + protected Node right; + + public LeftShiftNode(Tree tree, Node left, Node right) { + super(InternalUtils.typeOf(tree)); + assert tree.getKind() == Kind.LEFT_SHIFT; + this.tree = tree; + this.left = left; + this.right = right; + } + + public Node getLeftOperand() { + return left; + } + + public Node getRightOperand() { + return right; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitLeftShift(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " << " + getRightOperand() + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof LeftShiftNode)) { + return false; + } + LeftShiftNode other = (LeftShiftNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getLeftOperand(), getRightOperand()); + } + + @Override + public Collection<Node> getOperands() { + LinkedList<Node> list = new LinkedList<Node>(); + list.add(getLeftOperand()); + list.add(getRightOperand()); + return list; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/LessThanNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/LessThanNode.java new file mode 100644 index 0000000000..a8dd5a18b4 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/LessThanNode.java @@ -0,0 +1,85 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.LinkedList; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; + +/** + * A node for the less than comparison: + * + * <pre> + * <em>expression</em> < <em>expression</em> + * </pre> + * + * We allow less than nodes without corresponding AST {@link Tree}s. + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class LessThanNode extends Node { + + protected Tree tree; + protected Node left; + protected Node right; + + public LessThanNode(Tree tree, Node left, Node right) { + super(InternalUtils.typeOf(tree)); + assert tree.getKind() == Kind.LESS_THAN; + this.tree = tree; + this.left = left; + this.right = right; + } + + public Node getLeftOperand() { + return left; + } + + public Node getRightOperand() { + return right; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitLessThan(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " < " + getRightOperand() + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof LessThanNode)) { + return false; + } + LessThanNode other = (LessThanNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getLeftOperand(), getRightOperand()); + } + + @Override + public Collection<Node> getOperands() { + LinkedList<Node> list = new LinkedList<Node>(); + list.add(getLeftOperand()); + list.add(getRightOperand()); + return list; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/LessThanOrEqualNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/LessThanOrEqualNode.java new file mode 100644 index 0000000000..4944ab66a0 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/LessThanOrEqualNode.java @@ -0,0 +1,83 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.LinkedList; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; + +/** + * A node for the less than or equal comparison: + * + * <pre> + * <em>expression</em> <= <em>expression</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class LessThanOrEqualNode extends Node { + + protected Tree tree; + protected Node left; + protected Node right; + + public LessThanOrEqualNode(Tree tree, Node left, Node right) { + super(InternalUtils.typeOf(tree)); + assert tree.getKind() == Kind.LESS_THAN_EQUAL; + this.tree = tree; + this.left = left; + this.right = right; + } + + public Node getLeftOperand() { + return left; + } + + public Node getRightOperand() { + return right; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitLessThanOrEqual(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " <= " + getRightOperand() + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof LessThanOrEqualNode)) { + return false; + } + LessThanOrEqualNode other = (LessThanOrEqualNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getLeftOperand(), getRightOperand()); + } + + @Override + public Collection<Node> getOperands() { + LinkedList<Node> list = new LinkedList<Node>(); + list.add(getLeftOperand()); + list.add(getRightOperand()); + return list; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/LocalVariableNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/LocalVariableNode.java new file mode 100644 index 0000000000..bc31cf72ee --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/LocalVariableNode.java @@ -0,0 +1,106 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.Collections; + +import javax.lang.model.element.Element; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; +import org.checkerframework.javacutil.TreeUtils; + +import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.VariableTree; + +/** + * A node for a local variable or a parameter: + * + * <pre> + * <em>identifier</em> + * </pre> + * + * We allow local variable uses introduced by the {@link org.checkerframework.dataflow.cfg.CFGBuilder} without + * corresponding AST {@link Tree}s. + * + * @author Stefan Heule + * + */ +// TODO: don't use for parameters, as they don't have a tree +public class LocalVariableNode extends Node { + + protected Tree tree; + protected Node receiver; + + public LocalVariableNode(Tree t) { + super(InternalUtils.typeOf(t)); + // IdentifierTree for normal uses of the local variable or parameter, + // and VariableTree for the translation of an initializer block + assert t != null; + assert t instanceof IdentifierTree || t instanceof VariableTree; + tree = t; + this.receiver = null; + } + + public LocalVariableNode(Tree t, Node receiver) { + this(t); + this.receiver = receiver; + } + + public Element getElement() { + Element el; + if (tree instanceof IdentifierTree) { + el = TreeUtils.elementFromUse((IdentifierTree) tree); + } else { + assert tree instanceof VariableTree; + el = TreeUtils.elementFromDeclaration((VariableTree) tree); + } + return el; + } + + public Node getReceiver() { + return receiver; + } + + public String getName() { + if (tree instanceof IdentifierTree) { + return ((IdentifierTree) tree).getName().toString(); + } + return ((VariableTree) tree).getName().toString(); + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitLocalVariable(this, p); + } + + @Override + public String toString() { + return getName().toString(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof LocalVariableNode)) { + return false; + } + LocalVariableNode other = (LocalVariableNode) obj; + return getName().equals(other.getName()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getName()); + } + + @Override + public Collection<Node> getOperands() { + return Collections.emptyList(); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/LongLiteralNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/LongLiteralNode.java new file mode 100644 index 0000000000..47f856a0d8 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/LongLiteralNode.java @@ -0,0 +1,52 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.Collections; + +import com.sun.source.tree.LiteralTree; +import com.sun.source.tree.Tree; + +/** + * A node for a long literal. For example: + * + * <pre> + * <em>-3l</em> + * <em>0x80808080L</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class LongLiteralNode extends ValueLiteralNode { + + public LongLiteralNode(LiteralTree t) { + super(t); + assert t.getKind().equals(Tree.Kind.LONG_LITERAL); + } + + @Override + public Long getValue() { + return (Long) tree.getValue(); + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitLongLiteral(this, p); + } + + @Override + public boolean equals(Object obj) { + // test that obj is a LongLiteralNode + if (obj == null || !(obj instanceof LongLiteralNode)) { + return false; + } + // super method compares values + return super.equals(obj); + } + + @Override + public Collection<Node> getOperands() { + return Collections.emptyList(); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/MarkerNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/MarkerNode.java new file mode 100644 index 0000000000..fa7238e1c6 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/MarkerNode.java @@ -0,0 +1,89 @@ +package org.checkerframework.dataflow.cfg.node; + +/*>>> +import org.checkerframework.checker.nullness.qual.Nullable; +*/ + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import java.util.Collection; +import java.util.Collections; + +import javax.lang.model.type.TypeKind; +import javax.lang.model.util.Types; + +import com.sun.source.tree.Tree; + +/** + * MarkerNodes are no-op Nodes used for debugging information. + * They can hold a Tree and a message, which will be part of the + * String representation of the MarkerNode. + * + * An example use case for MarkerNodes is representing switch + * statements. + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class MarkerNode extends Node { + + protected /*@Nullable*/ Tree tree; + protected String message; + + public MarkerNode(/*@Nullable*/ Tree tree, String message, Types types) { + super(types.getNoType(TypeKind.NONE)); + this.tree = tree; + this.message = message; + } + + public String getMessage() { + return message; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitMarker(this, p); + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("marker "); + sb.append("(" + message + ")"); + return sb.toString(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof MarkerNode)) { + return false; + } + MarkerNode other = (MarkerNode) obj; + if (tree == null && other.getTree() != null) { + return false; + } + + return getTree().equals(other.getTree()) + && getMessage().equals(other.getMessage()); + } + + @Override + public int hashCode() { + int hash = 0; + if (tree != null) { + hash = HashCodeUtils.hash(tree); + } + return HashCodeUtils.hash(hash, getMessage()); + } + + @Override + public Collection<Node> getOperands() { + return Collections.emptyList(); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/MethodAccessNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/MethodAccessNode.java new file mode 100644 index 0000000000..75a8d681d3 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/MethodAccessNode.java @@ -0,0 +1,84 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.Collections; + +import javax.lang.model.element.ExecutableElement; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; +import org.checkerframework.javacutil.TreeUtils; + +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.Tree; + +/** + * A node for a method access, including a method accesses: + * + * <pre> + * <em>expression</em> . <em>method</em> () + * </pre> + * + * @author Stefan Heule + * + */ +public class MethodAccessNode extends Node { + + protected ExpressionTree tree; + protected ExecutableElement method; + protected Node receiver; + + // TODO: add method to get modifiers (static, access level, ..) + + public MethodAccessNode(ExpressionTree tree, Node receiver) { + super(InternalUtils.typeOf(tree)); + assert TreeUtils.isMethodAccess(tree); + this.tree = tree; + this.method = (ExecutableElement) TreeUtils.elementFromUse(tree); + this.receiver = receiver; + } + + public ExecutableElement getMethod() { + return method; + } + + public Node getReceiver() { + return receiver; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitMethodAccess(this, p); + } + + @Override + public String toString() { + return getReceiver() + "." + method.getSimpleName(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof MethodAccessNode)) { + return false; + } + MethodAccessNode other = (MethodAccessNode) obj; + return getReceiver().equals(other.getReceiver()) + && getMethod().equals(other.getMethod()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getReceiver(), getMethod()); + } + + @Override + public Collection<Node> getOperands() { + return Collections.singletonList(receiver); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/MethodInvocationNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/MethodInvocationNode.java new file mode 100644 index 0000000000..797185a83b --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/MethodInvocationNode.java @@ -0,0 +1,129 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.checkerframework.dataflow.cfg.node.AssignmentContext.MethodParameterContext; +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.Tree; +import com.sun.source.util.TreePath; + +/** + * A node for method invocation + * + * <pre> + * <em>target(arg1, arg2, ...)</em> + * </pre> + * + * CFGs may contain {@link MethodInvocationNode}s that correspond to no AST + * {@link Tree}, in which case, the tree field will be null. + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class MethodInvocationNode extends Node { + + protected MethodInvocationTree tree; + protected MethodAccessNode target; + protected List<Node> arguments; + protected TreePath treePath; + + public MethodInvocationNode(MethodInvocationTree tree, + MethodAccessNode target, List<Node> arguments, TreePath treePath) { + super(tree != null ? InternalUtils.typeOf(tree) : target.getMethod().getReturnType()); + this.tree = tree; + this.target = target; + this.arguments = arguments; + this.treePath = treePath; + + // set assignment contexts for parameters + int i = 0; + for (Node arg : arguments) { + AssignmentContext ctx = new MethodParameterContext(target.getMethod(), i++); + arg.setAssignmentContext(ctx); + } + } + + public MethodInvocationNode(MethodAccessNode target, List<Node> arguments, + TreePath treePath) { + this(null, target, arguments, treePath); + } + + public MethodAccessNode getTarget() { + return target; + } + + public List<Node> getArguments() { + return arguments; + } + + public Node getArgument(int i) { + return arguments.get(i); + } + + public TreePath getTreePath() { + return treePath; + } + + @Override + public MethodInvocationTree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitMethodInvocation(this, p); + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append(target); + sb.append("("); + boolean needComma = false; + for (Node arg : arguments) { + if (needComma) { + sb.append(", "); + } + sb.append(arg); + needComma = true; + } + sb.append(")"); + return sb.toString(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof MethodInvocationNode)) { + return false; + } + MethodInvocationNode other = (MethodInvocationNode) obj; + + return getTarget().equals(other.getTarget()) + && getArguments().equals(other.getArguments()); + } + + @Override + public int hashCode() { + int hash = 0; + hash = HashCodeUtils.hash(target); + for (Node arg : arguments) { + hash = HashCodeUtils.hash(hash, arg.hashCode()); + } + return hash; + } + + @Override + public Collection<Node> getOperands() { + List<Node> list = new LinkedList<Node>(); + list.add(target); + list.addAll(arguments); + return list; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/NarrowingConversionNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/NarrowingConversionNode.java new file mode 100644 index 0000000000..f0165d9589 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/NarrowingConversionNode.java @@ -0,0 +1,80 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.Collections; + +import javax.lang.model.type.TypeMirror; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.TypesUtils; + +import com.sun.source.tree.Tree; + +/** + * A node for the narrowing primitive conversion operation. See JLS 5.1.3 for + * the definition of narrowing primitive conversion. + * + * A {@link NarrowingConversionNode} does not correspond to any tree node in the + * parsed AST. It is introduced when a value of some primitive type appears in a + * context that requires a different primitive with more bits of precision. + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class NarrowingConversionNode extends Node { + + protected Tree tree; + protected Node operand; + + public NarrowingConversionNode(Tree tree, Node operand, TypeMirror type) { + super(type); + assert TypesUtils.isPrimitive(type) : "non-primitive type in narrowing conversion"; + this.tree = tree; + this.operand = operand; + } + + public Node getOperand() { + return operand; + } + + public TypeMirror getType() { + return type; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitNarrowingConversion(this, p); + } + + @Override + public String toString() { + return "NarrowingConversion(" + getOperand() + ", " + type + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof NarrowingConversionNode)) { + return false; + } + NarrowingConversionNode other = (NarrowingConversionNode) obj; + return getOperand().equals(other.getOperand()) + && TypesUtils.areSamePrimitiveTypes(getType(), other.getType()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getOperand()); + } + + @Override + public Collection<Node> getOperands() { + return Collections.singletonList(getOperand()); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/Node.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/Node.java new file mode 100644 index 0000000000..69220ea484 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/Node.java @@ -0,0 +1,171 @@ +package org.checkerframework.dataflow.cfg.node; + +/*>>> +import org.checkerframework.checker.nullness.qual.Nullable; +*/ + +import org.checkerframework.dataflow.cfg.CFGBuilder; +import org.checkerframework.dataflow.cfg.block.Block; + +import java.util.Collection; +import java.util.LinkedList; + +import javax.lang.model.type.TypeMirror; + +import com.sun.source.tree.Tree; + +/** + * A node in the abstract representation used for Java code inside a basic + * block. + * + * <p> + * + * The following invariants hold: + * + * <pre> + * block == null || block instanceof RegularBlock || block instanceof ExceptionBlock + * block instanceof RegularBlock ⇒ block.getContents().contains(this) + * block instanceof ExceptionBlock ⇒ block.getNode() == this + * block == null ⇔ "This object represents a parameter of the method." + * </pre> + * + * <pre> + * type != null + * tree != null ⇒ node.getType() == InternalUtils.typeOf(node.getTree()) + * </pre> + * + * @author Stefan Heule + * + */ +public abstract class Node { + + /** + * The basic block this node belongs to (see invariant about this field + * above). + */ + protected /*@Nullable*/ Block block; + + /** + * Is this node an l-value? + */ + protected boolean lvalue = false; + + /** + * The assignment context of this node. See {@link AssignmentContext}. + */ + protected /*@Nullable*/ AssignmentContext assignmentContext; + + /** + * Does this node represent a tree that appears in the source code (true) + * or one that the CFG builder added while desugaring (false). + */ + protected boolean inSource = true; + + /** + * The type of this node. For {@link Node}s with {@link Tree}s, this type is + * the type of the {@link Tree}. Otherwise, it is the type is set by the + * {@link CFGBuilder}. + */ + protected final TypeMirror type; + + public Node(TypeMirror type) { + assert type != null; + this.type = type; + } + + /** + * @return the basic block this node belongs to (or {@code null} if it + * represents the parameter of a method). + */ + public /*@Nullable*/ Block getBlock() { + return block; + } + + /** Set the basic block this node belongs to. */ + public void setBlock(Block b) { + block = b; + } + + /** + * Returns the {@link Tree} in the abstract syntax tree, or + * {@code null} if no corresponding tree exists. For instance, this is + * the case for an {@link ImplicitThisLiteralNode}. + * + * @return the corresponding {@link Tree} or {@code null}. + */ + abstract public /*@Nullable*/ Tree getTree(); + + /** + * Returns a {@link TypeMirror} representing the type of a {@link Node} A + * {@link Node} will always have a type even when it has no {@link Tree}. + * + * @return a {@link TypeMirror} representing the type of this {@link Node}. + */ + public TypeMirror getType() { + return type; + } + + /** + * Accept method of the visitor pattern + * + * @param <R> + * Result type of the operation. + * @param <P> + * Parameter type. + * @param visitor + * The visitor to be applied to this node. + * @param p + * The parameter for this operation. + */ + public abstract <R, P> R accept(NodeVisitor<R, P> visitor, P p); + + public boolean isLValue() { + return lvalue; + } + + /** + * Make this node an l-value. + */ + public void setLValue() { + lvalue = true; + } + + public boolean getInSource() { + return inSource; + } + + public void setInSource(boolean inSrc) { + inSource = inSrc; + } + + public AssignmentContext getAssignmentContext() { + return assignmentContext; + } + + public void setAssignmentContext(AssignmentContext assignmentContext) { + this.assignmentContext = assignmentContext; + } + + /** + * @return a collection containing all of the operand {@link Node}s of this + * {@link Node}. + */ + public abstract Collection<Node> getOperands(); + + /** + * @return a collection containing all of the operand {@link Node}s of this + * {@link Node}, as well as (transitively) the operands of its + * operands. + */ + public Collection<Node> getTransitiveOperands() { + LinkedList<Node> operands = new LinkedList<>(getOperands()); + LinkedList<Node> transitiveOperands = new LinkedList<>(); + while (!operands.isEmpty()) { + Node next = operands.removeFirst(); + operands.addAll(next.getOperands()); + transitiveOperands.add(next); + } + return transitiveOperands; + } + +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/NodeVisitor.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/NodeVisitor.java new file mode 100644 index 0000000000..69cb61db6f --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/NodeVisitor.java @@ -0,0 +1,163 @@ +package org.checkerframework.dataflow.cfg.node; + +/** + * A visitor for a {@link Node} tree. + * + * @author Stefan Heule + * + * @param <R> + * Return type of the visitor. Use {@link Void} if the visitor does + * not have a return value. + * @param <P> + * Parameter type of the visitor. Use {@link Void} if the visitor + * does not have a parameter. + */ +public interface NodeVisitor<R, P> { + // Literals + R visitShortLiteral(ShortLiteralNode n, P p); + + R visitIntegerLiteral(IntegerLiteralNode n, P p); + + R visitLongLiteral(LongLiteralNode n, P p); + + R visitFloatLiteral(FloatLiteralNode n, P p); + + R visitDoubleLiteral(DoubleLiteralNode n, P p); + + R visitBooleanLiteral(BooleanLiteralNode n, P p); + + R visitCharacterLiteral(CharacterLiteralNode n, P p); + + R visitStringLiteral(StringLiteralNode n, P p); + + R visitNullLiteral(NullLiteralNode n, P p); + + // Unary operations + R visitNumericalMinus(NumericalMinusNode n, P p); + + R visitNumericalPlus(NumericalPlusNode n, P p); + + R visitBitwiseComplement(BitwiseComplementNode n, P p); + + R visitNullChk(NullChkNode n, P p); + + // Binary operations + R visitStringConcatenate(StringConcatenateNode n, P p); + + R visitNumericalAddition(NumericalAdditionNode n, P p); + + R visitNumericalSubtraction(NumericalSubtractionNode n, P p); + + R visitNumericalMultiplication(NumericalMultiplicationNode n, P p); + + R visitIntegerDivision(IntegerDivisionNode n, P p); + + R visitFloatingDivision(FloatingDivisionNode n, P p); + + R visitIntegerRemainder(IntegerRemainderNode n, P p); + + R visitFloatingRemainder(FloatingRemainderNode n, P p); + + R visitLeftShift(LeftShiftNode n, P p); + + R visitSignedRightShift(SignedRightShiftNode n, P p); + + R visitUnsignedRightShift(UnsignedRightShiftNode n, P p); + + R visitBitwiseAnd(BitwiseAndNode n, P p); + + R visitBitwiseOr(BitwiseOrNode n, P p); + + R visitBitwiseXor(BitwiseXorNode n, P p); + + // Compound assignments + R visitStringConcatenateAssignment(StringConcatenateAssignmentNode n, P p); + + // Comparison operations + R visitLessThan(LessThanNode n, P p); + + R visitLessThanOrEqual(LessThanOrEqualNode n, P p); + + R visitGreaterThan(GreaterThanNode n, P p); + + R visitGreaterThanOrEqual(GreaterThanOrEqualNode n, P p); + + R visitEqualTo(EqualToNode n, P p); + + R visitNotEqual(NotEqualNode n, P p); + + // Conditional operations + R visitConditionalAnd(ConditionalAndNode n, P p); + + R visitConditionalOr(ConditionalOrNode n, P p); + + R visitConditionalNot(ConditionalNotNode n, P p); + + R visitTernaryExpression(TernaryExpressionNode n, P p); + + R visitAssignment(AssignmentNode n, P p); + + R visitLocalVariable(LocalVariableNode n, P p); + + R visitVariableDeclaration(VariableDeclarationNode n, P p); + + R visitFieldAccess(FieldAccessNode n, P p); + + R visitMethodAccess(MethodAccessNode n, P p); + + R visitArrayAccess(ArrayAccessNode n, P p); + + R visitImplicitThisLiteral(ImplicitThisLiteralNode n, P p); + + R visitExplicitThisLiteral(ExplicitThisLiteralNode n, P p); + + R visitSuper(SuperNode n, P p); + + R visitReturn(ReturnNode n, P p); + + R visitStringConversion(StringConversionNode n, P p); + + R visitNarrowingConversion(NarrowingConversionNode n, P p); + + R visitWideningConversion(WideningConversionNode n, P p); + + R visitInstanceOf(InstanceOfNode n, P p); + + R visitTypeCast(TypeCastNode n, P p); + + // Blocks + + R visitSynchronized(SynchronizedNode n, P p); + + // Statements + R visitAssertionError(AssertionErrorNode n, P p); + + R visitThrow(ThrowNode n, P p); + + // Cases + R visitCase(CaseNode n, P p); + + // Method and constructor invocations + R visitMethodInvocation(MethodInvocationNode n, P p); + + R visitObjectCreation(ObjectCreationNode n, P p); + + R visitMemberReference(FunctionalInterfaceNode n, P p); + + R visitArrayCreation(ArrayCreationNode n, P p); + + // Type, package and class names + R visitArrayType(ArrayTypeNode n, P p); + + R visitPrimitiveType(PrimitiveTypeNode n, P p); + + R visitClassName(ClassNameNode n, P p); + + R visitPackageName(PackageNameNode n, P p); + + // Parameterized types + R visitParameterizedType(ParameterizedTypeNode n, P p); + + // Marker nodes + R visitMarker(MarkerNode n, P p); +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/NotEqualNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/NotEqualNode.java new file mode 100644 index 0000000000..c043e20dee --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/NotEqualNode.java @@ -0,0 +1,83 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.LinkedList; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; + +/** + * A node for the not equal comparison: + * + * <pre> + * <em>expression</em> != <em>expression</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class NotEqualNode extends Node { + + protected Tree tree; + protected Node left; + protected Node right; + + public NotEqualNode(Tree tree, Node left, Node right) { + super(InternalUtils.typeOf(tree)); + assert tree.getKind() == Kind.NOT_EQUAL_TO; + this.tree = tree; + this.left = left; + this.right = right; + } + + public Node getLeftOperand() { + return left; + } + + public Node getRightOperand() { + return right; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitNotEqual(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " != " + getRightOperand() + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof NotEqualNode)) { + return false; + } + NotEqualNode other = (NotEqualNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getLeftOperand(), getRightOperand()); + } + + @Override + public Collection<Node> getOperands() { + LinkedList<Node> list = new LinkedList<Node>(); + list.add(getLeftOperand()); + list.add(getRightOperand()); + return list; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/NullChkNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/NullChkNode.java new file mode 100644 index 0000000000..01ac008f40 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/NullChkNode.java @@ -0,0 +1,72 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.Collections; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; + +/** + * A node for the unary 'nullchk' operation (generated by the Java compiler): + * + * <pre> + * <*nullchk*><em>expression</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + */ +public class NullChkNode extends Node { + + protected Tree tree; + protected Node operand; + + public NullChkNode(Tree tree, Node operand) { + super(InternalUtils.typeOf(tree)); + assert tree.getKind() == Kind.OTHER; + this.tree = tree; + this.operand = operand; + } + + public Node getOperand() { + return operand; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitNullChk(this, p); + } + + @Override + public String toString() { + return "(+ " + getOperand() + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof NumericalPlusNode)) { + return false; + } + NumericalPlusNode other = (NumericalPlusNode) obj; + return getOperand().equals(other.getOperand()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getOperand()); + } + + @Override + public Collection<Node> getOperands() { + return Collections.singletonList(getOperand()); + } + } diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/NullLiteralNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/NullLiteralNode.java new file mode 100644 index 0000000000..66e67152cd --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/NullLiteralNode.java @@ -0,0 +1,51 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.Collections; + +import com.sun.source.tree.LiteralTree; +import com.sun.source.tree.Tree; + +/** + * A node for the null literal. + * + * <pre> + * <em>null</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class NullLiteralNode extends ValueLiteralNode { + + public NullLiteralNode(LiteralTree t) { + super(t); + assert t.getKind().equals(Tree.Kind.NULL_LITERAL); + } + + @Override + public Void getValue() { + return (Void) tree.getValue(); + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitNullLiteral(this, p); + } + + @Override + public boolean equals(Object obj) { + // test that obj is a NullLiteralNode + if (obj == null || !(obj instanceof NullLiteralNode)) { + return false; + } + // super method compares values + return super.equals(obj); + } + + @Override + public Collection<Node> getOperands() { + return Collections.emptyList(); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/NumericalAdditionNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/NumericalAdditionNode.java new file mode 100644 index 0000000000..d4192adf47 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/NumericalAdditionNode.java @@ -0,0 +1,83 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.LinkedList; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; + +/** + * A node for the numerical addition: + * + * <pre> + * <em>expression</em> + <em>expression</em> + * </pre> + * + * @author Stefan Heule + * + */ +public class NumericalAdditionNode extends Node { + + protected Tree tree; + protected Node left; + protected Node right; + + public NumericalAdditionNode(Tree tree, Node left, Node right) { + super(InternalUtils.typeOf(tree)); + assert tree.getKind() == Kind.PLUS + || tree.getKind() == Kind.PLUS_ASSIGNMENT; + this.tree = tree; + this.left = left; + this.right = right; + } + + public Node getLeftOperand() { + return left; + } + + public Node getRightOperand() { + return right; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitNumericalAddition(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " + " + getRightOperand() + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof NumericalAdditionNode)) { + return false; + } + NumericalAdditionNode other = (NumericalAdditionNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getLeftOperand(), getRightOperand()); + } + + @Override + public Collection<Node> getOperands() { + LinkedList<Node> list = new LinkedList<Node>(); + list.add(getLeftOperand()); + list.add(getRightOperand()); + return list; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/NumericalMinusNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/NumericalMinusNode.java new file mode 100644 index 0000000000..459f299262 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/NumericalMinusNode.java @@ -0,0 +1,73 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.Collections; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; + +/** + * A node for the unary minus operation: + * + * <pre> + * - <em>expression</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class NumericalMinusNode extends Node { + + protected Tree tree; + protected Node operand; + + public NumericalMinusNode(Tree tree, Node operand) { + super(InternalUtils.typeOf(tree)); + assert tree.getKind() == Kind.UNARY_MINUS; + this.tree = tree; + this.operand = operand; + } + + public Node getOperand() { + return operand; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitNumericalMinus(this, p); + } + + @Override + public String toString() { + return "(- " + getOperand() + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof NumericalMinusNode)) { + return false; + } + NumericalMinusNode other = (NumericalMinusNode) obj; + return getOperand().equals(other.getOperand()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getOperand()); + } + + @Override + public Collection<Node> getOperands() { + return Collections.singletonList(getOperand()); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/NumericalMultiplicationNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/NumericalMultiplicationNode.java new file mode 100644 index 0000000000..947dc5dcde --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/NumericalMultiplicationNode.java @@ -0,0 +1,83 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.LinkedList; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; + +/** + * A node for the numerical multiplication: + * + * <pre> + * <em>expression</em> * <em>expression</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class NumericalMultiplicationNode extends Node { + + protected Tree tree; + protected Node left; + protected Node right; + + public NumericalMultiplicationNode(Tree tree, Node left, Node right) { + super(InternalUtils.typeOf(tree)); + assert tree.getKind() == Kind.MULTIPLY; + this.tree = tree; + this.left = left; + this.right = right; + } + + public Node getLeftOperand() { + return left; + } + + public Node getRightOperand() { + return right; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitNumericalMultiplication(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " * " + getRightOperand() + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof NumericalMultiplicationNode)) { + return false; + } + NumericalMultiplicationNode other = (NumericalMultiplicationNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getLeftOperand(), getRightOperand()); + } + + @Override + public Collection<Node> getOperands() { + LinkedList<Node> list = new LinkedList<Node>(); + list.add(getLeftOperand()); + list.add(getRightOperand()); + return list; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/NumericalPlusNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/NumericalPlusNode.java new file mode 100644 index 0000000000..3d19b278a7 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/NumericalPlusNode.java @@ -0,0 +1,73 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.Collections; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; + +/** + * A node for the unary plus operation: + * + * <pre> + * + <em>expression</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class NumericalPlusNode extends Node { + + protected Tree tree; + protected Node operand; + + public NumericalPlusNode(Tree tree, Node operand) { + super(InternalUtils.typeOf(tree)); + assert tree.getKind() == Kind.UNARY_PLUS; + this.tree = tree; + this.operand = operand; + } + + public Node getOperand() { + return operand; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitNumericalPlus(this, p); + } + + @Override + public String toString() { + return "(+ " + getOperand() + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof NumericalPlusNode)) { + return false; + } + NumericalPlusNode other = (NumericalPlusNode) obj; + return getOperand().equals(other.getOperand()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getOperand()); + } + + @Override + public Collection<Node> getOperands() { + return Collections.singletonList(getOperand()); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/NumericalSubtractionNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/NumericalSubtractionNode.java new file mode 100644 index 0000000000..1526a18461 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/NumericalSubtractionNode.java @@ -0,0 +1,83 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.LinkedList; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; + +/** + * A node for the numerical subtraction: + * + * <pre> + * <em>expression</em> - <em>expression</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class NumericalSubtractionNode extends Node { + + protected Tree tree; + protected Node left; + protected Node right; + + public NumericalSubtractionNode(Tree tree, Node left, Node right) { + super(InternalUtils.typeOf(tree)); + assert tree.getKind() == Kind.MINUS; + this.tree = tree; + this.left = left; + this.right = right; + } + + public Node getLeftOperand() { + return left; + } + + public Node getRightOperand() { + return right; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitNumericalSubtraction(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " - " + getRightOperand() + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof NumericalSubtractionNode)) { + return false; + } + NumericalSubtractionNode other = (NumericalSubtractionNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getLeftOperand(), getRightOperand()); + } + + @Override + public Collection<Node> getOperands() { + LinkedList<Node> list = new LinkedList<Node>(); + list.add(getLeftOperand()); + list.add(getRightOperand()); + return list; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ObjectCreationNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ObjectCreationNode.java new file mode 100644 index 0000000000..30f15edc17 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ObjectCreationNode.java @@ -0,0 +1,106 @@ +package org.checkerframework.dataflow.cfg.node; + +import org.checkerframework.dataflow.util.HashCodeUtils; +import org.checkerframework.javacutil.InternalUtils; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import com.sun.source.tree.NewClassTree; + +/** + * A node for new object creation + * + * <pre> + * <em>new constructor(arg1, arg2, ...)</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class ObjectCreationNode extends Node { + + protected NewClassTree tree; + protected Node constructor; + protected List<Node> arguments; + + public ObjectCreationNode(NewClassTree tree, + Node constructor, + List<Node> arguments) { + super(InternalUtils.typeOf(tree)); + this.tree = tree; + this.constructor = constructor; + this.arguments = arguments; + } + + public Node getConstructor() { + return constructor; + } + + public List<Node> getArguments() { + return arguments; + } + + public Node getArgument(int i) { + return arguments.get(i); + } + + @Override + public NewClassTree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitObjectCreation(this, p); + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("new " + constructor + "("); + boolean needComma = false; + for (Node arg : arguments) { + if (needComma) { + sb.append(", "); + } + sb.append(arg); + needComma = true; + } + sb.append(")"); + return sb.toString(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof ObjectCreationNode)) { + return false; + } + ObjectCreationNode other = (ObjectCreationNode) obj; + if (constructor == null && other.getConstructor() != null) { + return false; + } + + return getConstructor().equals(other.getConstructor()) + && getArguments().equals(other.getArguments()); + } + + @Override + public int hashCode() { + int hash = HashCodeUtils.hash(constructor); + for (Node arg : arguments) { + hash = HashCodeUtils.hash(hash, arg.hashCode()); + } + return hash; + } + + @Override + public Collection<Node> getOperands() { + LinkedList<Node> list = new LinkedList<Node>(); + list.add(constructor); + list.addAll(arguments); + return list; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/PackageNameNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/PackageNameNode.java new file mode 100644 index 0000000000..3851126f44 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/PackageNameNode.java @@ -0,0 +1,110 @@ +package org.checkerframework.dataflow.cfg.node; + +/*>>> +import org.checkerframework.checker.nullness.qual.Nullable; +*/ + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; +import org.checkerframework.javacutil.TreeUtils; + +import java.util.Collection; +import java.util.Collections; + +import javax.lang.model.element.Element; + +import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.MemberSelectTree; +import com.sun.source.tree.Tree; + +/** + * A node representing a package name used in an expression such as a + * constructor invocation + * + * <p> + * <em>package</em>.class.object(...) + * <p> + * parent.<em>package</em>.class.object(...) + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class PackageNameNode extends Node { + + protected final Tree tree; + /** The package named by this node */ + protected final Element element; + + /** The parent name, if any. */ + protected final /*@Nullable*/ PackageNameNode parent; + + public PackageNameNode(IdentifierTree tree) { + super(InternalUtils.typeOf(tree)); + this.tree = tree; + this.element = TreeUtils.elementFromUse(tree); + this.parent = null; + } + + public PackageNameNode(MemberSelectTree tree, PackageNameNode parent) { + super(InternalUtils.typeOf(tree)); + this.tree = tree; + this.element = TreeUtils.elementFromUse(tree); + this.parent = parent; + } + + public Element getElement() { + return element; + } + + public PackageNameNode getParent() { + return parent; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitPackageName(this, p); + } + + @Override + public String toString() { + return getElement().getSimpleName().toString(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof PackageNameNode)) { + return false; + } + PackageNameNode other = (PackageNameNode) obj; + if (getParent() == null) { + return other.getParent() == null + && getElement().equals(other.getElement()); + } else { + return getParent().equals(other.getParent()) + && getElement().equals(other.getElement()); + } + } + + @Override + public int hashCode() { + if (parent == null) { + return HashCodeUtils.hash(getElement()); + } + return HashCodeUtils.hash(getElement(), getParent()); + } + + @Override + public Collection<Node> getOperands() { + if (parent == null) { + return Collections.emptyList(); + } + return Collections.singleton((Node) parent); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ParameterizedTypeNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ParameterizedTypeNode.java new file mode 100644 index 0000000000..7939b41fa7 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ParameterizedTypeNode.java @@ -0,0 +1,73 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.Collections; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.ParameterizedTypeTree; +import com.sun.source.tree.Tree; + +/** + * A node for a parameterized type occurring in an expression: + * + * <pre> + * <em>type<arg1, arg2></em> + * </pre> + * + * Parameterized types don't represent any computation to be done + * at runtime, so we might choose to represent them differently by + * modifying the {@link Node}s in which parameterized types can occur, such + * as {@link ObjectCreationNode}s. + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ + +public class ParameterizedTypeNode extends Node { + + protected Tree tree; + + public ParameterizedTypeNode(Tree t) { + super(InternalUtils.typeOf(t)); + assert t instanceof ParameterizedTypeTree; + tree = t; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitParameterizedType(this, p); + } + + @Override + public String toString() { + return getTree().toString(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof ParameterizedTypeNode)) { + return false; + } + ParameterizedTypeNode other = (ParameterizedTypeNode) obj; + return getTree().equals(other.getTree()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getTree()); + } + + @Override + public Collection<Node> getOperands() { + return Collections.emptyList(); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/PrimitiveTypeNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/PrimitiveTypeNode.java new file mode 100644 index 0000000000..69b185641d --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/PrimitiveTypeNode.java @@ -0,0 +1,65 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.Collections; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.PrimitiveTypeTree; +import com.sun.source.tree.Tree; + +/** + * A node representing a primitive type used in an expression + * such as a field access + * + * <em>type</em> .class + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class PrimitiveTypeNode extends Node { + + protected final PrimitiveTypeTree tree; + + public PrimitiveTypeNode(PrimitiveTypeTree tree) { + super(InternalUtils.typeOf(tree)); + this.tree = tree; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitPrimitiveType(this, p); + } + + @Override + public String toString() { + return tree.toString(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof PrimitiveTypeNode)) { + return false; + } + PrimitiveTypeNode other = (PrimitiveTypeNode) obj; + return getType().equals(other.getType()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getType()); + } + + @Override + public Collection<Node> getOperands() { + return Collections.emptyList(); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ReturnNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ReturnNode.java new file mode 100644 index 0000000000..dbc74c83e3 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ReturnNode.java @@ -0,0 +1,101 @@ +package org.checkerframework.dataflow.cfg.node; + +/*>>> +import org.checkerframework.checker.nullness.qual.Nullable; +*/ + +import com.sun.source.tree.LambdaExpressionTree; +import com.sun.tools.javac.code.Symbol.MethodSymbol; +import org.checkerframework.dataflow.cfg.node.AssignmentContext.LambdaReturnContext; +import org.checkerframework.dataflow.cfg.node.AssignmentContext.MethodReturnContext; +import org.checkerframework.dataflow.util.HashCodeUtils; + +import java.util.Collection; +import java.util.Collections; + +import javax.lang.model.type.TypeKind; +import javax.lang.model.util.Types; + +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.ReturnTree; + +/** + * A node for a return statement: + * + * <pre> + * return + * return <em>expression</em> + * </pre> + * + * @author Stefan Heule + * + */ +public class ReturnNode extends Node { + + protected ReturnTree tree; + protected /*@Nullable*/ Node result; + + public ReturnNode(ReturnTree t, /*@Nullable*/ Node result, Types types, MethodTree methodTree) { + super(types.getNoType(TypeKind.NONE)); + this.result = result; + tree = t; + result.setAssignmentContext(new MethodReturnContext(methodTree)); + } + + public ReturnNode(ReturnTree t, /*@Nullable*/ Node result, Types types, LambdaExpressionTree lambda, MethodSymbol methodSymbol) { + super(types.getNoType(TypeKind.NONE)); + this.result = result; + tree = t; + result.setAssignmentContext(new LambdaReturnContext(methodSymbol)); + } + + + public Node getResult() { + return result; + } + + @Override + public ReturnTree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitReturn(this, p); + } + + @Override + public String toString() { + if (result != null) { + return "return " + result; + } + return "return"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof ReturnNode)) { + return false; + } + ReturnNode other = (ReturnNode) obj; + if ((result == null) != (other.result == null)) { + return false; + } + return (result == null || result.equals(other.result)); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(result); + } + + @Override + public Collection<Node> getOperands() { + if (result == null) { + return Collections.emptyList(); + } else { + return Collections.singletonList(result); + } + } + +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ShortLiteralNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ShortLiteralNode.java new file mode 100644 index 0000000000..f61da82397 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ShortLiteralNode.java @@ -0,0 +1,57 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.Collections; + +import com.sun.source.tree.LiteralTree; +import com.sun.source.tree.Tree; + +/** + * A node for a short literal. For example: + * + * <pre> + * <em>5</em> + * <em>0x8fff</em> + * </pre> + * + * Java source and the AST representation do not have "short" literals. They + * have integer literals that may be narrowed to shorts depending on context. If + * we use explicit NarrowingConversionNodes, do we need ShortLiteralNodes too? + * TODO: Decide this question. + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class ShortLiteralNode extends ValueLiteralNode { + + public ShortLiteralNode(LiteralTree t) { + super(t); + assert t.getKind().equals(Tree.Kind.INT_LITERAL); + } + + @Override + public Short getValue() { + return (Short) tree.getValue(); + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitShortLiteral(this, p); + } + + @Override + public boolean equals(Object obj) { + // test that obj is a ShortLiteralNode + if (obj == null || !(obj instanceof ShortLiteralNode)) { + return false; + } + // super method compares values + return super.equals(obj); + } + + @Override + public Collection<Node> getOperands() { + return Collections.emptyList(); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/SignedRightShiftNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/SignedRightShiftNode.java new file mode 100644 index 0000000000..3683ab9a3c --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/SignedRightShiftNode.java @@ -0,0 +1,83 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.LinkedList; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; + +/** + * A node for bitwise right shift operations with sign extension: + * + * <pre> + * <em>expression</em> >> <em>expression</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class SignedRightShiftNode extends Node { + + protected Tree tree; + protected Node left; + protected Node right; + + public SignedRightShiftNode(Tree tree, Node left, Node right) { + super(InternalUtils.typeOf(tree)); + assert tree.getKind() == Kind.RIGHT_SHIFT; + this.tree = tree; + this.left = left; + this.right = right; + } + + public Node getLeftOperand() { + return left; + } + + public Node getRightOperand() { + return right; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitSignedRightShift(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " >> " + getRightOperand() + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof SignedRightShiftNode)) { + return false; + } + SignedRightShiftNode other = (SignedRightShiftNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getLeftOperand(), getRightOperand()); + } + + @Override + public Collection<Node> getOperands() { + LinkedList<Node> list = new LinkedList<Node>(); + list.add(getLeftOperand()); + list.add(getRightOperand()); + return list; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/StringConcatenateAssignmentNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/StringConcatenateAssignmentNode.java new file mode 100644 index 0000000000..d6c4c532b0 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/StringConcatenateAssignmentNode.java @@ -0,0 +1,60 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.LinkedList; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; + +/** + * A node for the string concatenation compound assignment: + * + * <pre> + * <em>variable</em> += <em>expression</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class StringConcatenateAssignmentNode extends Node { + protected Tree tree; + protected Node left; + protected Node right; + + public StringConcatenateAssignmentNode(Tree tree, Node left, Node right) { + super(InternalUtils.typeOf(tree)); + assert tree.getKind() == Kind.PLUS_ASSIGNMENT; + this.tree = tree; + this.left = left; + this.right = right; + } + + public Node getLeftOperand() { + return left; + } + + public Node getRightOperand() { + return right; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitStringConcatenateAssignment(this, p); + } + + @Override + public Collection<Node> getOperands() { + LinkedList<Node> list = new LinkedList<Node>(); + list.add(getLeftOperand()); + list.add(getRightOperand()); + return list; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/StringConcatenateNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/StringConcatenateNode.java new file mode 100644 index 0000000000..e42d6ac7db --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/StringConcatenateNode.java @@ -0,0 +1,83 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.LinkedList; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; + +/** + * A node for string concatenation: + * + * <pre> + * <em>expression</em> + <em>expression</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class StringConcatenateNode extends Node { + + protected Tree tree; + protected Node left; + protected Node right; + + public StringConcatenateNode(Tree tree, Node left, Node right) { + super(InternalUtils.typeOf(tree)); + assert tree.getKind() == Kind.PLUS; + this.tree = tree; + this.left = left; + this.right = right; + } + + public Node getLeftOperand() { + return left; + } + + public Node getRightOperand() { + return right; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitStringConcatenate(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " + " + getRightOperand() + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof StringConcatenateNode)) { + return false; + } + StringConcatenateNode other = (StringConcatenateNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getLeftOperand(), getRightOperand()); + } + + @Override + public Collection<Node> getOperands() { + LinkedList<Node> list = new LinkedList<Node>(); + list.add(getLeftOperand()); + list.add(getRightOperand()); + return list; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/StringConversionNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/StringConversionNode.java new file mode 100644 index 0000000000..2f62c8af9a --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/StringConversionNode.java @@ -0,0 +1,82 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.Collections; + +import javax.lang.model.type.TypeMirror; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import com.sun.source.tree.Tree; + +/** + * A node for the string conversion operation. See JLS 5.1.11 for the definition + * of string conversion. + * + * A {@link StringConversionNode} does not correspond to any tree node in the + * parsed AST. It is introduced when a value of non-string type appears in a + * context that requires a {@link String}, such as in a string concatenation. A + * {@link StringConversionNode} should be treated as a potential call to the + * toString method of its operand, but does not necessarily call any method + * because null is converted to the string "null". + * + * Conversion of primitive types to Strings requires first boxing and then + * string conversion. + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class StringConversionNode extends Node { + + protected Tree tree; + protected Node operand; + + // TODO: The type of a string conversion should be a final + // TypeMirror representing java.lang.String. Currently we require + // the caller to pass in a TypeMirror instead of creating one + // through the javax.lang.model.type.Types interface. + public StringConversionNode(Tree tree, Node operand, TypeMirror type) { + super(type); + this.tree = tree; + this.operand = operand; + } + + public Node getOperand() { + return operand; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitStringConversion(this, p); + } + + @Override + public String toString() { + return "StringConversion(" + getOperand() + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof StringConversionNode)) { + return false; + } + StringConversionNode other = (StringConversionNode) obj; + return getOperand().equals(other.getOperand()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getOperand()); + } + + @Override + public Collection<Node> getOperands() { + return Collections.singletonList(getOperand()); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/StringLiteralNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/StringLiteralNode.java new file mode 100644 index 0000000000..e8d9291c55 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/StringLiteralNode.java @@ -0,0 +1,55 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.Collections; + +import com.sun.source.tree.LiteralTree; +import com.sun.source.tree.Tree; + +/** + * A node for an string literal. For example: + * + * <pre> + * <em>"abc"</em> + * </pre> + * + * @author Stefan Heule + * + */ +public class StringLiteralNode extends ValueLiteralNode { + + public StringLiteralNode(LiteralTree t) { + super(t); + assert t.getKind().equals(Tree.Kind.STRING_LITERAL); + } + + @Override + public String getValue() { + return (String) tree.getValue(); + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitStringLiteral(this, p); + } + + @Override + public boolean equals(Object obj) { + // test that obj is a StringLiteralNode + if (!(obj instanceof StringLiteralNode)) { + return false; + } + // super method compares values + return super.equals(obj); + } + + @Override + public Collection<Node> getOperands() { + return Collections.emptyList(); + } + + @Override + public String toString() { + return "\"" + super.toString() + "\""; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/SuperNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/SuperNode.java new file mode 100644 index 0000000000..d6ce410285 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/SuperNode.java @@ -0,0 +1,71 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.Collections; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.Tree; + +/** + * A node for a reference to 'super'. + * + * <pre> + * <em>super</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class SuperNode extends Node { + + protected Tree tree; + + public SuperNode(Tree t) { + super(InternalUtils.typeOf(t)); + assert t instanceof IdentifierTree + && ((IdentifierTree) t).getName().contentEquals("super"); + tree = t; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitSuper(this, p); + } + + public String getName() { + return "super"; + } + + @Override + public String toString() { + return getName(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof SuperNode)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getName()); + } + + @Override + public Collection<Node> getOperands() { + return Collections.emptyList(); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/SynchronizedNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/SynchronizedNode.java new file mode 100644 index 0000000000..609a0a20f8 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/SynchronizedNode.java @@ -0,0 +1,91 @@ +package org.checkerframework.dataflow.cfg.node; + +/*>>> +import org.checkerframework.checker.nullness.qual.Nullable; +*/ + +/* + * This represents the start and end of synchronized code block. + * If startOfBlock == true it is the node preceding a synchronized code block. + * Otherwise it is the node immediately after a synchronized code block. + */ + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import java.util.Collection; +import java.util.Collections; + +import javax.lang.model.type.TypeKind; +import javax.lang.model.util.Types; + +import com.sun.source.tree.Tree; + +public class SynchronizedNode extends Node { + + protected /*@Nullable*/ Tree tree; + protected Node expression; + protected boolean startOfBlock; + + public SynchronizedNode(/*@Nullable*/ Tree tree, Node expression, boolean startOfBlock, Types types) { + super(types.getNoType(TypeKind.NONE)); + this.tree = tree; + this.expression = expression; + this.startOfBlock = startOfBlock; + } + + @Override + public Tree getTree() { + return tree; + } + + public Node getExpression() { + return expression; + } + + public boolean getIsStartOfBlock() { + return startOfBlock; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitSynchronized(this, p); + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("synchronized "); + sb.append("(" + expression + ")"); + return sb.toString(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof SynchronizedNode)) { + return false; + } + SynchronizedNode other = (SynchronizedNode) obj; + if (tree == null && other.getTree() != null) { + return false; + } + + return getTree().equals(other.getTree()) + && getExpression().equals(other.getExpression()) + && startOfBlock == other.startOfBlock; + } + + @Override + public int hashCode() { + int hash = 0; + if (tree != null) { + hash = HashCodeUtils.hash(tree); + } + hash = HashCodeUtils.hash(startOfBlock); + return HashCodeUtils.hash(hash, getExpression()); + } + + @Override + public Collection<Node> getOperands() { + return Collections.emptyList(); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/TernaryExpressionNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/TernaryExpressionNode.java new file mode 100644 index 0000000000..514f85944d --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/TernaryExpressionNode.java @@ -0,0 +1,94 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.LinkedList; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.ConditionalExpressionTree; +import com.sun.source.tree.Tree.Kind; + +/** + * A node for a conditional expression: + * + * <pre> + * <em>expression</em> ? <em>expression</em> : <em>expression</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class TernaryExpressionNode extends Node { + + protected ConditionalExpressionTree tree; + protected Node condition; + protected Node thenOperand; + protected Node elseOperand; + + public TernaryExpressionNode(ConditionalExpressionTree tree, Node condition, + Node thenOperand, Node elseOperand) { + super(InternalUtils.typeOf(tree)); + assert tree.getKind().equals(Kind.CONDITIONAL_EXPRESSION); + this.tree = tree; + this.condition = condition; + this.thenOperand = thenOperand; + this.elseOperand = elseOperand; + } + + public Node getConditionOperand() { + return condition; + } + + public Node getThenOperand() { + return thenOperand; + } + + public Node getElseOperand() { + return elseOperand; + } + + @Override + public ConditionalExpressionTree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitTernaryExpression(this, p); + } + + @Override + public String toString() { + return "(" + getConditionOperand() + " ? " + getThenOperand() + " : " + + getElseOperand() + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof TernaryExpressionNode)) { + return false; + } + TernaryExpressionNode other = (TernaryExpressionNode) obj; + return getConditionOperand().equals(other.getConditionOperand()) + && getThenOperand().equals(other.getThenOperand()) + && getElseOperand().equals(other.getElseOperand()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getConditionOperand(), getThenOperand(), + getElseOperand()); + } + + @Override + public Collection<Node> getOperands() { + LinkedList<Node> list = new LinkedList<Node>(); + list.add(getConditionOperand()); + list.add(getThenOperand()); + list.add(getElseOperand()); + return list; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ThisLiteralNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ThisLiteralNode.java new file mode 100644 index 0000000000..f62d6ac675 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ThisLiteralNode.java @@ -0,0 +1,48 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.Collections; + +import javax.lang.model.type.TypeMirror; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +/** + * A node for a reference to 'this', either implicit or explicit. + * + * <pre> + * <em>this</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public abstract class ThisLiteralNode extends Node { + + public ThisLiteralNode(TypeMirror type) { + super(type); + } + + public String getName() { + return "this"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof ThisLiteralNode)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getName()); + } + + @Override + public Collection<Node> getOperands() { + return Collections.emptyList(); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ThrowNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ThrowNode.java new file mode 100644 index 0000000000..f563cef96b --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ThrowNode.java @@ -0,0 +1,74 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.Collections; + +import javax.lang.model.type.TypeKind; +import javax.lang.model.util.Types; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import com.sun.source.tree.ThrowTree; +import com.sun.source.tree.Tree; + +/** + * A node for exception throws: + * + * <pre> + * <em>throw</em> expr + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class ThrowNode extends Node { + + protected ThrowTree tree; + protected Node expression; + + public ThrowNode(ThrowTree tree, + Node expression, Types types) { + super(types.getNoType(TypeKind.NONE)); + this.tree = tree; + this.expression = expression; + } + + public Node getExpression() { + return expression; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitThrow(this, p); + } + + @Override + public String toString() { + return "throw " + expression; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof ThrowNode)) { + return false; + } + ThrowNode other = (ThrowNode) obj; + return getExpression().equals(other.getExpression()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(expression); + } + + @Override + public Collection<Node> getOperands() { + return Collections.singletonList(expression); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/TypeCastNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/TypeCastNode.java new file mode 100644 index 0000000000..ce287a5cb6 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/TypeCastNode.java @@ -0,0 +1,76 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.Collections; + +import javax.lang.model.type.TypeMirror; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import com.sun.source.tree.Tree; + +/** + * A node for the cast operator: + * + * (<em>Point</em>) <em>x</em> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class TypeCastNode extends Node { + + protected Tree tree; + protected Node operand; + + public TypeCastNode(Tree tree, Node operand, TypeMirror type) { + super(type); + this.tree = tree; + this.operand = operand; + } + + public Node getOperand() { + return operand; + } + + public TypeMirror getType() { + return type; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitTypeCast(this, p); + } + + @Override + public String toString() { + return "(" + getType() + ")" + getOperand(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof TypeCastNode)) { + return false; + } + TypeCastNode other = (TypeCastNode) obj; + // TODO: TypeMirror.equals may be too restrictive. + // Check whether Types.isSameType is the better comparison. + return getOperand().equals(other.getOperand()) + && getType().equals(other.getType()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getOperand()); + } + + @Override + public Collection<Node> getOperands() { + return Collections.singletonList(getOperand()); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/UnsignedRightShiftNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/UnsignedRightShiftNode.java new file mode 100644 index 0000000000..0ddea5b6c6 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/UnsignedRightShiftNode.java @@ -0,0 +1,83 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.LinkedList; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.Tree; +import com.sun.source.tree.Tree.Kind; + +/** + * A node for bitwise right shift operations with zero extension: + * + * <pre> + * <em>expression</em> >>> <em>expression</em> + * </pre> + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class UnsignedRightShiftNode extends Node { + + protected Tree tree; + protected Node left; + protected Node right; + + public UnsignedRightShiftNode(Tree tree, Node left, Node right) { + super(InternalUtils.typeOf(tree)); + assert tree.getKind() == Kind.UNSIGNED_RIGHT_SHIFT; + this.tree = tree; + this.left = left; + this.right = right; + } + + public Node getLeftOperand() { + return left; + } + + public Node getRightOperand() { + return right; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitUnsignedRightShift(this, p); + } + + @Override + public String toString() { + return "(" + getLeftOperand() + " >>> " + getRightOperand() + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof UnsignedRightShiftNode)) { + return false; + } + UnsignedRightShiftNode other = (UnsignedRightShiftNode) obj; + return getLeftOperand().equals(other.getLeftOperand()) + && getRightOperand().equals(other.getRightOperand()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getLeftOperand(), getRightOperand()); + } + + @Override + public Collection<Node> getOperands() { + LinkedList<Node> list = new LinkedList<Node>(); + list.add(getLeftOperand()); + list.add(getRightOperand()); + return list; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ValueLiteralNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ValueLiteralNode.java new file mode 100644 index 0000000000..75d18ea8f7 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/ValueLiteralNode.java @@ -0,0 +1,80 @@ +package org.checkerframework.dataflow.cfg.node; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import java.util.Collection; +import java.util.Collections; + +import com.sun.source.tree.LiteralTree; + +/*>>> +import org.checkerframework.checker.nullness.qual.Nullable; +*/ + +/** + * A node for a literals that have some form of value: + * <ul> + * <li>integer literal</li> + * <li>long literal</li> + * <li>char literal</li> + * <li>string literal</li> + * <li>float literal</li> + * <li>double literal</li> + * <li>boolean literal</li> + * <li>null literal</li> + * </ul> + * + * @author Stefan Heule + * + */ +public abstract class ValueLiteralNode extends Node { + + protected final LiteralTree tree; + + /** + * @return the value of the literal + */ + abstract public /*@Nullable*/ Object getValue(); + + public ValueLiteralNode(LiteralTree tree) { + super(InternalUtils.typeOf(tree)); + this.tree = tree; + } + + @Override + public LiteralTree getTree() { + return tree; + } + + @Override + public String toString() { + return String.valueOf(getValue()); + } + + /** + * Compare the value of this nodes. + */ + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof ValueLiteralNode)) { + return false; + } + ValueLiteralNode other = (ValueLiteralNode) obj; + Object val = getValue(); + Object otherVal = other.getValue(); + return ((val == null || otherVal == null) && val == otherVal) || val.equals(otherVal); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getValue()); + } + + @Override + public Collection<Node> getOperands() { + return Collections.emptyList(); + } + +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/VariableDeclarationNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/VariableDeclarationNode.java new file mode 100644 index 0000000000..5841768e38 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/VariableDeclarationNode.java @@ -0,0 +1,76 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.Collections; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.VariableTree; + +/** + * A node for a local variable declaration: + * + * <pre> + * <em>modifier</em> <em>type</em> <em>identifier</em>; + * </pre> + * + * Note: Does not have an initializer block, as that will be translated to a + * separate {@link AssignmentNode}. + * + * @author Stefan Heule + * + */ +public class VariableDeclarationNode extends Node { + + protected VariableTree tree; + protected String name; + + // TODO: make modifier accessible + + public VariableDeclarationNode(VariableTree t) { + super(InternalUtils.typeOf(t)); + tree = t; + name = tree.getName().toString(); + } + + public String getName() { + return name; + } + + @Override + public VariableTree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitVariableDeclaration(this, p); + } + + @Override + public String toString() { + return name; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof VariableDeclarationNode)) { + return false; + } + VariableDeclarationNode other = (VariableDeclarationNode) obj; + return getName().equals(other.getName()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getName()); + } + + @Override + public Collection<Node> getOperands() { + return Collections.emptyList(); + } + +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/WideningConversionNode.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/WideningConversionNode.java new file mode 100644 index 0000000000..4724ec1066 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/node/WideningConversionNode.java @@ -0,0 +1,80 @@ +package org.checkerframework.dataflow.cfg.node; + +import java.util.Collection; +import java.util.Collections; + +import javax.lang.model.type.TypeMirror; + +import org.checkerframework.dataflow.util.HashCodeUtils; + +import org.checkerframework.javacutil.TypesUtils; + +import com.sun.source.tree.Tree; + +/** + * A node for the widening primitive conversion operation. See JLS 5.1.2 for the + * definition of widening primitive conversion. + * + * A {@link WideningConversionNode} does not correspond to any tree node in the + * parsed AST. It is introduced when a value of some primitive type appears in a + * context that requires a different primitive with more bits of precision. + * + * @author Stefan Heule + * @author Charlie Garrett + * + */ +public class WideningConversionNode extends Node { + + protected Tree tree; + protected Node operand; + + public WideningConversionNode(Tree tree, Node operand, TypeMirror type) { + super(type); + assert TypesUtils.isPrimitive(type) : "non-primitive type in widening conversion"; + this.tree = tree; + this.operand = operand; + } + + public Node getOperand() { + return operand; + } + + public TypeMirror getType() { + return type; + } + + @Override + public Tree getTree() { + return tree; + } + + @Override + public <R, P> R accept(NodeVisitor<R, P> visitor, P p) { + return visitor.visitWideningConversion(this, p); + } + + @Override + public String toString() { + return "WideningConversion(" + getOperand() + ", " + type + ")"; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof WideningConversionNode)) { + return false; + } + WideningConversionNode other = (WideningConversionNode) obj; + return getOperand().equals(other.getOperand()) + && TypesUtils.areSamePrimitiveTypes(getType(), other.getType()); + } + + @Override + public int hashCode() { + return HashCodeUtils.hash(getOperand()); + } + + @Override + public Collection<Node> getOperands() { + return Collections.singletonList(getOperand()); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/playground/ConstantPropagationPlayground.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/playground/ConstantPropagationPlayground.java new file mode 100644 index 0000000000..1713f50e29 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/cfg/playground/ConstantPropagationPlayground.java @@ -0,0 +1,32 @@ +package org.checkerframework.dataflow.cfg.playground; + +import org.checkerframework.dataflow.analysis.Analysis; +import org.checkerframework.dataflow.cfg.JavaSource2CFGDOT; +import org.checkerframework.dataflow.constantpropagation.Constant; +import org.checkerframework.dataflow.constantpropagation.ConstantPropagationStore; +import org.checkerframework.dataflow.constantpropagation.ConstantPropagationTransfer; + +public class ConstantPropagationPlayground { + + /** + * Run constant propagation for a specific file and create a PDF of the CFG + * in the end. + */ + public static void main(String[] args) { + + /* Configuration: change as appropriate */ + String inputFile = "cfg-input.java"; // input file name and path + String outputDir = "cfg"; // output directory + String method = "test"; // name of the method to analyze + String clazz = "Test"; // name of the class to consider + + // run the analysis and create a PDF file + ConstantPropagationTransfer transfer = new ConstantPropagationTransfer(); + // TODO: correct processing environment + Analysis<Constant, ConstantPropagationStore, ConstantPropagationTransfer> analysis = new Analysis<>( + null, transfer); + JavaSource2CFGDOT.generateDOTofCFG(inputFile, outputDir, method, + clazz, true, analysis); + } + +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/constantpropagation/Constant.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/constantpropagation/Constant.java new file mode 100644 index 0000000000..291ecf3a69 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/constantpropagation/Constant.java @@ -0,0 +1,101 @@ +package org.checkerframework.dataflow.constantpropagation; + +/*>>> +import org.checkerframework.checker.nullness.qual.Nullable; +*/ + +import java.util.Objects; + +import org.checkerframework.dataflow.analysis.AbstractValue; + +public class Constant implements AbstractValue<Constant> { + + /** What kind of abstract value is this? */ + protected Type type; + + /** The value of this abstract value (or null) */ + protected /*@Nullable*/ Integer value; + + public enum Type { + CONSTANT, TOP, BOTTOM, + } + + public Constant(Type type) { + assert !type.equals(Type.CONSTANT); + this.type = type; + } + + public Constant(Integer value) { + this.type = Type.CONSTANT; + this.value = value; + } + + public boolean isTop() { + return type.equals(Type.TOP); + } + + public boolean isBottom() { + return type.equals(Type.BOTTOM); + } + + public boolean isConstant() { + return type.equals(Type.CONSTANT); + } + + public Integer getValue() { + assert isConstant(); + return value; + } + + public Constant copy() { + if (isConstant()) { + return new Constant(value); + } + return new Constant(type); + } + + @Override + public Constant leastUpperBound(Constant other) { + if (other.isBottom()) { + return this.copy(); + } + if (this.isBottom()) { + return other.copy(); + } + if (other.isTop() || this.isTop()) { + return new Constant(Type.TOP); + } + if (other.getValue().equals(getValue())) { + return this.copy(); + } + return new Constant(Type.TOP); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof Constant)) { + return false; + } + Constant other = (Constant) obj; + return type == other.type && Objects.equals(value, other.value); + } + + @Override + public int hashCode() { + return type.hashCode() + (value != null ? value.hashCode() : 0); + } + + @Override + public String toString() { + switch (type) { + case TOP: + return "T"; + case BOTTOM: + return "-"; + case CONSTANT: + return value.toString(); + } + assert false; + return "???"; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/constantpropagation/ConstantPropagationStore.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/constantpropagation/ConstantPropagationStore.java new file mode 100644 index 0000000000..da27a6eb21 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/constantpropagation/ConstantPropagationStore.java @@ -0,0 +1,165 @@ +package org.checkerframework.dataflow.constantpropagation; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.checkerframework.dataflow.analysis.FlowExpressions; +import org.checkerframework.dataflow.analysis.Store; +import org.checkerframework.dataflow.cfg.CFGVisualizer; +import org.checkerframework.dataflow.cfg.node.IntegerLiteralNode; +import org.checkerframework.dataflow.cfg.node.LocalVariableNode; +import org.checkerframework.dataflow.cfg.node.Node; +import org.checkerframework.dataflow.constantpropagation.Constant.Type; + +public class ConstantPropagationStore implements + Store<ConstantPropagationStore> { + + /** Information about variables gathered so far. */ + Map<Node, Constant> contents; + + public ConstantPropagationStore() { + contents = new HashMap<>(); + } + + protected ConstantPropagationStore(Map<Node, Constant> contents) { + this.contents = contents; + } + + public Constant getInformation(Node n) { + if (contents.containsKey(n)) { + return contents.get(n); + } + return new Constant(Type.TOP); + } + + public void mergeInformation(Node n, Constant val) { + Constant value; + if (contents.containsKey(n)) { + value = val.leastUpperBound(contents.get(n)); + } else { + value = val; + } + // TODO: remove (only two nodes supported atm) + assert n instanceof IntegerLiteralNode + || n instanceof LocalVariableNode; + contents.put(n, value); + } + + public void setInformation(Node n, Constant val) { + // TODO: remove (only two nodes supported atm) + assert n instanceof IntegerLiteralNode + || n instanceof LocalVariableNode; + contents.put(n, val); + } + + @Override + public ConstantPropagationStore copy() { + return new ConstantPropagationStore(new HashMap<>(contents)); + } + + @Override + public ConstantPropagationStore leastUpperBound( + ConstantPropagationStore other) { + Map<Node, Constant> newContents = new HashMap<>(); + + // go through all of the information of the other class + for (Entry<Node, Constant> e : other.contents.entrySet()) { + Node n = e.getKey(); + Constant otherVal = e.getValue(); + if (contents.containsKey(n)) { + // merge if both contain information about a variable + newContents.put(n, otherVal.leastUpperBound(contents.get(n))); + } else { + // add new information + newContents.put(n, otherVal); + } + } + + for (Entry<Node, Constant> e : contents.entrySet()) { + Node n = e.getKey(); + Constant thisVal = e.getValue(); + if (!other.contents.containsKey(n)) { + // add new information + newContents.put(n, thisVal); + } + } + + return new ConstantPropagationStore(newContents); + } + + @Override + public boolean equals(Object o) { + if (o == null) { + return false; + } + if (!(o instanceof ConstantPropagationStore)) { + return false; + } + ConstantPropagationStore other = (ConstantPropagationStore) o; + // go through all of the information of the other object + for (Entry<Node, Constant> e : other.contents.entrySet()) { + Node n = e.getKey(); + Constant otherVal = e.getValue(); + if (otherVal.isBottom()) { + continue; // no information + } + if (contents.containsKey(n)) { + if (!otherVal.equals(contents.get(n))) { + return false; + } + } else { + return false; + } + } + // go through all of the information of the this object + for (Entry<Node, Constant> e : contents.entrySet()) { + Node n = e.getKey(); + Constant thisVal = e.getValue(); + if (thisVal.isBottom()) { + continue; // no information + } + if (other.contents.containsKey(n)) { + continue; + } else { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + int s = 0; + for (Entry<Node, Constant> e : contents.entrySet()) { + if (!e.getValue().isBottom()) { + s += e.hashCode(); + } + } + return s; + } + + @Override + public String toString() { + // only output local variable information + Map<Node, Constant> smallerContents = new HashMap<>(); + for (Entry<Node, Constant> e : contents.entrySet()) { + if (e.getKey() instanceof LocalVariableNode) { + smallerContents.put(e.getKey(), e.getValue()); + } + } + return smallerContents.toString(); + } + + @Override + public boolean canAlias(FlowExpressions.Receiver a, + FlowExpressions.Receiver b) { + return true; + } + + @Override + public void visualize(CFGVisualizer<?, ConstantPropagationStore, ?> viz) { + // Do nothing since ConstantPropagationStore doesn't support visualize + } + +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/constantpropagation/ConstantPropagationTransfer.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/constantpropagation/ConstantPropagationTransfer.java new file mode 100644 index 0000000000..c0b2143d12 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/constantpropagation/ConstantPropagationTransfer.java @@ -0,0 +1,88 @@ +package org.checkerframework.dataflow.constantpropagation; + +import org.checkerframework.dataflow.analysis.ConditionalTransferResult; +import org.checkerframework.dataflow.analysis.RegularTransferResult; +import org.checkerframework.dataflow.analysis.TransferFunction; +import org.checkerframework.dataflow.analysis.TransferInput; +import org.checkerframework.dataflow.analysis.TransferResult; +import org.checkerframework.dataflow.cfg.UnderlyingAST; +import org.checkerframework.dataflow.cfg.node.AbstractNodeVisitor; +import org.checkerframework.dataflow.cfg.node.AssignmentNode; +import org.checkerframework.dataflow.cfg.node.EqualToNode; +import org.checkerframework.dataflow.cfg.node.IntegerLiteralNode; +import org.checkerframework.dataflow.cfg.node.LocalVariableNode; +import org.checkerframework.dataflow.cfg.node.Node; + +import java.util.List; + +public class ConstantPropagationTransfer + extends + AbstractNodeVisitor<TransferResult<Constant, ConstantPropagationStore>, TransferInput<Constant, ConstantPropagationStore>> + implements TransferFunction<Constant, ConstantPropagationStore> { + + @Override + public ConstantPropagationStore initialStore(UnderlyingAST underlyingAST, + List<LocalVariableNode> parameters) { + ConstantPropagationStore store = new ConstantPropagationStore(); + return store; + } + + @Override + public TransferResult<Constant, ConstantPropagationStore> visitLocalVariable( + LocalVariableNode node, TransferInput<Constant, ConstantPropagationStore> before) { + ConstantPropagationStore store = before.getRegularStore(); + Constant value = store.getInformation(node); + return new RegularTransferResult<>(value, store); + } + + @Override + public TransferResult<Constant, ConstantPropagationStore> visitNode(Node n, + TransferInput<Constant, ConstantPropagationStore> p) { + return new RegularTransferResult<>(null, p.getRegularStore()); + } + + @Override + public TransferResult<Constant, ConstantPropagationStore> visitAssignment( + AssignmentNode n, + TransferInput<Constant, ConstantPropagationStore> pi) { + ConstantPropagationStore p = pi.getRegularStore(); + Node target = n.getTarget(); + Constant info = null; + if (target instanceof LocalVariableNode) { + LocalVariableNode t = (LocalVariableNode) target; + info = p.getInformation(n.getExpression()); + p.setInformation(t, info); + } + return new RegularTransferResult<>(info, p); + } + + @Override + public TransferResult<Constant, ConstantPropagationStore> visitIntegerLiteral( + IntegerLiteralNode n, + TransferInput<Constant, ConstantPropagationStore> pi) { + ConstantPropagationStore p = pi.getRegularStore(); + Constant c = new Constant(n.getValue()); + p.setInformation(n, c); + return new RegularTransferResult<>(c, p); + } + + @Override + public TransferResult<Constant, ConstantPropagationStore> visitEqualTo( + EqualToNode n, TransferInput<Constant, ConstantPropagationStore> pi) { + ConstantPropagationStore p = pi.getRegularStore(); + ConstantPropagationStore old = p.copy(); + Node left = n.getLeftOperand(); + Node right = n.getRightOperand(); + process(p, left, right); + process(p, right, left); + return new ConditionalTransferResult<>(null, p, old); + } + + protected void process(ConstantPropagationStore p, Node a, Node b) { + Constant val = p.getInformation(a); + if (b instanceof LocalVariableNode && val.isConstant()) { + p.setInformation(b, val); + } + } + +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/qual/Deterministic.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/qual/Deterministic.java new file mode 100644 index 0000000000..fd797f8803 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/qual/Deterministic.java @@ -0,0 +1,92 @@ +package org.checkerframework.dataflow.qual; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A method is called <em>deterministic</em> if it returns the same value + * (according to {@code ==}) every time it is called with the same + * parameters and in the same environment. The parameters include the + * receiver, and the environment includes all of the Java heap (that is, + * all fields of all objects and all static variables). + * <p> + * This annotation is important to pluggable type-checking because, after a + * call to a {@code @Deterministic} method, flow-sensitive type refinement + * can assume that anything learned about the first invocation is true + * about subsequent invocations (so long as no non-{@code @}{@link + * SideEffectFree} method call intervenes). For example, + * the following code never suffers a null pointer + * exception, so the Nullness Checker need not issue a warning: + * <pre>{@code if (x.myDeterministicMethod() != null) { + x.myDeterministicMethod().hashCode(); + }}</pre> + * <p> + * Note that {@code @Deterministic} guarantees that the result is + * identical according to {@code ==}, <b>not</b> equal according to + * {@code equals}. This means that writing {@code @Deterministic} on a + * method that returns a reference is often erroneous unless the + * returned value is cached or interned. + * <p> + * Also see {@link Pure}, which means both deterministic and {@link + * SideEffectFree}. + * <p> + * <b>Analysis:</b> + * The Checker Framework performs a conservative analysis to verify a + * {@code @Deterministic} annotation. The Checker Framework issues a + * warning if the method uses any of the following Java constructs: + * <ol> + * <li>Assignment to any expression, except for local variables (and method + * parameters). + * <li>A method invocation of a method that is not {@link Deterministic}. + * <li>Construction of a new object. + * <li>Catching any exceptions. This is to prevent a method to get a hold of + * newly created objects and using these objects (or some property thereof) + * to change their return value. For instance, the following method must be + * forbidden. + * <pre>{@code + @Deterministic + int f() { + try { + int b = 0; + int a = 1/b; + } catch (Throwable t) { + return t.hashCode(); + } + return 0; + } + }</pre> + * </ol> + * A constructor can be {@code @Pure}, but a constructor <em>invocation</em> is + * not deterministic since it returns a different new object each time. + * TODO: Side-effect-free constructors could be allowed to set their own fields. + * <p> + * + * Note that the rules for checking currently imply that every {@code + * Deterministic} method is also {@link SideEffectFree}. This might change + * in the future; in general, a deterministic method does not need to be + * side-effect-free. + * <p> + * + * These rules are conservative: any code that passes the checks is + * deterministic, but the Checker Framework may issue false positive + * warnings, for code that uses one of the forbidden constructs but is + * deterministic nonetheless. + * <p> + * + * In fact, the rules are so conservative that checking is currently + * disabled by default, but can be enabled via the + * {@code -AcheckPurityAnnotations} command-line option. + * <p> + * + * @checker_framework.manual #type-refinement-purity Side effects, determinism, purity, and flow-sensitive analysis + * + * @author Stefan Heule + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.CONSTRUCTOR }) +public @interface Deterministic { +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/qual/Pure.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/qual/Pure.java new file mode 100644 index 0000000000..a559c1db27 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/qual/Pure.java @@ -0,0 +1,37 @@ +package org.checkerframework.dataflow.qual; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * {@code Pure} is a method annotation that means both {@link + * SideEffectFree} and {@link Deterministic}. The more important of these, + * when performing pluggable type-checking, is usually {@link + * SideEffectFree}. + * + * @checker_framework.manual #type-refinement-purity Side effects, determinism, purity, and flow-sensitive analysis + * + * @author Stefan Heule + * + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.CONSTRUCTOR }) +public @interface Pure { + /** + * The type of purity. + */ + public static enum Kind { + /** The method has no visible side-effects. */ + SIDE_EFFECT_FREE, + + /** + * The method returns exactly the same value when called in the same + * environment. + */ + DETERMINISTIC + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/qual/SideEffectFree.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/qual/SideEffectFree.java new file mode 100644 index 0000000000..eadcc4f49e --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/qual/SideEffectFree.java @@ -0,0 +1,63 @@ +package org.checkerframework.dataflow.qual; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A method is called <em>side-effect-free</em> if it has no visible + * side-effects, such as setting a field of an object that existed before + * the method was called. + * <p> + * Only the visible side-effects are important. The method is allowed to cache + * the answer to a computationally expensive query, for instance. It is also + * allowed to modify newly-created objects, and a constructor is + * side-effect-free if it does not modify any objects that existed before + * it was called. + * <p> + * This annotation is important to pluggable type-checking because if some + * fact about an object is known before a call to such a method, then the + * fact is still known afterwards, even if the fact is about some non-final + * field. When any non-{@code @SideEffectFree} method is called, then a + * pluggable type-checker must assume that any field of any accessible + * object might have been modified, which annuls the effect of + * flow-sensitive type refinement and prevents the pluggable type-checker + * from making conclusions that are obvious to a programmer. + * <p> + * Also see {@link Pure}, which means both side-effect-free and {@link + * Deterministic}. + * <p> + * <b>Analysis:</b> + * The Checker Framework performs a conservative analysis to verify a + * {@code @SideEffectFree} annotation. + * The Checker Framework issues a warning + * if the method uses any of the following Java constructs: + * <ol> + * <li>Assignment to any expression, except for local variables and method + * parameters. + * <li>A method invocation of a method that is not {@code @SideEffectFree}. + * <li>Construction of a new object where the constructor is not {@code @SideEffectFree}. + * </ol> + * These rules are conservative: any code that passes the checks is + * side-effect-free, but the Checker Framework may issue false positive + * warnings, for code that uses one of the forbidden constructs but is + * side-effect-free nonetheless. In particular, a method that caches its + * result will be rejected. + * <p> + * + * In fact, the rules are so conservative that checking is currently + * disabled by default, but can be enabled via the + * {@code -AcheckPurityAnnotations} command-line option. + * <p> + * + * @checker_framework.manual #type-refinement-purity Side effects, determinism, purity, and flow-sensitive analysis + * + * @author Stefan Heule + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.CONSTRUCTOR }) +public @interface SideEffectFree { +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/qual/TerminatesExecution.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/qual/TerminatesExecution.java new file mode 100644 index 0000000000..cd9d5d8c79 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/qual/TerminatesExecution.java @@ -0,0 +1,38 @@ +package org.checkerframework.dataflow.qual; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * {@code TerminatesExecution} is a method annotation that indicates that a + * method terminates the execution of the program. This can be used to + * annotate methods such as {@code System.exit()}. + * <p> + + * The annotation enables flow-sensitive type refinement to be more + * precise. For example, after + * <pre> + * if (x == null) { + * System.err.println("Bad value supplied"); + * System.exit(1); + * } + * </pre> + * the Nullness Checker can determine that {@code x} is non-null. + * + * <p> + * The annotation is a <em>trusted</em> annotation, meaning that it is not + * checked whether the annotated method really does terminate the program. + * + * @checker_framework.manual #type-refinement Automatic type refinement (flow-sensitive type qualifier inference) + * + * @author Stefan Heule + * + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.CONSTRUCTOR }) +public @interface TerminatesExecution { +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/util/HashCodeUtils.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/util/HashCodeUtils.java new file mode 100644 index 0000000000..b8fa81af27 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/util/HashCodeUtils.java @@ -0,0 +1,103 @@ +package org.checkerframework.dataflow.util; + +/** + * Utility class to implement the {@code hashCode} method. + * + * @author Stefan Heule + * + */ +public class HashCodeUtils { + + /** Odd prime number. */ + private static int prime = 31; + + /** Seed. */ + private static int seed = 17; + + /** Add a boolean value to a given hash. */ + public static int hash(int hash, boolean item) { + return hash * prime + (item ? 1 : 0); + } + + /** Add a char value to a given hash. */ + public static int hash(int hash, char item) { + return hash * prime + item; + } + + /** Add an int value to a given hash. */ + public static int hash(int hash, int item) { + return hash * prime + item; + } + + /** Add a long value to a given hash. */ + public static int hash(int hash, long item) { + return hash * prime + (int) (item ^ (item >>> 32)); + } + + /** Add a float value to a given hash. */ + public static int hash(int hash, float item) { + return hash * prime + Float.floatToIntBits(item); + } + + /** Add a double value to a given hash. */ + public static int hash(int hash, double item) { + long l = Double.doubleToLongBits(item); + return seed * prime + (int) (l ^ (l >>> 32)); + } + + /** Add an object to a given hash. */ + public static int hash(int hash, Object item) { + if (item == null) { + return hash * prime; + } + return hash * prime + item.hashCode(); + } + + /** Hash a boolean value. */ + public static int hash(boolean item) { + return (item ? 1 : 0); + } + + /** Hash a char value. */ + public static int hash(char item) { + return item; + } + + /** Hash an int value. */ + public static int hash(int item) { + return item; + } + + /** Hash a long value. */ + public static int hash(long item) { + return (int) (item ^ (item >>> 32)); + } + + /** Hash a float value. */ + public static int hash(float item) { + return Float.floatToIntBits(item); + } + + /** Hash a double value. */ + public static int hash(double item) { + long l = Double.doubleToLongBits(item); + return (int) (l ^ (l >>> 32)); + } + + /** Hash an object. */ + public static int hash(Object item) { + if (item == null) { + return 0; + } + return item.hashCode(); + } + + /** Hash multiple objects. */ + public static int hash(Object... items) { + int result = seed; + for (Object item : items) { + result = result * prime + (item == null ? 0 : item.hashCode()); + } + return result; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/util/MostlySingleton.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/util/MostlySingleton.java new file mode 100644 index 0000000000..44bdbd6f61 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/util/MostlySingleton.java @@ -0,0 +1,150 @@ +package org.checkerframework.dataflow.util; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Set; + +/** + * A set that is more efficient than HashSet for 0 and 1 elements. + */ +final public class MostlySingleton<T> implements Set<T> { + private enum State { + EMPTY, SINGLETON, ANY + } + private State state = State.EMPTY; + private T value; + private HashSet<T> set; + + @Override + public int size() { + switch (state) { + case EMPTY: + return 0; + case SINGLETON: + return 1; + case ANY: + return set.size(); + default: + throw new AssertionError(); + } + } + + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + public boolean contains(Object o) { + switch (state) { + case EMPTY: + return false; + case SINGLETON: + return Objects.equals(o, value); + case ANY: + return set.contains(o); + default: + throw new AssertionError(); + } + } + + @Override + @SuppressWarnings("fallthrough") + public boolean add(T e) { + switch (state) { + case EMPTY: + state = State.SINGLETON; + value = e; + return true; + case SINGLETON: + state = State.ANY; + set = new HashSet<T>(); + set.add(value); + value = null; + // fallthrough + case ANY: + return set.add(e); + default: + throw new AssertionError(); + } + } + + @Override + public Iterator<T> iterator() { + switch (state) { + case EMPTY: + return Collections.emptyIterator(); + case SINGLETON: + return new Iterator<T>() { + private boolean hasNext = true; + + @Override + public boolean hasNext() { + return hasNext; + } + + @Override + public T next() { + if (hasNext) { + hasNext = false; + return value; + } + throw new NoSuchElementException(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + case ANY: + return set.iterator(); + default: + throw new AssertionError(); + } + } + + @Override + public Object[] toArray() { + throw new UnsupportedOperationException(); + } + + @Override + public <S> S[] toArray(S[] a) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean containsAll(Collection<?> c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(Collection<? extends T> c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(Collection<?> c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(Collection<?> c) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/util/NodeUtils.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/util/NodeUtils.java new file mode 100644 index 0000000000..3c2efc548e --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/util/NodeUtils.java @@ -0,0 +1,45 @@ +package org.checkerframework.dataflow.util; + +import org.checkerframework.javacutil.TypesUtils; + +import org.checkerframework.dataflow.cfg.node.ConditionalOrNode; +import org.checkerframework.dataflow.cfg.node.Node; + +import com.sun.source.tree.Tree; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.tree.JCTree; + +/** + * A utility class to operate on a given {@link Node}. + * + * @author Stefan Heule + * + */ +public class NodeUtils { + + /** + * @return true iff {@code node} corresponds to a boolean typed + * expression (either the primitive type {@code boolean}, or + * class type {@link java.lang.Boolean}) + */ + public static boolean isBooleanTypeNode(Node node) { + + if (node instanceof ConditionalOrNode) { + return true; + } + + // not all nodes have an associated tree, but those are all not of a + // boolean type. + Tree tree = node.getTree(); + if (tree == null) { + return false; + } + + Type type = ((JCTree) tree).type; + if (TypesUtils.isBooleanType(type)) { + return true; + } + + return false; + } +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/util/PurityChecker.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/util/PurityChecker.java new file mode 100644 index 0000000000..5fafb7a8a1 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/util/PurityChecker.java @@ -0,0 +1,528 @@ +package org.checkerframework.dataflow.util; + +/*>>> +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; +*/ + +import org.checkerframework.dataflow.qual.Deterministic; +import org.checkerframework.dataflow.qual.Pure; +import org.checkerframework.dataflow.qual.Pure.Kind; +import org.checkerframework.dataflow.qual.SideEffectFree; + +import org.checkerframework.javacutil.AnnotationProvider; +import org.checkerframework.javacutil.InternalUtils; +import org.checkerframework.javacutil.Pair; +import org.checkerframework.javacutil.TreeUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumSet; +import java.util.List; + +import javax.lang.model.element.Element; + +import com.sun.source.tree.ArrayAccessTree; +import com.sun.source.tree.AssertTree; +import com.sun.source.tree.AssignmentTree; +import com.sun.source.tree.BinaryTree; +import com.sun.source.tree.BlockTree; +import com.sun.source.tree.BreakTree; +import com.sun.source.tree.CaseTree; +import com.sun.source.tree.CatchTree; +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.CompoundAssignmentTree; +import com.sun.source.tree.ConditionalExpressionTree; +import com.sun.source.tree.ContinueTree; +import com.sun.source.tree.DoWhileLoopTree; +import com.sun.source.tree.EmptyStatementTree; +import com.sun.source.tree.EnhancedForLoopTree; +import com.sun.source.tree.ExpressionStatementTree; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.ForLoopTree; +import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.IfTree; +import com.sun.source.tree.InstanceOfTree; +import com.sun.source.tree.LabeledStatementTree; +import com.sun.source.tree.LambdaExpressionTree; +import com.sun.source.tree.LiteralTree; +import com.sun.source.tree.MemberReferenceTree; +import com.sun.source.tree.MemberSelectTree; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.NewArrayTree; +import com.sun.source.tree.NewClassTree; +import com.sun.source.tree.ParenthesizedTree; +import com.sun.source.tree.ReturnTree; +import com.sun.source.tree.SwitchTree; +import com.sun.source.tree.SynchronizedTree; +import com.sun.source.tree.ThrowTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.TryTree; +import com.sun.source.tree.TypeCastTree; +import com.sun.source.tree.UnaryTree; +import com.sun.source.tree.VariableTree; +import com.sun.source.tree.WhileLoopTree; +import com.sun.source.util.SimpleTreeVisitor; +import com.sun.tools.javac.tree.TreeScanner; + +/** + * A visitor that determines the purity (as defined by {@link + * org.checkerframework.dataflow.qual.SideEffectFree}, {@link org.checkerframework.dataflow.qual.Deterministic}, + * and {@link org.checkerframework.dataflow.qual.Pure}) of a statement or expression. The + * entry point is method {@link #checkPurity}. + * + * @see SideEffectFree + * @see Deterministic + * @see Pure + * + * @author Stefan Heule + * + */ +public class PurityChecker { + + /** + * Compute whether the given statement is + * side-effect-free, deterministic, or both. + * Returns a result that can be queried. + */ + public static PurityResult checkPurity(Tree statement, + AnnotationProvider annoProvider, boolean assumeSideEffectFree) { + PurityCheckerHelper helper = new PurityCheckerHelper(annoProvider, assumeSideEffectFree); + PurityResult res = helper.scan(statement, new PurityResult()); + return res; + } + + /** + * Result of the {@link PurityChecker}. + * Can be queried queried regarding whether a given tree was + * side-effect-free, deterministic, or both; also gives reasons if + * the answer is "no". + */ + public static class PurityResult { + + protected final List<Pair<Tree, String>> notSeFreeReasons; + protected final List<Pair<Tree, String>> notDetReasons; + protected final List<Pair<Tree, String>> notBothReasons; + protected EnumSet<Pure.Kind> types; + + public PurityResult() { + notSeFreeReasons = new ArrayList<>(); + notDetReasons = new ArrayList<>(); + notBothReasons = new ArrayList<>(); + types = EnumSet.allOf(Pure.Kind.class); + } + + public EnumSet<Pure.Kind> getTypes() { + return types; + } + + /** Is the method pure w.r.t. a given set of types? */ + public boolean isPure(Collection<Kind> kinds) { + return types.containsAll(kinds); + } + + /** + * Get the {@code reason}s why the method is not side-effect-free. + */ + public List<Pair<Tree, String>> getNotSeFreeReasons() { + return notSeFreeReasons; + } + + /** + * Add {@code reason} as a reason why the method is not side-effect + * free. + */ + public void addNotSeFreeReason(Tree t, String msgId) { + notSeFreeReasons.add(Pair.of(t, msgId)); + types.remove(Kind.SIDE_EFFECT_FREE); + } + + /** + * Get the {@code reason}s why the method is not deterministic. + */ + public List<Pair<Tree, String>> getNotDetReasons() { + return notDetReasons; + } + + /** + * Add {@code reason} as a reason why the method is not deterministic. + */ + public void addNotDetReason(Tree t, String msgId) { + notDetReasons.add(Pair.of(t, msgId)); + types.remove(Kind.DETERMINISTIC); + } + + /** + * Get the {@code reason}s why the method is not both side-effect-free + * and deterministic. + */ + public List<Pair<Tree, String>> getNotBothReasons() { + return notBothReasons; + } + + /** + * Add {@code reason} as a reason why the method is not both side-effect + * free and deterministic. + */ + public void addNotBothReason(Tree t, String msgId) { + notBothReasons.add(Pair.of(t, msgId)); + types.remove(Kind.DETERMINISTIC); + types.remove(Kind.SIDE_EFFECT_FREE); + } + } + + /** + * Helper class to keep {@link PurityChecker}'s interface clean. The + * implementation is heavily based on {@link TreeScanner}, but some parts of + * the AST are skipped (such as types or modifiers). Furthermore, scanning + * works differently in that the input parameter (usually named {@code p}) + * gets "threaded through", instead of using {@code reduce}. + */ + protected static class PurityCheckerHelper + extends SimpleTreeVisitor<PurityResult, PurityResult> { + + protected final AnnotationProvider annoProvider; + /** True if all methods should be assumed to be @SideEffectFree, + * for the purposes of org.checkerframework.dataflow analysis. */ + private final boolean assumeSideEffectFree; + protected /*@Nullable*/ List<Element> methodParameter; + + public PurityCheckerHelper(AnnotationProvider annoProvider, boolean assumeSideEffectFree) { + this.annoProvider = annoProvider; + this.assumeSideEffectFree = assumeSideEffectFree; + } + + /** + * Scan a single node. + */ + public PurityResult scan(Tree node, PurityResult p) { + return node == null ? p : node.accept(this, p); + } + + /** + * Scan a list of nodes. + */ + public PurityResult scan(Iterable<? extends Tree> nodes, PurityResult p) { + PurityResult r = p; + if (nodes != null) { + for (Tree node : nodes) { + r = scan(node, r); + } + } + return r; + } + + @Override + protected PurityResult defaultAction(Tree node, PurityResult p) { + assert false : "this type of tree is unexpected here"; + return null; + } + + @Override + public PurityResult visitClass(ClassTree node, PurityResult p) { + return p; + } + + @Override + public PurityResult visitVariable(VariableTree node, PurityResult p) { + return scan(node.getInitializer(), p); + } + + @Override + public PurityResult visitEmptyStatement(EmptyStatementTree node, + PurityResult p) { + return p; + } + + @Override + public PurityResult visitBlock(BlockTree node, PurityResult p) { + return scan(node.getStatements(), p); + } + + @Override + public PurityResult visitDoWhileLoop(DoWhileLoopTree node, + PurityResult p) { + PurityResult r = scan(node.getStatement(), p); + r = scan(node.getCondition(), r); + return r; + } + + @Override + public PurityResult visitWhileLoop(WhileLoopTree node, PurityResult p) { + PurityResult r = scan(node.getCondition(), p); + r = scan(node.getStatement(), r); + return r; + } + + @Override + public PurityResult visitForLoop(ForLoopTree node, PurityResult p) { + PurityResult r = scan(node.getInitializer(), p); + r = scan(node.getCondition(), r); + r = scan(node.getUpdate(), r); + r = scan(node.getStatement(), r); + return r; + } + + @Override + public PurityResult visitEnhancedForLoop(EnhancedForLoopTree node, + PurityResult p) { + PurityResult r = scan(node.getVariable(), p); + r = scan(node.getExpression(), r); + r = scan(node.getStatement(), r); + return r; + } + + @Override + public PurityResult visitLabeledStatement(LabeledStatementTree node, + PurityResult p) { + return scan(node.getStatement(), p); + } + + @Override + public PurityResult visitSwitch(SwitchTree node, PurityResult p) { + PurityResult r = scan(node.getExpression(), p); + r = scan(node.getCases(), r); + return r; + } + + @Override + public PurityResult visitCase(CaseTree node, PurityResult p) { + PurityResult r = scan(node.getExpression(), p); + r = scan(node.getStatements(), r); + return r; + } + + @Override + public PurityResult visitSynchronized(SynchronizedTree node, + PurityResult p) { + PurityResult r = scan(node.getExpression(), p); + r = scan(node.getBlock(), r); + return r; + } + + @Override + public PurityResult visitTry(TryTree node, PurityResult p) { + PurityResult r = scan(node.getResources(), p); + r = scan(node.getBlock(), r); + r = scan(node.getCatches(), r); + r = scan(node.getFinallyBlock(), r); + return r; + } + + @Override + public PurityResult visitCatch(CatchTree node, PurityResult p) { + p.addNotDetReason(node, "catch"); + PurityResult r = scan(node.getParameter(), p); + r = scan(node.getBlock(), r); + return r; + } + + @Override + public PurityResult visitConditionalExpression( + ConditionalExpressionTree node, PurityResult p) { + PurityResult r = scan(node.getCondition(), p); + r = scan(node.getTrueExpression(), r); + r = scan(node.getFalseExpression(), r); + return r; + } + + @Override + public PurityResult visitIf(IfTree node, PurityResult p) { + PurityResult r = scan(node.getCondition(), p); + r = scan(node.getThenStatement(), r); + r = scan(node.getElseStatement(), r); + return r; + } + + @Override + public PurityResult visitExpressionStatement( + ExpressionStatementTree node, PurityResult p) { + return scan(node.getExpression(), p); + } + + @Override + public PurityResult visitBreak(BreakTree node, PurityResult p) { + return p; + } + + @Override + public PurityResult visitContinue(ContinueTree node, PurityResult p) { + return p; + } + + @Override + public PurityResult visitReturn(ReturnTree node, PurityResult p) { + return scan(node.getExpression(), p); + } + + @Override + public PurityResult visitThrow(ThrowTree node, PurityResult p) { + return scan(node.getExpression(), p); + } + + @Override + public PurityResult visitAssert(AssertTree node, PurityResult p) { + PurityResult r = scan(node.getCondition(), p); + r = scan(node.getDetail(), r); + return r; + } + + @Override + public PurityResult visitMethodInvocation(MethodInvocationTree node, + PurityResult p) { + Element elt = TreeUtils.elementFromUse(node); + String reason = "call"; + if (!PurityUtils.hasPurityAnnotation(annoProvider, elt)) { + p.addNotBothReason(node, reason); + } else { + boolean det = PurityUtils.isDeterministic(annoProvider, elt); + boolean seFree = (assumeSideEffectFree + || PurityUtils.isSideEffectFree(annoProvider, + elt)); + if (!det && !seFree) { + p.addNotBothReason(node, reason); + } else if (!det) { + p.addNotDetReason(node, reason); + } else if (!seFree) { + p.addNotSeFreeReason(node, reason); + } + } + PurityResult r = scan(node.getMethodSelect(), p); + r = scan(node.getArguments(), r); + return r; + } + + @Override + public PurityResult visitNewClass(NewClassTree node, PurityResult p) { + Element methodElement = InternalUtils.symbol(node); + boolean sideEffectFree = (assumeSideEffectFree + || PurityUtils.isSideEffectFree(annoProvider, + methodElement)); + if (sideEffectFree) { + p.addNotDetReason(node, "object.creation"); + } else { + p.addNotBothReason(node, "object.creation"); + } + PurityResult r = scan(node.getEnclosingExpression(), p); + r = scan(node.getArguments(), r); + r = scan(node.getClassBody(), r); + return r; + } + + @Override + public PurityResult visitNewArray(NewArrayTree node, PurityResult p) { + PurityResult r = scan(node.getDimensions(), p); + r = scan(node.getInitializers(), r); + return r; + } + + @Override + public PurityResult visitLambdaExpression(LambdaExpressionTree node, + PurityResult p) { + PurityResult r = scan(node.getParameters(), p); + r = scan(node.getBody(), r); + return r; + } + + @Override + public PurityResult visitParenthesized(ParenthesizedTree node, + PurityResult p) { + return scan(node.getExpression(), p); + } + + @Override + public PurityResult visitAssignment(AssignmentTree node, PurityResult p) { + ExpressionTree variable = node.getVariable(); + p = assignmentCheck(p, variable); + PurityResult r = scan(variable, p); + r = scan(node.getExpression(), r); + return r; + } + + protected PurityResult assignmentCheck(PurityResult p, + ExpressionTree variable) { + if (TreeUtils.isFieldAccess(variable)) { + // rhs is a field access + p.addNotBothReason(variable, "assign.field"); + } else if (variable instanceof ArrayAccessTree) { + // rhs is array access + p.addNotBothReason(variable, "assign.array"); + } else { + // rhs is a local variable + assert isLocalVariable(variable); + } + return p; + } + + protected boolean isLocalVariable(ExpressionTree variable) { + return variable instanceof IdentifierTree + && !TreeUtils.isFieldAccess(variable); + } + + @Override + public PurityResult visitCompoundAssignment( + CompoundAssignmentTree node, PurityResult p) { + ExpressionTree variable = node.getVariable(); + p = assignmentCheck(p, variable); + PurityResult r = scan(variable, p); + r = scan(node.getExpression(), r); + return r; + } + + @Override + public PurityResult visitUnary(UnaryTree node, PurityResult p) { + return scan(node.getExpression(), p); + } + + @Override + public PurityResult visitBinary(BinaryTree node, PurityResult p) { + PurityResult r = scan(node.getLeftOperand(), p); + r = scan(node.getRightOperand(), r); + return r; + } + + @Override + public PurityResult visitTypeCast(TypeCastTree node, PurityResult p) { + PurityResult r = scan(node.getExpression(), p); + return r; + } + + @Override + public PurityResult visitInstanceOf(InstanceOfTree node, PurityResult p) { + PurityResult r = scan(node.getExpression(), p); + return r; + } + + @Override + public PurityResult visitArrayAccess(ArrayAccessTree node, + PurityResult p) { + PurityResult r = scan(node.getExpression(), p); + r = scan(node.getIndex(), r); + return r; + } + + @Override + public PurityResult visitMemberSelect(MemberSelectTree node, + PurityResult p) { + return scan(node.getExpression(), p); + } + + @Override + public PurityResult visitMemberReference(MemberReferenceTree node, + PurityResult p) { + assert false : "this type of tree is unexpected here"; + return null; + } + + @Override + public PurityResult visitIdentifier(IdentifierTree node, PurityResult p) { + return p; + } + + @Override + public PurityResult visitLiteral(LiteralTree node, PurityResult p) { + return p; + } + } + +} diff --git a/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/util/PurityUtils.java b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/util/PurityUtils.java new file mode 100644 index 0000000000..d9186a3b12 --- /dev/null +++ b/third_party/checker_framework_dataflow/java/org/checkerframework/dataflow/util/PurityUtils.java @@ -0,0 +1,107 @@ +package org.checkerframework.dataflow.util; + +import java.util.ArrayList; +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; + +import org.checkerframework.dataflow.qual.Deterministic; +import org.checkerframework.dataflow.qual.Pure; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.dataflow.qual.Pure.Kind; + +import org.checkerframework.javacutil.AnnotationProvider; +import org.checkerframework.javacutil.InternalUtils; + +import com.sun.source.tree.MethodTree; + +/** + * An utility class for working with the {@link SideEffectFree}, {@link + * Deterministic}, and {@link Pure} annotations. + * + * @see SideEffectFree + * @see Deterministic + * @see Pure + * + * @author Stefan Heule + * + */ +public class PurityUtils { + + /** Does the method {@code tree} have any purity annotation? */ + public static boolean hasPurityAnnotation(AnnotationProvider provider, + MethodTree tree) { + return !getPurityKinds(provider, tree).isEmpty(); + } + + /** Does the method {@code methodElement} have any purity annotation? */ + public static boolean hasPurityAnnotation(AnnotationProvider provider, + Element methodElement) { + return !getPurityKinds(provider, methodElement).isEmpty(); + } + + /** Is the method {@code tree} deterministic? */ + public static boolean isDeterministic(AnnotationProvider provider, + MethodTree tree) { + Element methodElement = InternalUtils.symbol(tree); + return isDeterministic(provider, methodElement); + } + + /** Is the method {@code methodElement} deterministic? */ + public static boolean isDeterministic(AnnotationProvider provider, + Element methodElement) { + List<Kind> kinds = getPurityKinds(provider, methodElement); + return kinds.contains(Kind.DETERMINISTIC); + } + + /** Is the method {@code tree} side-effect-free? */ + public static boolean isSideEffectFree(AnnotationProvider provider, + MethodTree tree) { + Element methodElement = InternalUtils.symbol(tree); + return isSideEffectFree(provider, methodElement); + } + + /** Is the method {@code methodElement} side-effect-free? */ + public static boolean isSideEffectFree(AnnotationProvider provider, + Element methodElement) { + List<Kind> kinds = getPurityKinds(provider, methodElement); + return kinds.contains(Kind.SIDE_EFFECT_FREE); + } + + /** + * @return the types of purity of the method {@code tree}. + */ + public static List<Pure.Kind> getPurityKinds(AnnotationProvider provider, + MethodTree tree) { + Element methodElement = InternalUtils.symbol(tree); + return getPurityKinds(provider, methodElement); + } + + /** + * @return the types of purity of the method {@code methodElement}. + * TODO: should the return type be an EnumSet? + */ + public static List<Pure.Kind> getPurityKinds(AnnotationProvider provider, + Element methodElement) { + AnnotationMirror pureAnnotation = provider.getDeclAnnotation( + methodElement, Pure.class); + AnnotationMirror sefAnnotation = provider.getDeclAnnotation( + methodElement, SideEffectFree.class); + AnnotationMirror detAnnotation = provider.getDeclAnnotation( + methodElement, Deterministic.class); + + List<Pure.Kind> kinds = new ArrayList<>(); + if (pureAnnotation != null) { + kinds.add(Kind.DETERMINISTIC); + kinds.add(Kind.SIDE_EFFECT_FREE); + } + if (sefAnnotation != null) { + kinds.add(Kind.SIDE_EFFECT_FREE); + } + if (detAnnotation != null) { + kinds.add(Kind.DETERMINISTIC); + } + return kinds; + } +} |