diff options
Diffstat (limited to 'third_party/checker_framework_javacutil')
20 files changed, 5772 insertions, 0 deletions
diff --git a/third_party/checker_framework_javacutil/LICENSE.txt b/third_party/checker_framework_javacutil/LICENSE.txt new file mode 100644 index 0000000000..c4b232d9bc --- /dev/null +++ b/third_party/checker_framework_javacutil/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_javacutil/java/org/checkerframework/javacutil/AbstractTypeProcessor.java b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/AbstractTypeProcessor.java new file mode 100644 index 0000000000..8db8da333c --- /dev/null +++ b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/AbstractTypeProcessor.java @@ -0,0 +1,220 @@ +package org.checkerframework.javacutil; + +import java.util.HashSet; +import java.util.Set; + +import javax.annotation.processing.*; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.ElementFilter; + +import com.sun.tools.javac.main.JavaCompiler; +import com.sun.tools.javac.comp.CompileStates.CompileState; +import com.sun.tools.javac.processing.JavacProcessingEnvironment; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.Log; + +import com.sun.source.tree.ClassTree; +import com.sun.source.util.JavacTask; +import com.sun.source.util.TaskEvent; +import com.sun.source.util.TaskListener; +import com.sun.source.util.TreePath; +import com.sun.source.util.Trees; + +/** + * This class is an abstract annotation processor designed to be a + * convenient superclass for concrete "type processors", processors that + * require the type information in the processed source. + * + * <p>Type processing occurs in one round after the tool (e.g. Java compiler) + * analyzes the source (all sources taken as input to the tool and sources + * generated by other annotation processors). + * + * <p>The tool infrastructure will interact with classes extending this abstract + * class as follows. + * + * <p> + * 1-3 are Identical to the {@link Processor} life cycle. + * 4-5 are unique to {@code AbstractTypeProcessor} subclasses. + * + * <ol> + * + * <li>If an existing {@code Processor} object is not being used, to + * create an instance of a processor the tool calls the no-arg + * constructor of the processor class. + * + * <li>Next, the tool calls the {@link #init init} method with + * an appropriate {@code ProcessingEnvironment}. + * + * <li>Afterwards, the tool calls {@link #getSupportedAnnotationTypes + * getSupportedAnnotationTypes}, {@link #getSupportedOptions + * getSupportedOptions}, and {@link #getSupportedSourceVersion + * getSupportedSourceVersion}. These methods are only called once per + * run, not on each round. + * + * + * <li>For each class containing a supported annotation, the tool calls + * {@link #typeProcess(TypeElement, TreePath) typeProcess} method on the + * {@code Processor}. The class is guaranteed to be type-checked Java code + * and all the tree type and symbol information is resolved. + * + * <li>Finally, the tools calls the + * {@link #typeProcessingOver() typeProcessingOver} method + * on the {@code Processor}. + * + * </ol> + * + * <p>The tool is permitted to ask type processors to process a class once + * it is analyzed before the rest of classes are analyzed. The tool is also + * permitted to stop type processing immediately if any errors are raised, + * without invoking {@code typeProcessingOver} + * + * <p>A subclass may override any of the methods in this class, as long as the + * general {@link javax.annotation.processing.Processor Processor} + * contract is obeyed, with one notable exception. + * {@link #process(Set, RoundEnvironment)} may not be overridden, as it + * is called during the declaration annotation phase before classes are analyzed. + * + * @author Mahmood Ali + * @author Werner Dietl + */ +public abstract class AbstractTypeProcessor extends AbstractProcessor { + /** + * The set of fully-qualified element names that should be type-checked. + * We store the names of the elements, in order to prevent + * possible confusion between different Element instantiations. + */ + private final Set<Name> elements = new HashSet<Name>(); + + /** + * Method {@link #typeProcessingStart()} must be invoked exactly once, + * before any invocation of {@link #typeProcess(TypeElement, TreePath)}. + */ + private boolean hasInvokedTypeProcessingStart = false; + + /** + * Method {@link #typeProcessingOver()} must be invoked exactly once, + * after the last invocation of {@link #typeProcess(TypeElement, TreePath)}. + */ + private static boolean hasInvokedTypeProcessingOver = false; + + /** + * The TaskListener registered for completion of attribution. + */ + private final AttributionTaskListener listener = new AttributionTaskListener(); + + /** + * Constructor for subclasses to call. + */ + protected AbstractTypeProcessor() { } + + /** + * {@inheritDoc} + * + * Register a TaskListener that will get called after FLOW. + */ + @Override + public void init(ProcessingEnvironment env) { + super.init(env); + JavacTask.instance(env).addTaskListener(listener); + Context ctx = ((JavacProcessingEnvironment) processingEnv).getContext(); + JavaCompiler compiler = JavaCompiler.instance(ctx); + compiler.shouldStopPolicyIfNoError = CompileState.max(compiler.shouldStopPolicyIfNoError, + CompileState.FLOW); + } + + /** + * The use of this method is obsolete in type processors. The method is + * called during declaration annotation processing phase only. + * It registers the names of elements to process. + */ + @Override + public final boolean process(Set<? extends TypeElement> annotations, + RoundEnvironment roundEnv) { + for (TypeElement elem : ElementFilter.typesIn(roundEnv.getRootElements())) { + elements.add(elem.getQualifiedName()); + } + return false; + } + + /** + * A method to be called once before the first call to typeProcess. + * + * <p>Subclasses may override this method to do any initialization work. + */ + public void typeProcessingStart() {} + + /** + * Processes a fully-analyzed class that contains a supported annotation + * (see {@link #getSupportedAnnotationTypes()}). + * + * <p>The passed class is always valid type-checked Java code. + * + * @param element element of the analyzed class + * @param tree the tree path to the element, with the leaf being a + * {@link ClassTree} + */ + public abstract void typeProcess(TypeElement element, TreePath tree); + + /** + * A method to be called once all the classes are processed and no error + * is reported. + * + * <p>Subclasses may override this method to do any aggregate analysis + * (e.g. generate report, persistence) or resource deallocation. + * + * <p>If an error (a Java error or a processor error) is reported, this + * method is not guaranteed to be invoked. + */ + public void typeProcessingOver() { } + + /** + * A task listener that invokes the processor whenever a class is fully + * analyzed. + */ + private final class AttributionTaskListener implements TaskListener { + + @Override + public void finished(TaskEvent e) { + if (e.getKind() != TaskEvent.Kind.ANALYZE) { + return; + } + + if (!hasInvokedTypeProcessingStart) { + typeProcessingStart(); + hasInvokedTypeProcessingStart = true; + } + + Log log = Log.instance(((JavacProcessingEnvironment) processingEnv).getContext()); + + if (!hasInvokedTypeProcessingOver && elements.isEmpty() && log.nerrors == 0) { + typeProcessingOver(); + hasInvokedTypeProcessingOver = true; + } + + if (e.getTypeElement() == null) { + throw new AssertionError("event task without a type element"); + } + if (e.getCompilationUnit() == null) { + throw new AssertionError("event task without compilation unit"); + } + + if (!elements.remove(e.getTypeElement().getQualifiedName())) { + return; + } + + TypeElement elem = e.getTypeElement(); + TreePath p = Trees.instance(processingEnv).getPath(elem); + + typeProcess(elem, p); + + if (!hasInvokedTypeProcessingOver && elements.isEmpty() && log.nerrors == 0) { + typeProcessingOver(); + hasInvokedTypeProcessingOver = true; + } + } + + @Override + public void started(TaskEvent e) { } + } +} diff --git a/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/AnnotationProvider.java b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/AnnotationProvider.java new file mode 100644 index 0000000000..580be40ef4 --- /dev/null +++ b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/AnnotationProvider.java @@ -0,0 +1,38 @@ +package org.checkerframework.javacutil; + +import java.lang.annotation.Annotation; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; + +import com.sun.source.tree.Tree; + +/** + * An implementation of AnnotationProvider returns annotations on + * Java AST elements. + */ +public interface AnnotationProvider { + + /** + * Returns the actual annotation mirror used to annotate this type, + * whose name equals the passed annotationName if one exists, null otherwise. + * + * @param anno annotation class + * @return the annotation mirror for anno + */ + public AnnotationMirror getDeclAnnotation(Element elt, + Class<? extends Annotation> anno); + + /** + * Return the annotation on {@code tree} that has the class + * {@code target}. If no annotation for the given target class exists, + * the result is {@code null} + * + * @param tree + * The tree of which the annotation is returned + * @param target + * The class of the annotation + */ + public AnnotationMirror getAnnotationMirror(Tree tree, + Class<? extends Annotation> target); +} diff --git a/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/AnnotationUtils.java b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/AnnotationUtils.java new file mode 100644 index 0000000000..19eb74df49 --- /dev/null +++ b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/AnnotationUtils.java @@ -0,0 +1,612 @@ +package org.checkerframework.javacutil; + +/*>>> +import org.checkerframework.dataflow.qual.Pure; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.checker.nullness.qual.*; +import org.checkerframework.checker.interning.qual.*; +*/ + + +import java.lang.annotation.Annotation; +import java.lang.annotation.Inherited; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; +import javax.lang.model.util.Elements; + +import com.sun.source.tree.AnnotationTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.ModifiersTree; +import com.sun.tools.javac.code.Symbol.VarSymbol; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.model.JavacElements; +/** + * A utility class for working with annotations. + */ +public class AnnotationUtils { + + // Class cannot be instantiated. + private AnnotationUtils() { throw new AssertionError("Class AnnotationUtils cannot be instantiated."); } + + // TODO: hack to clear out static state. + // {@link org.checkerframework.qualframework.util.QualifierContext} should + // handle instantiation of utility classes. + public static void clear() { + annotationsFromNames.clear(); + annotationMirrorNames.clear(); + annotationMirrorSimpleNames.clear(); + annotationClassNames.clear(); + } + + // ********************************************************************** + // Factory Methods to create instances of AnnotationMirror + // ********************************************************************** + + /** Caching for annotation creation. */ + private static final Map<CharSequence, AnnotationMirror> annotationsFromNames + = new HashMap<CharSequence, AnnotationMirror>(); + + + private static final int ANNOTATION_CACHE_SIZE = 500; + + /** + * Cache names of AnnotationMirrors for faster access. Values in + * the map are interned Strings, so they can be compared with ==. + */ + private static final Map<AnnotationMirror, /*@Interned*/ String> annotationMirrorNames + = CollectionUtils.createLRUCache(ANNOTATION_CACHE_SIZE); + + /** + * Cache simple names of AnnotationMirrors for faster access. Values in + * the map are interned Strings, so they can be compared with ==. + */ + private static final Map<AnnotationMirror, /*@Interned*/ String> annotationMirrorSimpleNames + = CollectionUtils.createLRUCache(ANNOTATION_CACHE_SIZE); + + /** + * Cache names of classes representing AnnotationMirrors for + * faster access. Values in the map are interned Strings, so they + * can be compared with ==. + */ + private static final Map<Class<? extends Annotation>, /*@Interned*/ String> annotationClassNames + = new HashMap<Class<? extends Annotation>, /*@Interned*/ String>(); + + /** + * Creates an {@link AnnotationMirror} given by a particular + * fully-qualified name. getElementValues on the result returns an + * empty map. + * + * @param elements the element utilities to use + * @param name the name of the annotation to create + * @return an {@link AnnotationMirror} of type {@code} name + */ + public static AnnotationMirror fromName(Elements elements, CharSequence name) { + if (annotationsFromNames.containsKey(name)) { + return annotationsFromNames.get(name); + } + final DeclaredType annoType = typeFromName(elements, name); + if (annoType == null) { + return null; + } + if (annoType.asElement().getKind() != ElementKind.ANNOTATION_TYPE) { + ErrorReporter.errorAbort(annoType + " is not an annotation"); + return null; // dead code + } + AnnotationMirror result = new AnnotationMirror() { + String toString = "@" + annoType; + + @Override + public DeclaredType getAnnotationType() { + return annoType; + } + @Override + public Map<? extends ExecutableElement, ? extends AnnotationValue> + getElementValues() { + return Collections.emptyMap(); + } + /*@SideEffectFree*/ + @Override + public String toString() { + return toString; + } + }; + annotationsFromNames.put(name, result); + return result; + } + + /** + * Creates an {@link AnnotationMirror} given by a particular annotation + * class. + * + * @param elements the element utilities to use + * @param clazz the annotation class + * @return an {@link AnnotationMirror} of type given type + */ + public static AnnotationMirror fromClass(Elements elements, Class<? extends Annotation> clazz) { + return fromName(elements, clazz.getCanonicalName()); + } + + /** + * A utility method that converts a {@link CharSequence} (usually a {@link + * String}) into a {@link TypeMirror} named thereby. + * + * @param elements the element utilities to use + * @param name the name of a type + * @return the {@link TypeMirror} corresponding to that name + */ + private static DeclaredType typeFromName(Elements elements, CharSequence name) { + /*@Nullable*/ TypeElement typeElt = elements.getTypeElement(name); + if (typeElt == null) { + return null; + } + + return (DeclaredType) typeElt.asType(); + } + + + // ********************************************************************** + // Helper methods to handle annotations. mainly workaround + // AnnotationMirror.equals undesired property + // (I think the undesired property is that it's reference equality.) + // ********************************************************************** + + /** + * @return the fully-qualified name of an annotation as a String + */ + public static final /*@Interned*/ String annotationName(AnnotationMirror annotation) { + if (annotationMirrorNames.containsKey(annotation)) { + return annotationMirrorNames.get(annotation); + } + + final DeclaredType annoType = annotation.getAnnotationType(); + final TypeElement elm = (TypeElement) annoType.asElement(); + /*@Interned*/ String name = elm.getQualifiedName().toString().intern(); + annotationMirrorNames.put(annotation, name); + return name; + } + + /** + * @return the simple name of an annotation as a String + */ + public static String annotationSimpleName(AnnotationMirror annotation) { + if (annotationMirrorSimpleNames.containsKey(annotation)) { + return annotationMirrorSimpleNames.get(annotation); + } + + final DeclaredType annoType = annotation.getAnnotationType(); + final TypeElement elm = (TypeElement) annoType.asElement(); + /*@Interned*/ String name = elm.getSimpleName().toString().intern(); + annotationMirrorSimpleNames.put(annotation, name); + return name; + } + + /** + * Checks if both annotations are the same. + * + * Returns true iff both annotations are of the same type and have the + * same annotation values. This behavior differs from + * {@code AnnotationMirror.equals(Object)}. The equals method returns + * true iff both annotations are the same and annotate the same annotation + * target (e.g. field, variable, etc). + * + * @return true iff a1 and a2 are the same annotation + */ + public static boolean areSame(/*@Nullable*/ AnnotationMirror a1, /*@Nullable*/ AnnotationMirror a2) { + if (a1 != null && a2 != null) { + if (annotationName(a1) != annotationName(a2)) { + return false; + } + + Map<? extends ExecutableElement, ? extends AnnotationValue> elval1 = getElementValuesWithDefaults(a1); + Map<? extends ExecutableElement, ? extends AnnotationValue> elval2 = getElementValuesWithDefaults(a2); + + return elval1.toString().equals(elval2.toString()); + } + + // only true, iff both are null + return a1 == a2; + } + + /** + * @see #areSame(AnnotationMirror, AnnotationMirror) + * @return true iff a1 and a2 have the same annotation type + */ + public static boolean areSameIgnoringValues(AnnotationMirror a1, AnnotationMirror a2) { + if (a1 != null && a2 != null) { + return annotationName(a1) == annotationName(a2); + } + return a1 == a2; + } + + /** + * Checks that the annotation {@code am} has the name {@code aname}. Values + * are ignored. + */ + public static boolean areSameByName(AnnotationMirror am, /*@Interned*/ String aname) { + // Both strings are interned. + return annotationName(am) == aname; + } + + /** + * Checks that the annotation {@code am} has the name of {@code anno}. + * Values are ignored. + */ + public static boolean areSameByClass(AnnotationMirror am, + Class<? extends Annotation> anno) { + /*@Interned*/ String canonicalName; + if (annotationClassNames.containsKey(anno)) { + canonicalName = annotationClassNames.get(anno); + } else { + canonicalName = anno.getCanonicalName().intern(); + annotationClassNames.put(anno, canonicalName); + } + return areSameByName(am, canonicalName); + } + + /** + * Checks that two collections contain the same annotations. + * + * @return true iff c1 and c2 contain the same annotations + */ + public static boolean areSame(Collection<? extends AnnotationMirror> c1, Collection<? extends AnnotationMirror> c2) { + if (c1.size() != c2.size()) { + return false; + } + if (c1.size() == 1) { + return areSame(c1.iterator().next(), c2.iterator().next()); + } + + Set<AnnotationMirror> s1 = createAnnotationSet(); + Set<AnnotationMirror> s2 = createAnnotationSet(); + s1.addAll(c1); + s2.addAll(c2); + + // depend on the fact that Set is an ordered set. + Iterator<AnnotationMirror> iter1 = s1.iterator(); + Iterator<AnnotationMirror> iter2 = s2.iterator(); + + while (iter1.hasNext()) { + AnnotationMirror anno1 = iter1.next(); + AnnotationMirror anno2 = iter2.next(); + if (!areSame(anno1, anno2)) { + return false; + } + } + return true; + } + + /** + * Checks that the collection contains the annotation. + * Using Collection.contains does not always work, because it + * does not use areSame for comparison. + * + * @return true iff c contains anno, according to areSame + */ + public static boolean containsSame(Collection<? extends AnnotationMirror> c, AnnotationMirror anno) { + for (AnnotationMirror an : c) { + if (AnnotationUtils.areSame(an, anno)) { + return true; + } + } + return false; + } + + /** + * Checks that the collection contains the annotation. + * Using Collection.contains does not always work, because it + * does not use areSame for comparison. + * + * @return true iff c contains anno, according to areSameByClass + */ + public static boolean containsSameByClass(Collection<? extends AnnotationMirror> c, Class<? extends Annotation> anno) { + for (AnnotationMirror an : c) { + if (AnnotationUtils.areSameByClass(an, anno)) { + return true; + } + } + return false; + } + + /** + * Checks that the collection contains the annotation ignoring values. + * Using Collection.contains does not always work, because it + * does not use areSameIgnoringValues for comparison. + * + * @return true iff c contains anno, according to areSameIgnoringValues + */ + public static boolean containsSameIgnoringValues(Collection<? extends AnnotationMirror> c, AnnotationMirror anno) { + for (AnnotationMirror an : c) { + if (AnnotationUtils.areSameIgnoringValues(an, anno)) { + return true; + } + } + return false; + } + + private static final Comparator<AnnotationMirror> ANNOTATION_ORDERING + = new Comparator<AnnotationMirror>() { + @Override + public int compare(AnnotationMirror a1, AnnotationMirror a2) { + String n1 = a1.toString(); + String n2 = a2.toString(); + + return n1.compareTo(n2); + } + }; + + /** + * provide ordering for {@link AnnotationMirror} based on their fully + * qualified name. The ordering ignores annotation values when ordering. + * + * The ordering is meant to be used as {@link TreeSet} or {@link TreeMap} + * ordering. A {@link Set} should not contain two annotations that only + * differ in values. + */ + public static Comparator<AnnotationMirror> annotationOrdering() { + return ANNOTATION_ORDERING; + } + + /** + * Create a map suitable for storing {@link AnnotationMirror} as keys. + * + * It can store one instance of {@link AnnotationMirror} of a given + * declared type, regardless of the annotation element values. + * + * @param <V> the value of the map + * @return a new map with {@link AnnotationMirror} as key + */ + public static <V> Map<AnnotationMirror, V> createAnnotationMap() { + return new TreeMap<AnnotationMirror, V>(annotationOrdering()); + } + + /** + * Constructs a {@link Set} suitable for storing {@link AnnotationMirror}s. + * + * It stores at most once instance of {@link AnnotationMirror} of a given + * type, regardless of the annotation element values. + * + * @return a new set to store {@link AnnotationMirror} as element + */ + public static Set<AnnotationMirror> createAnnotationSet() { + return new TreeSet<AnnotationMirror>(annotationOrdering()); + } + + /** Returns true if the given annotation has a @Inherited meta-annotation. */ + public static boolean hasInheritedMeta(AnnotationMirror anno) { + return anno.getAnnotationType().asElement().getAnnotation(Inherited.class) != null; + } + + + // ********************************************************************** + // Extractors for annotation values + // ********************************************************************** + + /** + * Returns the values of an annotation's attributes, including defaults. + * The method with the same name in JavacElements cannot be used directly, + * because it includes a cast to Attribute.Compound, which doesn't hold + * for annotations generated by the Checker Framework. + * + * @see AnnotationMirror#getElementValues() + * @see JavacElements#getElementValuesWithDefaults(AnnotationMirror) + * + * @param ad annotation to examine + * @return the values of the annotation's elements, including defaults + */ + public static Map<? extends ExecutableElement, ? extends AnnotationValue> + getElementValuesWithDefaults(AnnotationMirror ad) { + Map<ExecutableElement, AnnotationValue> valMap + = new HashMap<ExecutableElement, AnnotationValue>(); + if (ad.getElementValues() != null) { + valMap.putAll(ad.getElementValues()); + } + for (ExecutableElement meth : + ElementFilter.methodsIn(ad.getAnnotationType().asElement().getEnclosedElements())) { + AnnotationValue defaultValue = meth.getDefaultValue(); + if (defaultValue != null && !valMap.containsKey(meth)) { + valMap.put(meth, defaultValue); + } + } + return valMap; + } + + + /** + * Verify whether the attribute with the name {@code name} exists in + * the annotation {@code anno}. + * + * @param anno the annotation to examine + * @param name the name of the attribute + * @return whether the attribute exists in anno + */ + public static <T> boolean hasElementValue(AnnotationMirror anno, CharSequence name) { + Map<? extends ExecutableElement, ? extends AnnotationValue> valmap = anno.getElementValues(); + for (ExecutableElement elem : valmap.keySet()) { + if (elem.getSimpleName().contentEquals(name)) { + return true; + } + } + return false; + } + + /** + * Get the attribute with the name {@code name} of the annotation + * {@code anno}. The result is expected to have type {@code expectedType}. + * + * <p> + * <em>Note 1</em>: The method does not work well for attributes of an array + * type (as it would return a list of {@link AnnotationValue}s). Use + * {@code getElementValueArray} instead. + * + * <p> + * <em>Note 2</em>: The method does not work for attributes of an enum type, + * as the AnnotationValue is a VarSymbol and would be cast to the enum type, + * which doesn't work. Use {@code getElementValueEnum} instead. + * + * + * @param anno the annotation to disassemble + * @param name the name of the attribute to access + * @param expectedType the expected type used to cast the return type + * @param useDefaults whether to apply default values to the attribute + * @return the value of the attribute with the given name + */ + public static <T> T getElementValue(AnnotationMirror anno, CharSequence name, + Class<T> expectedType, boolean useDefaults) { + Map<? extends ExecutableElement, ? extends AnnotationValue> valmap; + if (useDefaults) { + valmap = getElementValuesWithDefaults(anno); + } else { + valmap = anno.getElementValues(); + } + for (ExecutableElement elem : valmap.keySet()) { + if (elem.getSimpleName().contentEquals(name)) { + AnnotationValue val = valmap.get(elem); + return expectedType.cast(val.getValue()); + } + } + ErrorReporter.errorAbort("No element with name \'" + name + "\' in annotation " + anno); + return null; // dead code + } + + /** + * Version that is suitable for Enum elements. + */ + public static <T extends Enum<T>> T getElementValueEnum( + AnnotationMirror anno, CharSequence name, Class<T> t, + boolean useDefaults) { + VarSymbol vs = getElementValue(anno, name, VarSymbol.class, useDefaults); + T value = Enum.valueOf(t, vs.getSimpleName().toString()); + return value; + } + + /** + * Get the attribute with the name {@code name} of the annotation + * {@code anno}, where the attribute has an array type. One element of the + * result is expected to have type {@code expectedType}. + * + * Parameter useDefaults is used to determine whether default values + * should be used for annotation values. Finding defaults requires + * more computation, so should be false when no defaulting is needed. + * + * @param anno the annotation to disassemble + * @param name the name of the attribute to access + * @param expectedType the expected type used to cast the return type + * @param useDefaults whether to apply default values to the attribute + * @return the value of the attribute with the given name + */ + public static <T> List<T> getElementValueArray(AnnotationMirror anno, + CharSequence name, Class<T> expectedType, boolean useDefaults) { + @SuppressWarnings("unchecked") + List<AnnotationValue> la = getElementValue(anno, name, List.class, useDefaults); + List<T> result = new ArrayList<T>(la.size()); + for (AnnotationValue a : la) { + result.add(expectedType.cast(a.getValue())); + } + return result; + } + + /** + * Get the attribute with the name {@code name} of the annotation + * {@code anno}, or the default value if no attribute is present explicitly, + * where the attribute has an array type and the elements are {@code Enum}s. + * One element of the result is expected to have type {@code expectedType}. + */ + public static <T extends Enum<T>> List<T> getElementValueEnumArray( + AnnotationMirror anno, CharSequence name, Class<T> t, + boolean useDefaults) { + @SuppressWarnings("unchecked") + List<AnnotationValue> la = getElementValue(anno, name, List.class, useDefaults); + List<T> result = new ArrayList<T>(la.size()); + for (AnnotationValue a : la) { + T value = Enum.valueOf(t, a.getValue().toString()); + result.add(value); + } + return result; + } + + /** + * Get the Name of the class that is referenced by attribute 'name'. + * This is a convenience method for the most common use-case. + * Like getElementValue(anno, name, ClassType.class).getQualifiedName(), but + * this method ensures consistent use of the qualified name. + */ + public static Name getElementValueClassName(AnnotationMirror anno, CharSequence name, + boolean useDefaults) { + Type.ClassType ct = getElementValue(anno, name, Type.ClassType.class, useDefaults); + // TODO: Is it a problem that this returns the type parameters too? Should I cut them off? + return ct.asElement().getQualifiedName(); + } + + /** + * Get the Class that is referenced by attribute 'name'. + * This method uses Class.forName to load the class. It returns + * null if the class wasn't found. + */ + public static Class<?> getElementValueClass(AnnotationMirror anno, CharSequence name, + boolean useDefaults) { + Name cn = getElementValueClassName(anno, name, useDefaults); + try { + Class<?> cls = Class.forName(cn.toString()); + return cls; + } catch (ClassNotFoundException e) { + ErrorReporter.errorAbort("Could not load class '" + cn + "' for field '" + name + + "' in annotation " + anno, e); + return null; // dead code + } + } + + /** + * See checkers.types.QualifierHierarchy#updateMappingToMutableSet(QualifierHierarchy, Map, Object, AnnotationMirror) + * (Not linked because it is in an independent project. + */ + public static <T> void updateMappingToImmutableSet(Map<T, Set<AnnotationMirror>> map, + T key, Set<AnnotationMirror> newQual) { + + Set<AnnotationMirror> result = AnnotationUtils.createAnnotationSet(); + // TODO: if T is also an AnnotationMirror, should we use areSame? + if (!map.containsKey(key)) { + result.addAll(newQual); + } else { + result.addAll(map.get(key)); + result.addAll(newQual); + } + map.put(key, Collections.unmodifiableSet(result)); + } + + /** + * Returns the annotations explicitly written on a constructor result. + * Callers should check that {@code constructorDeclaration} is in fact a declaration + * of a constructor. + * + * @param constructorDeclaration declaration tree of constructor + * @return set of annotations explicit on the resulting type of the constructor + */ + public static Set<AnnotationMirror> getExplicitAnnotationsOnConstructorResult(MethodTree constructorDeclaration) { + Set<AnnotationMirror> annotationSet = AnnotationUtils.createAnnotationSet(); + ModifiersTree modifiersTree = constructorDeclaration.getModifiers(); + if (modifiersTree != null) { + List<? extends AnnotationTree> annotationTrees = modifiersTree.getAnnotations(); + annotationSet.addAll(InternalUtils.annotationsFromTypeAnnotationTrees(annotationTrees)); + } + return annotationSet; + } +} diff --git a/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/BasicAnnotationProvider.java b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/BasicAnnotationProvider.java new file mode 100644 index 0000000000..b885b5cbea --- /dev/null +++ b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/BasicAnnotationProvider.java @@ -0,0 +1,35 @@ +package org.checkerframework.javacutil; + +import java.lang.annotation.Annotation; + +import java.util.List; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; + +import com.sun.source.tree.Tree; + +public class BasicAnnotationProvider implements AnnotationProvider { + + @Override + public AnnotationMirror getDeclAnnotation(Element elt, + Class<? extends Annotation> anno) { + List<? extends AnnotationMirror> annotationMirrors = elt + .getAnnotationMirrors(); + + // Then look at the real annotations. + for (AnnotationMirror am : annotationMirrors) { + if (AnnotationUtils.areSameByClass(am, anno)) { + return am; + } + } + + return null; + } + + @Override + public AnnotationMirror getAnnotationMirror(Tree tree, + Class<? extends Annotation> target) { + return null; + } +} diff --git a/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/BasicTypeProcessor.java b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/BasicTypeProcessor.java new file mode 100644 index 0000000000..1fa1e6cb82 --- /dev/null +++ b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/BasicTypeProcessor.java @@ -0,0 +1,42 @@ +package org.checkerframework.javacutil; + +import javax.lang.model.element.TypeElement; + +import com.sun.source.tree.CompilationUnitTree; + +import com.sun.source.util.TreePath; +import com.sun.source.util.TreePathScanner; + +/** + * Process the types in an AST in a trivial manner, with hooks for derived classes + * to actually do something. + */ +public abstract class BasicTypeProcessor extends AbstractTypeProcessor { + /** The source tree that's being scanned. */ + protected CompilationUnitTree currentRoot; + + /** + * Create a TreePathScanner at the given root. + */ + protected abstract TreePathScanner<?, ?> createTreePathScanner(CompilationUnitTree root); + + /** + * Visit the tree path for the type element. + */ + @Override + public void typeProcess(TypeElement e, TreePath p) { + currentRoot = p.getCompilationUnit(); + + TreePathScanner<?, ?> scanner = null; + try { + scanner = createTreePathScanner(currentRoot); + scanner.scan(p, null); + } catch (Throwable t) { + System.err.println("BasicTypeProcessor.typeProcess: unexpected Throwable (" + + t.getClass().getSimpleName() + ") when processing " + + currentRoot.getSourceFile().getName() + + (t.getMessage()!=null ? "; message: " + t.getMessage() : "")); + } + } + +} diff --git a/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/CollectionUtils.java b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/CollectionUtils.java new file mode 100644 index 0000000000..eaaa69115d --- /dev/null +++ b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/CollectionUtils.java @@ -0,0 +1,26 @@ +package org.checkerframework.javacutil; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Utility methods related to Java Collections + */ +public class CollectionUtils { + + /** + * A Utility method for creating LRU cache + * @param size size of the cache + * @return a new cache with the provided size + */ + public static <K, V> Map<K, V> createLRUCache(final int size) { + return new LinkedHashMap<K, V>() { + + private static final long serialVersionUID = 5261489276168775084L; + @Override + protected boolean removeEldestEntry(Map.Entry<K, V> entry) { + return size() > size; + } + }; + } +} diff --git a/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/ElementUtils.java b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/ElementUtils.java new file mode 100644 index 0000000000..19f0500585 --- /dev/null +++ b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/ElementUtils.java @@ -0,0 +1,425 @@ +package org.checkerframework.javacutil; + +/*>>> +import org.checkerframework.checker.nullness.qual.Nullable; +*/ + +import static com.sun.tools.javac.code.Flags.ABSTRACT; +import static com.sun.tools.javac.code.Flags.EFFECTIVELY_FINAL; +import static com.sun.tools.javac.code.Flags.FINAL; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Deque; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.Name; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; +import javax.lang.model.util.Elements; + +import com.sun.tools.javac.code.Symbol; + +/** + * A Utility class for analyzing {@code Element}s. + */ +public class ElementUtils { + + // Class cannot be instantiated. + private ElementUtils() { throw new AssertionError("Class ElementUtils cannot be instantiated."); } + + /** + * Returns the innermost type element enclosing the given element + * + * @param elem the enclosed element of a class + * @return the innermost type element + */ + public static TypeElement enclosingClass(final Element elem) { + Element result = elem; + while (result != null && !result.getKind().isClass() + && !result.getKind().isInterface()) { + /*@Nullable*/ Element encl = result.getEnclosingElement(); + result = encl; + } + return (TypeElement) result; + } + + /** + * Returns the innermost package element enclosing the given element. + * The same effect as {@link javax.lang.model.util.Elements#getPackageOf(Element)}. + * Returns the element itself if it is a package. + * + * @param elem the enclosed element of a package + * @return the innermost package element + * + */ + public static PackageElement enclosingPackage(final Element elem) { + Element result = elem; + while (result != null && result.getKind() != ElementKind.PACKAGE) { + /*@Nullable*/ Element encl = result.getEnclosingElement(); + result = encl; + } + return (PackageElement) result; + } + + /** + * Returns the "parent" package element for the given package element. + * For package "A.B" it gives "A". + * For package "A" it gives the default package. + * For the default package it returns null; + * + * Note that packages are not enclosed within each other, we have to manually climb + * the namespaces. Calling "enclosingPackage" on a package element returns the + * package element itself again. + * + * @param elem the package to start from + * @return the parent package element + * + */ + public static PackageElement parentPackage(final Elements e, final PackageElement elem) { + // The following might do the same thing: + // ((Symbol) elt).owner; + // TODO: verify and see whether the change is worth it. + String fqnstart = elem.getQualifiedName().toString(); + String fqn = fqnstart; + if (fqn != null && !fqn.isEmpty() && fqn.contains(".")) { + fqn = fqn.substring(0, fqn.lastIndexOf('.')); + return e.getPackageElement(fqn); + } + return null; + } + + /** + * Returns true if the element is a static element: whether it is a static + * field, static method, or static class + * + * @return true if element is static + */ + public static boolean isStatic(Element element) { + return element.getModifiers().contains(Modifier.STATIC); + } + + /** + * Returns true if the element is a final element: a final field, final + * method, or final class + * + * @return true if the element is final + */ + public static boolean isFinal(Element element) { + return element.getModifiers().contains(Modifier.FINAL); + } + + /** + * Returns true if the element is a effectively final element. + * + * @return true if the element is effectively final + */ + public static boolean isEffectivelyFinal(Element element) { + Symbol sym = (Symbol) element; + if (sym.getEnclosingElement().getKind() == ElementKind.METHOD && + (sym.getEnclosingElement().flags() & ABSTRACT) != 0) { + return true; + } + return (sym.flags() & (FINAL | EFFECTIVELY_FINAL)) != 0; + } + + /** + * Returns the {@code TypeMirror} for usage of Element as a value. It + * returns the return type of a method element, the class type of a + * constructor, or simply the type mirror of the element itself. + * + * @return the type for the element used as a value + */ + public static TypeMirror getType(Element element) { + if (element.getKind() == ElementKind.METHOD) { + return ((ExecutableElement)element).getReturnType(); + } else if (element.getKind() == ElementKind.CONSTRUCTOR) { + return enclosingClass(element).asType(); + } else { + return element.asType(); + } + } + + /** + * Returns the qualified name of the inner most class enclosing + * the provided {@code Element} + * + * @param element + * an element enclosed by a class, or a + * {@code TypeElement} + * @return the qualified {@code Name} of the innermost class + * enclosing the element + */ + public static /*@Nullable*/ Name getQualifiedClassName(Element element) { + if (element.getKind() == ElementKind.PACKAGE) { + PackageElement elem = (PackageElement) element; + return elem.getQualifiedName(); + } + + TypeElement elem = enclosingClass(element); + if (elem == null) { + return null; + } + + return elem.getQualifiedName(); + } + + /** + * Returns a verbose name that identifies the element. + */ + public static String getVerboseName(Element elt) { + if (elt.getKind() == ElementKind.PACKAGE || + elt.getKind().isClass() || + elt.getKind().isInterface()) { + return getQualifiedClassName(elt).toString(); + } else { + return getQualifiedClassName(elt) + "." + elt.toString(); + } + } + + /** + * Check if the element is an element for 'java.lang.Object' + * + * @param element the type element + * @return true iff the element is java.lang.Object element + */ + public static boolean isObject(TypeElement element) { + return element.getQualifiedName().contentEquals("java.lang.Object"); + } + + /** + * Returns true if the element is a constant time reference + */ + public static boolean isCompileTimeConstant(Element elt) { + return elt != null + && (elt.getKind() == ElementKind.FIELD || elt.getKind() == ElementKind.LOCAL_VARIABLE) + && ((VariableElement)elt).getConstantValue() != null; + } + + /** + * Returns true if the element is declared in ByteCode. + * Always return false if elt is a package. + */ + public static boolean isElementFromByteCode(Element elt) { + if (elt == null) { + return false; + } + + if (elt instanceof Symbol.ClassSymbol) { + Symbol.ClassSymbol clss = (Symbol.ClassSymbol) elt; + if (null != clss.classfile) { + // The class file could be a .java file + return clss.classfile.getName().endsWith(".class"); + } else { + return false; + } + } + return isElementFromByteCode(elt.getEnclosingElement(), elt); + } + + /** + * Returns true if the element is declared in ByteCode. + * Always return false if elt is a package. + */ + private static boolean isElementFromByteCode(Element elt, Element orig) { + if (elt == null) { + return false; + } + if (elt instanceof Symbol.ClassSymbol) { + Symbol.ClassSymbol clss = (Symbol.ClassSymbol) elt; + if (null != clss.classfile) { + // The class file could be a .java file + return (clss.classfile.getName().endsWith(".class") || + clss.classfile.getName().endsWith(".class)") || + clss.classfile.getName().endsWith(".class)]")); + } else { + return false; + } + } + return isElementFromByteCode(elt.getEnclosingElement(), elt); + } + + /** + * Returns the field of the class + */ + public static VariableElement findFieldInType(TypeElement type, String name) { + for (VariableElement field: ElementFilter.fieldsIn(type.getEnclosedElements())) { + if (field.getSimpleName().toString().equals(name)) { + return field; + } + } + return null; + } + + public static Set<VariableElement> findFieldsInType(TypeElement type, Collection<String> names) { + Set<VariableElement> results = new HashSet<VariableElement>(); + for (VariableElement field: ElementFilter.fieldsIn(type.getEnclosedElements())) { + if (names.contains(field.getSimpleName().toString())) { + results.add(field); + } + } + return results; + } + + public static boolean isError(Element element) { + return element.getClass().getName().equals("com.sun.tools.javac.comp.Resolve$SymbolNotFoundError"); + } + + /** + * Does the given element need a receiver for accesses? + * For example, an access to a local variable does not require a receiver. + * + * @param element the element to test + * @return whether the element requires a receiver for accesses + */ + public static boolean hasReceiver(Element element) { + return (element.getKind().isField() || + element.getKind() == ElementKind.METHOD || + element.getKind() == ElementKind.CONSTRUCTOR) + && !ElementUtils.isStatic(element); + } + + /** + * Determine all type elements for the classes and interfaces referenced + * in the extends/implements clauses of the given type element. + * TODO: can we learn from the implementation of + * com.sun.tools.javac.model.JavacElements.getAllMembers(TypeElement)? + */ + public static List<TypeElement> getSuperTypes(Elements elements, TypeElement type) { + + List<TypeElement> superelems = new ArrayList<TypeElement>(); + if (type == null) { + return superelems; + } + + // Set up a stack containing type, which is our starting point. + Deque<TypeElement> stack = new ArrayDeque<TypeElement>(); + stack.push(type); + + while (!stack.isEmpty()) { + TypeElement current = stack.pop(); + + // For each direct supertype of the current type element, if it + // hasn't already been visited, push it onto the stack and + // add it to our superelems set. + TypeMirror supertypecls = current.getSuperclass(); + if (supertypecls.getKind() != TypeKind.NONE) { + TypeElement supercls = (TypeElement) ((DeclaredType)supertypecls).asElement(); + if (!superelems.contains(supercls)) { + stack.push(supercls); + superelems.add(supercls); + } + } + for (TypeMirror supertypeitf : current.getInterfaces()) { + TypeElement superitf = (TypeElement) ((DeclaredType)supertypeitf).asElement(); + if (!superelems.contains(superitf)) { + stack.push(superitf); + superelems.add(superitf); + } + } + } + + // Include java.lang.Object as implicit superclass for all classes and interfaces. + TypeElement jlobject = elements.getTypeElement("java.lang.Object"); + if (!superelems.contains(jlobject)) { + superelems.add(jlobject); + } + + return Collections.<TypeElement>unmodifiableList(superelems); + } + + /** + * Return all fields declared in the given type or any superclass/interface. + * TODO: should this use javax.lang.model.util.Elements.getAllMembers(TypeElement) + * instead of our own getSuperTypes? + */ + public static List<VariableElement> getAllFieldsIn(Elements elements, TypeElement type) { + List<VariableElement> fields = new ArrayList<VariableElement>(); + fields.addAll(ElementFilter.fieldsIn(type.getEnclosedElements())); + List<TypeElement> alltypes = getSuperTypes(elements, type); + for (TypeElement atype : alltypes) { + fields.addAll(ElementFilter.fieldsIn(atype.getEnclosedElements())); + } + return Collections.<VariableElement>unmodifiableList(fields); + } + + /** + * Return all methods declared in the given type or any superclass/interface. + * Note that no constructors will be returned. + * TODO: should this use javax.lang.model.util.Elements.getAllMembers(TypeElement) + * instead of our own getSuperTypes? + */ + public static List<ExecutableElement> getAllMethodsIn(Elements elements, TypeElement type) { + List<ExecutableElement> meths = new ArrayList<ExecutableElement>(); + meths.addAll(ElementFilter.methodsIn(type.getEnclosedElements())); + + List<TypeElement> alltypes = getSuperTypes(elements, type); + for (TypeElement atype : alltypes) { + meths.addAll(ElementFilter.methodsIn(atype.getEnclosedElements())); + } + return Collections.<ExecutableElement>unmodifiableList(meths); + } + + public static boolean isTypeDeclaration(Element elt) { + switch (elt.getKind()) { + // These tree kinds are always declarations. Uses of the declared + // types have tree kind IDENTIFIER. + case ANNOTATION_TYPE: + case CLASS: + case ENUM: + case INTERFACE: + case TYPE_PARAMETER: + return true; + + default: + return false; + } + } + + /** + * Check that a method Element matches a signature. + * + * Note: Matching the receiver type must be done elsewhere as + * the Element receiver type is only populated when annotated. + * + * @param method the method Element + * @param methodName the name of the method + * @param parameters the formal parameters' Classes + * @return true if the method matches + */ + public static boolean matchesElement(ExecutableElement method, + String methodName, + Class<?> ... parameters) { + + if (!method.getSimpleName().toString().equals(methodName)) { + return false; + } + + if (method.getParameters().size() != parameters.length) { + return false; + } else { + for (int i = 0; i < method.getParameters().size(); i++) { + if (!method.getParameters().get(i).asType().toString().equals( + parameters[i].getName())) { + + return false; + } + } + } + + return true; + } +} diff --git a/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/ErrorHandler.java b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/ErrorHandler.java new file mode 100644 index 0000000000..9e3c27a7b9 --- /dev/null +++ b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/ErrorHandler.java @@ -0,0 +1,18 @@ +package org.checkerframework.javacutil; + +/** + * An implementation of the ErrorHandler interface can be registered + * with the ErrorReporter class to change the default behavior on + * errors. + */ +public interface ErrorHandler { + + /** + * Log an error message and abort processing. + * + * @param msg the error message to log + */ + public void errorAbort(String msg); + + public void errorAbort(String msg, Throwable cause); +} diff --git a/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/ErrorReporter.java b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/ErrorReporter.java new file mode 100644 index 0000000000..309f1bb4ee --- /dev/null +++ b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/ErrorReporter.java @@ -0,0 +1,41 @@ +package org.checkerframework.javacutil; + +/** + * Handle errors detected in utility classes. By default, the error reporter + * throws a RuntimeException, but clients of the utility library may register + * a handler to change the behavior. For example, type checkers can direct + * errors to the org.checkerframework.framework.source.SourceChecker class. + */ +public class ErrorReporter { + + protected static ErrorHandler handler = null; + + /** + * Register a handler to customize error reporting. + */ + public static void setHandler(ErrorHandler h) { + handler = h; + } + + /** + * Log an error message and abort processing. + * Call this method instead of raising an exception. + * + * @param msg the error message to log + */ + public static void errorAbort(String msg) { + if (handler != null) { + handler.errorAbort(msg); + } else { + throw new RuntimeException(msg, new Throwable()); + } + } + + public static void errorAbort(String msg, Throwable cause) { + if (handler != null) { + handler.errorAbort(msg, cause); + } else { + throw new RuntimeException(msg, cause); + } + } +} diff --git a/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/InternalUtils.java b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/InternalUtils.java new file mode 100644 index 0000000000..dbff45169b --- /dev/null +++ b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/InternalUtils.java @@ -0,0 +1,403 @@ +package org.checkerframework.javacutil; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.type.WildcardType; +import javax.lang.model.util.Elements; + +import com.sun.source.tree.AnnotatedTypeTree; +import com.sun.source.tree.AnnotationTree; +import com.sun.source.tree.ArrayAccessTree; +import com.sun.source.tree.AssignmentTree; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.NewArrayTree; +import com.sun.source.tree.NewClassTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.TypeParameterTree; +import com.sun.source.util.TreePath; +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Symbol.TypeSymbol; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.code.Type.AnnotatedType; +import com.sun.tools.javac.code.Types; +import com.sun.tools.javac.processing.JavacProcessingEnvironment; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCAnnotatedType; +import com.sun.tools.javac.tree.JCTree.JCAnnotation; +import com.sun.tools.javac.tree.JCTree.JCExpressionStatement; +import com.sun.tools.javac.tree.JCTree.JCMemberReference; +import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; +import com.sun.tools.javac.tree.JCTree.JCNewArray; +import com.sun.tools.javac.tree.JCTree.JCNewClass; +import com.sun.tools.javac.tree.JCTree.JCTypeParameter; +import com.sun.tools.javac.tree.TreeInfo; +import com.sun.tools.javac.util.Context; + +/*>>> + import org.checkerframework.checker.nullness.qual.*; + */ + +/** + * Static utility methods used by annotation abstractions in this package. Some + * methods in this class depend on the use of Sun javac internals; any procedure + * in the Checker Framework that uses a non-public API should be placed here. + */ +public class InternalUtils { + + // Class cannot be instantiated. + private InternalUtils() { + throw new AssertionError("Class InternalUtils cannot be instantiated."); + } + + /** + * Gets the {@link Element} ("symbol") for the given Tree API node. + * + * @param tree the {@link Tree} node to get the symbol for + * @throws IllegalArgumentException + * if {@code tree} is null or is not a valid javac-internal tree + * (JCTree) + * @return the {@code {@link Symbol}} for the given tree, or null if one + * could not be found + */ + public static /*@Nullable*/ Element symbol(Tree tree) { + if (tree == null) { + ErrorReporter.errorAbort("InternalUtils.symbol: tree is null"); + return null; // dead code + } + + if (!(tree instanceof JCTree)) { + ErrorReporter.errorAbort("InternalUtils.symbol: tree is not a valid Javac tree"); + return null; // dead code + } + + if (TreeUtils.isExpressionTree(tree)) { + tree = TreeUtils.skipParens((ExpressionTree) tree); + } + + switch (tree.getKind()) { + case VARIABLE: + case METHOD: + case CLASS: + case ENUM: + case INTERFACE: + case ANNOTATION_TYPE: + case TYPE_PARAMETER: + return TreeInfo.symbolFor((JCTree) tree); + + // symbol() only works on MethodSelects, so we need to get it manually + // for method invocations. + case METHOD_INVOCATION: + return TreeInfo.symbol(((JCMethodInvocation) tree).getMethodSelect()); + + case ASSIGNMENT: + return TreeInfo.symbol((JCTree)((AssignmentTree)tree).getVariable()); + + case ARRAY_ACCESS: + return symbol(((ArrayAccessTree)tree).getExpression()); + + case NEW_CLASS: + return ((JCNewClass)tree).constructor; + + case MEMBER_REFERENCE: + // TreeInfo.symbol, which is used in the default case, didn't handle + // member references until JDK8u20. So handle it here. + return ((JCMemberReference) tree).sym; + + default: + return TreeInfo.symbol((JCTree) tree); + } + } + + /** + * Determines whether or not the node referred to by the given + * {@link TreePath} is an anonymous constructor (the constructor for an + * anonymous class. + * + * @param method the {@link TreePath} for a node that may be an anonymous + * constructor + * @return true if the given path points to an anonymous constructor, false + * if it does not + */ + public static boolean isAnonymousConstructor(final MethodTree method) { + /*@Nullable*/ Element e = InternalUtils.symbol(method); + if (e == null || !(e instanceof Symbol)) { + return false; + } + + if ((((/*@NonNull*/ Symbol)e).flags() & Flags.ANONCONSTR) != 0) { + return true; + } + + return false; + } + + /** + * indicates whether it should return the constructor that gets invoked + * in cases of anonymous classes + */ + private static final boolean RETURN_INVOKE_CONSTRUCTOR = true; + + /** + * Determines the symbol for a constructor given an invocation via + * {@code new}. + * + * If the tree is a declaration of an anonymous class, then method returns + * constructor that gets invoked in the extended class, rather than the + * anonymous constructor implicitly added by the constructor (JLS 15.9.5.1) + * + * @param tree the constructor invocation + * @return the {@link ExecutableElement} corresponding to the constructor + * call in {@code tree} + */ + public static ExecutableElement constructor(NewClassTree tree) { + + if (!(tree instanceof JCTree.JCNewClass)) { + ErrorReporter.errorAbort("InternalUtils.constructor: not a javac internal tree"); + return null; // dead code + } + + JCNewClass newClassTree = (JCNewClass) tree; + + if (RETURN_INVOKE_CONSTRUCTOR && tree.getClassBody() != null) { + // anonymous constructor bodies should contain exactly one statement + // in the form: + // super(arg1, ...) + // or + // o.super(arg1, ...) + // + // which is a method invocation (!) to the actual constructor + + // the method call is guaranteed to return nonnull + JCMethodDecl anonConstructor = + (JCMethodDecl) TreeInfo.declarationFor(newClassTree.constructor, newClassTree); + assert anonConstructor != null; + assert anonConstructor.body.stats.size() == 1; + JCExpressionStatement stmt = (JCExpressionStatement) anonConstructor.body.stats.head; + JCTree.JCMethodInvocation superInvok = (JCMethodInvocation) stmt.expr; + return (ExecutableElement) TreeInfo.symbol(superInvok.meth); + } + + Element e = newClassTree.constructor; + + assert e instanceof ExecutableElement; + + return (ExecutableElement) e; + } + + public final static List<AnnotationMirror> annotationsFromTypeAnnotationTrees(List<? extends AnnotationTree> annos) { + List<AnnotationMirror> annotations = new ArrayList<AnnotationMirror>(annos.size()); + for (AnnotationTree anno : annos) { + annotations.add(((JCAnnotation)anno).attribute); + } + return annotations; + } + + public final static List<? extends AnnotationMirror> annotationsFromTree(AnnotatedTypeTree node) { + return annotationsFromTypeAnnotationTrees(((JCAnnotatedType)node).annotations); + } + + public final static List<? extends AnnotationMirror> annotationsFromTree(TypeParameterTree node) { + return annotationsFromTypeAnnotationTrees(((JCTypeParameter)node).annotations); + } + + public final static List<? extends AnnotationMirror> annotationsFromArrayCreation(NewArrayTree node, int level) { + + assert node instanceof JCNewArray; + final JCNewArray newArray = ((JCNewArray) node); + + if (level == -1) { + return annotationsFromTypeAnnotationTrees(newArray.annotations); + } + + if (newArray.dimAnnotations.length() > 0 + && (level >= 0) + && (level < newArray.dimAnnotations.size())) + return annotationsFromTypeAnnotationTrees(newArray.dimAnnotations.get(level)); + + return Collections.emptyList(); + } + + public static TypeMirror typeOf(Tree tree) { + return ((JCTree) tree).type; + } + + /** + * Returns whether a TypeVariable represents a captured type. + */ + public static boolean isCaptured(TypeVariable typeVar) { + if (typeVar instanceof AnnotatedType) { + return ((Type.TypeVar) ((Type.AnnotatedType) typeVar).unannotatedType()).isCaptured(); + } + return ((Type.TypeVar) typeVar).isCaptured(); + } + + /** + * Returns whether a TypeMirror represents a class type. + */ + public static boolean isClassType(TypeMirror type) { + return (type instanceof Type.ClassType); + } + + /** + * Returns the least upper bound of two {@link TypeMirror}s, + * ignoring any annotations on the types. + * + * Wrapper around Types.lub to add special handling for + * null types, primitives, and wildcards. + * + * @param processingEnv the {@link ProcessingEnvironment} to use. + * @param tm1 a {@link TypeMirror}. + * @param tm2 a {@link TypeMirror}. + * @return the least upper bound of {@code tm1} and {@code tm2}. + */ + public static TypeMirror leastUpperBound( + ProcessingEnvironment processingEnv, TypeMirror tm1, TypeMirror tm2) { + Type t1 = ((Type) tm1).unannotatedType(); + Type t2 = ((Type) tm2).unannotatedType(); + JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) processingEnv; + Types types = Types.instance(javacEnv.getContext()); + if (types.isSameType(t1, t2)) { + // Special case if the two types are equal. + return t1; + } + // Handle the 'null' type manually (not done by types.lub). + if (t1.getKind() == TypeKind.NULL) { + return t2; + } + if (t2.getKind() == TypeKind.NULL) { + return t1; + } + // Special case for primitives. + if (TypesUtils.isPrimitive(t1) || TypesUtils.isPrimitive(t2)) { + if (types.isAssignable(t1, t2)) { + return t2; + } else if (types.isAssignable(t2, t1)) { + return t1; + } else { + return processingEnv.getTypeUtils().getNoType(TypeKind.NONE); + } + } + if (t1.getKind() == TypeKind.WILDCARD) { + WildcardType wc1 = (WildcardType) t1; + Type bound = (Type) wc1.getExtendsBound(); + if (bound == null) { + // Implicit upper bound of java.lang.Object + Elements elements = processingEnv.getElementUtils(); + return elements.getTypeElement("java.lang.Object").asType(); + } + t1 = bound; + } + if (t2.getKind() == TypeKind.WILDCARD) { + WildcardType wc2 = (WildcardType) t2; + Type bound = (Type) wc2.getExtendsBound(); + if (bound == null) { + // Implicit upper bound of java.lang.Object + Elements elements = processingEnv.getElementUtils(); + return elements.getTypeElement("java.lang.Object").asType(); + } + t2 = bound; + } + return types.lub(t1, t2); + } + + /** + * Returns the greatest lower bound of two {@link TypeMirror}s, + * ignoring any annotations on the types. + * + * Wrapper around Types.glb to add special handling for + * null types, primitives, and wildcards. + * + * + * @param processingEnv the {@link ProcessingEnvironment} to use. + * @param tm1 a {@link TypeMirror}. + * @param tm2 a {@link TypeMirror}. + * @return the greatest lower bound of {@code tm1} and {@code tm2}. + */ + public static TypeMirror greatestLowerBound( + ProcessingEnvironment processingEnv, TypeMirror tm1, TypeMirror tm2) { + Type t1 = ((Type) tm1).unannotatedType(); + Type t2 = ((Type) tm2).unannotatedType(); + JavacProcessingEnvironment javacEnv = (JavacProcessingEnvironment) processingEnv; + Types types = Types.instance(javacEnv.getContext()); + if (types.isSameType(t1, t2)) { + // Special case if the two types are equal. + return t1; + } + // Handle the 'null' type manually. + if (t1.getKind() == TypeKind.NULL) { + return t1; + } + if (t2.getKind() == TypeKind.NULL) { + return t2; + } + // Special case for primitives. + if (TypesUtils.isPrimitive(t1) || TypesUtils.isPrimitive(t2)) { + if (types.isAssignable(t1, t2)) { + return t1; + } else if (types.isAssignable(t2, t1)) { + return t2; + } else { + // Javac types.glb returns TypeKind.Error when the GLB does + // not exist, but we can't create one. Use TypeKind.NONE + // instead. + return processingEnv.getTypeUtils().getNoType(TypeKind.NONE); + } + } + if (t1.getKind() == TypeKind.WILDCARD) { + return t2; + } + if (t2.getKind() == TypeKind.WILDCARD) { + return t1; + } + + // If neither type is a primitive type, null type, or wildcard + // and if the types are not the same, use javac types.glb + return types.glb(t1, t2); + } + + /** + * Returns the return type of a method, where the "raw" return type of that + * method is given (i.e., the return type might still contain unsubstituted + * type variables), given the receiver of the method call. + */ + public static TypeMirror substituteMethodReturnType(TypeMirror methodType, + TypeMirror substitutedReceiverType) { + if (methodType.getKind() != TypeKind.TYPEVAR) { + return methodType; + } + // TODO: find a nicer way to substitute type variables + String t = methodType.toString(); + Type finalReceiverType = (Type) substitutedReceiverType; + int i = 0; + for (TypeSymbol typeParam : finalReceiverType.tsym.getTypeParameters()) { + if (t.equals(typeParam.toString())) { + return finalReceiverType.getTypeArguments().get(i); + } + i++; + } + assert false; + return null; + } + + /** Helper function to extract the javac Context from the + * javac processing environment. + * + * @param env the processing environment + * @return the javac Context + */ + public static Context getJavacContext(ProcessingEnvironment env) { + return ((JavacProcessingEnvironment)env).getContext(); + } +} diff --git a/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/Pair.java b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/Pair.java new file mode 100644 index 0000000000..0d8a862f08 --- /dev/null +++ b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/Pair.java @@ -0,0 +1,69 @@ +package org.checkerframework.javacutil; + +/*>>> +import org.checkerframework.dataflow.qual.Pure; +import org.checkerframework.dataflow.qual.SideEffectFree; +*/ + +/** + * Simple pair class for multiple returns. + * + * TODO: as class is immutable, use @Covariant annotation. + */ +public class Pair<V1, V2> { + public final V1 first; + public final V2 second; + + private Pair(V1 v1, V2 v2) { + this.first = v1; + this.second = v2; + } + + public static <V1, V2> Pair<V1, V2> of(V1 v1, V2 v2) { + return new Pair<V1, V2>(v1, v2); + } + + /*@SideEffectFree*/ + @Override + public String toString() { + return "Pair(" + first + ", " + second + ")"; + } + + private int hashCode = -1; + + /*@Pure*/ + @Override + public int hashCode() { + if (hashCode == -1) { + hashCode = 31; + if (first != null) { + hashCode += 17 * first.hashCode(); + } + if (second != null) { + hashCode += 17 * second.hashCode(); + } + } + return hashCode; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Pair)) { + return false; + } + @SuppressWarnings("unchecked") + Pair<V1, V2> other = (Pair<V1, V2>) o; + if (this.first == null) { + if (other.first != null) return false; + } else { + if (!this.first.equals(other.first)) return false; + } + if (this.second == null) { + if (other.second != null) return false; + } else { + if (!this.second.equals(other.second)) return false; + } + + return true; + } +} diff --git a/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/Resolver.java b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/Resolver.java new file mode 100644 index 0000000000..45d959fe02 --- /dev/null +++ b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/Resolver.java @@ -0,0 +1,381 @@ +package org.checkerframework.javacutil; + +import static com.sun.tools.javac.code.Kinds.PCK; +import static com.sun.tools.javac.code.Kinds.TYP; +import static com.sun.tools.javac.code.Kinds.VAR; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; + +import com.sun.source.util.TreePath; +import com.sun.source.util.Trees; +import com.sun.tools.javac.api.JavacScope; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Symbol.ClassSymbol; +import com.sun.tools.javac.code.Symbol.PackageSymbol; +import com.sun.tools.javac.code.Symbol.TypeSymbol; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.comp.AttrContext; +import com.sun.tools.javac.comp.DeferredAttr; +import com.sun.tools.javac.comp.Env; +import com.sun.tools.javac.comp.Resolve; +import com.sun.tools.javac.processing.JavacProcessingEnvironment; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.Log; +import com.sun.tools.javac.util.Name; +import com.sun.tools.javac.util.Names; + +/** + * A Utility class to find symbols corresponding to string references. + */ +public class Resolver { + private final Resolve resolve; + private final Names names; + private final Trees trees; + private final Log log; + + private static final Method FIND_METHOD; + private static final Method FIND_VAR; + private static final Method FIND_IDENT; + private static final Method FIND_IDENT_IN_TYPE; + private static final Method FIND_IDENT_IN_PACKAGE; + private static final Method FIND_TYPE; + + private static final Class<?> ACCESSERROR; + // Note that currently access(...) is defined in InvalidSymbolError, a superclass of AccessError + private static final Method ACCESSERROR_ACCESS; + + static { + try { + FIND_METHOD = Resolve.class.getDeclaredMethod("findMethod", + Env.class, Type.class, Name.class, List.class, List.class, + boolean.class, boolean.class, boolean.class); + FIND_METHOD.setAccessible(true); + + FIND_VAR = Resolve.class.getDeclaredMethod("findVar", + Env.class, Name.class); + FIND_VAR.setAccessible(true); + + FIND_IDENT = Resolve.class.getDeclaredMethod( + "findIdent", Env.class, Name.class, int.class); + FIND_IDENT.setAccessible(true); + + FIND_IDENT_IN_TYPE = Resolve.class.getDeclaredMethod( + "findIdentInType", Env.class, Type.class, Name.class, + int.class); + FIND_IDENT_IN_TYPE.setAccessible(true); + + FIND_IDENT_IN_PACKAGE = Resolve.class.getDeclaredMethod( + "findIdentInPackage", Env.class, TypeSymbol.class, Name.class, + int.class); + FIND_IDENT_IN_PACKAGE.setAccessible(true); + + FIND_TYPE = Resolve.class.getDeclaredMethod( + "findType", Env.class, Name.class); + FIND_TYPE.setAccessible(true); + } catch (Exception e) { + Error err = new AssertionError( + "Compiler 'Resolve' class doesn't contain required 'find' method"); + err.initCause(e); + throw err; + } + + try { + ACCESSERROR = Class.forName("com.sun.tools.javac.comp.Resolve$AccessError"); + ACCESSERROR_ACCESS = ACCESSERROR.getMethod("access", Name.class, TypeSymbol.class); + ACCESSERROR_ACCESS.setAccessible(true); + } catch (ClassNotFoundException e) { + ErrorReporter.errorAbort("Compiler 'Resolve$AccessError' class could not be retrieved.", e); + // Unreachable code - needed so the compiler does not warn about a possibly uninitialized final field. + throw new AssertionError(); + } catch (NoSuchMethodException e) { + ErrorReporter.errorAbort("Compiler 'Resolve$AccessError' class doesn't contain required 'access' method", e); + // Unreachable code - needed so the compiler does not warn about a possibly uninitialized final field. + throw new AssertionError(); + } + } + + public Resolver(ProcessingEnvironment env) { + Context context = ((JavacProcessingEnvironment) env).getContext(); + this.resolve = Resolve.instance(context); + this.names = Names.instance(context); + this.trees = Trees.instance(env); + this.log = Log.instance(context); + } + + /** + * Finds the package with name {@code name}. + * + * @param name + * The name of the package. + * @param path + * The tree path to the local scope. + * @return the {@code PackageSymbol} for the package if it is found, + * {@code null} otherwise + */ + public PackageSymbol findPackage(String name, TreePath path) { + Log.DiagnosticHandler discardDiagnosticHandler = + new Log.DiscardDiagnosticHandler(log); + try { + JavacScope scope = (JavacScope) trees.getScope(path); + Env<AttrContext> env = scope.getEnv(); + Element res = wrapInvocationOnResolveInstance(FIND_IDENT, env, + names.fromString(name), PCK); + // findIdent will return a PackageSymbol even for a symbol that is not a package, + // such as a.b.c.MyClass.myStaticField. "exists()" must be called on it to ensure + // that it exists. + if (res.getKind() == ElementKind.PACKAGE) { + PackageSymbol ps = (PackageSymbol) res; + return ps.exists() ? ps : null; + } else { + return null; + } + } finally { + log.popDiagnosticHandler(discardDiagnosticHandler); + } + } + + /** + * Finds the field with name {@code name} in a given type. + * + * <p> + * The method adheres to all the rules of Java's scoping (while also + * considering the imports) for name resolution. + * + * @param name + * The name of the field. + * @param type + * The type of the receiver (i.e., the type in which to look for + * the field). + * @param path + * The tree path to the local scope. + * @return the element for the field + */ + public VariableElement findField(String name, TypeMirror type, TreePath path) { + Log.DiagnosticHandler discardDiagnosticHandler = + new Log.DiscardDiagnosticHandler(log); + try { + JavacScope scope = (JavacScope) trees.getScope(path); + Env<AttrContext> env = scope.getEnv(); + Element res = wrapInvocationOnResolveInstance(FIND_IDENT_IN_TYPE, env, type, + names.fromString(name), VAR); + if (res.getKind() == ElementKind.FIELD) { + return (VariableElement) res; + } else if (res.getKind() == ElementKind.OTHER && ACCESSERROR.isInstance(res)) { + // Return the inaccessible field that was found + return (VariableElement) wrapInvocation(res, ACCESSERROR_ACCESS, null, null); + } else { + // Most likely didn't find the field and the Element is a SymbolNotFoundError + return null; + } + } finally { + log.popDiagnosticHandler(discardDiagnosticHandler); + } + } + + /** + * Finds the local variable with name {@code name} in the given scope. + * + * @param name + * The name of the local variable. + * @param path + * The tree path to the local scope. + * @return the element for the local variable + */ + public VariableElement findLocalVariableOrParameter(String name, TreePath path) { + Log.DiagnosticHandler discardDiagnosticHandler = + new Log.DiscardDiagnosticHandler(log); + try { + JavacScope scope = (JavacScope) trees.getScope(path); + Env<AttrContext> env = scope.getEnv(); + Element res = wrapInvocationOnResolveInstance(FIND_VAR, env, + names.fromString(name)); + if (res.getKind() == ElementKind.LOCAL_VARIABLE + || res.getKind() == ElementKind.PARAMETER) { + return (VariableElement) res; + } else { + // Most likely didn't find the variable and the Element is a SymbolNotFoundError + return null; + } + } finally { + log.popDiagnosticHandler(discardDiagnosticHandler); + } + } + + /** + * Finds the class literal with name {@code name}. + * + * <p> + * The method adheres to all the rules of Java's scoping (while also + * considering the imports) for name resolution. + * + * @param name + * The name of the class. + * @param path + * The tree path to the local scope. + * @return the element for the class + */ + public Element findClass(String name, TreePath path) { + Log.DiagnosticHandler discardDiagnosticHandler = + new Log.DiscardDiagnosticHandler(log); + try { + JavacScope scope = (JavacScope) trees.getScope(path); + Env<AttrContext> env = scope.getEnv(); + return wrapInvocationOnResolveInstance(FIND_TYPE, env, names.fromString(name)); + } finally { + log.popDiagnosticHandler(discardDiagnosticHandler); + } + } + + /** + * Finds the class with name {@code name} in a given package. + * + * @param name + * The name of the class. + * @param pck + * The PackageSymbol for the package. + * @param path + * The tree path to the local scope. + * @return the {@code ClassSymbol} for the class if it is found, + * {@code null} otherwise + */ + public ClassSymbol findClassInPackage(String name, PackageSymbol pck, TreePath path) { + Log.DiagnosticHandler discardDiagnosticHandler = + new Log.DiscardDiagnosticHandler(log); + try { + JavacScope scope = (JavacScope) trees.getScope(path); + Env<AttrContext> env = scope.getEnv(); + Element res = wrapInvocationOnResolveInstance(FIND_IDENT_IN_PACKAGE, env, pck, + names.fromString(name), TYP); + if (res.getKind() == ElementKind.CLASS) { + return (ClassSymbol) res; + } else { + return null; + } + } finally { + log.popDiagnosticHandler(discardDiagnosticHandler); + } + } + + /** + * Finds the method element for a given name and list of expected parameter + * types. + * + * <p> + * The method adheres to all the rules of Java's scoping (while also + * considering the imports) for name resolution. + * + * @param methodName + * Name of the method to find. + * @param receiverType + * Type of the receiver of the method + * @param path + * Tree path. + * @return the method element (if found) + */ + public Element findMethod(String methodName, TypeMirror receiverType, + TreePath path, java.util.List<TypeMirror> argumentTypes) { + Log.DiagnosticHandler discardDiagnosticHandler = + new Log.DiscardDiagnosticHandler(log); + try { + JavacScope scope = (JavacScope) trees.getScope(path); + Env<AttrContext> env = scope.getEnv(); + + Type site = (Type) receiverType; + Name name = names.fromString(methodName); + List<Type> argtypes = List.nil(); + for (TypeMirror a : argumentTypes) { + argtypes = argtypes.append((Type) a); + } + List<Type> typeargtypes = List.nil(); + boolean allowBoxing = true; + boolean useVarargs = false; + boolean operator = true; + + try { + // For some reason we have to set our own method context, which is rather ugly. + // TODO: find a nicer way to do this. + Object methodContext = buildMethodContext(); + Object oldContext = getField(resolve, "currentResolutionContext"); + setField(resolve, "currentResolutionContext", methodContext); + Element result = wrapInvocationOnResolveInstance(FIND_METHOD, env, site, name, argtypes, + typeargtypes, allowBoxing, useVarargs, operator); + setField(resolve, "currentResolutionContext", oldContext); + return result; + } catch (Throwable t) { + Error err = new AssertionError("Unexpected Reflection error"); + err.initCause(t); + throw err; + } + } finally { + log.popDiagnosticHandler(discardDiagnosticHandler); + } + } + + /** + * Build an instance of {@code Resolve$MethodResolutionContext}. + */ + protected Object buildMethodContext() throws ClassNotFoundException, + InstantiationException, IllegalAccessException, + InvocationTargetException, NoSuchFieldException { + // Class is not accessible, instantiate reflectively. + Class<?> methCtxClss = Class.forName("com.sun.tools.javac.comp.Resolve$MethodResolutionContext"); + Constructor<?> constructor = methCtxClss.getDeclaredConstructors()[0]; + constructor.setAccessible(true); + Object methodContext = constructor.newInstance(resolve); + // we need to also initialize the fields attrMode and step + setField(methodContext, "attrMode", DeferredAttr.AttrMode.CHECK); + @SuppressWarnings("rawtypes") + List<?> phases = (List) getField(resolve, "methodResolutionSteps"); + setField(methodContext, "step", phases.get(1)); + return methodContext; + } + + /** Reflectively set a field. */ + private void setField(Object receiver, String fieldName, + Object value) throws NoSuchFieldException, + IllegalAccessException { + Field f = receiver.getClass().getDeclaredField(fieldName); + f.setAccessible(true); + f.set(receiver, value); + } + + /** Reflectively get the value of a field. */ + private Object getField(Object receiver, String fieldName) throws NoSuchFieldException, + IllegalAccessException { + Field f = receiver.getClass().getDeclaredField(fieldName); + f.setAccessible(true); + return f.get(receiver); + } + + private Symbol wrapInvocationOnResolveInstance(Method method, Object... args) { + return wrapInvocation(resolve, method, args); + } + + private Symbol wrapInvocation(Object receiver, Method method, Object... args) { + try { + return (Symbol) method.invoke(receiver, args); + } catch (IllegalAccessException e) { + Error err = new AssertionError("Unexpected Reflection error"); + err.initCause(e); + throw err; + } catch (IllegalArgumentException e) { + Error err = new AssertionError("Unexpected Reflection error"); + err.initCause(e); + throw err; + } catch (InvocationTargetException e) { + Error err = new AssertionError("Unexpected Reflection error"); + err.initCause(e); + throw err; + } + } +} diff --git a/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/TreeUtils.java b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/TreeUtils.java new file mode 100644 index 0000000000..5ceb5450dd --- /dev/null +++ b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/TreeUtils.java @@ -0,0 +1,967 @@ +package org.checkerframework.javacutil; + +/*>>> +import org.checkerframework.checker.nullness.qual.*; +*/ + +import java.util.EnumSet; +import java.util.Set; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +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.util.ElementFilter; + +import com.sun.source.tree.AnnotatedTypeTree; +import com.sun.source.tree.ArrayAccessTree; +import com.sun.source.tree.BinaryTree; +import com.sun.source.tree.BlockTree; +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.CompoundAssignmentTree; +import com.sun.source.tree.ExpressionStatementTree; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.LiteralTree; +import com.sun.source.tree.MemberSelectTree; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.NewClassTree; +import com.sun.source.tree.ParameterizedTypeTree; +import com.sun.source.tree.ParenthesizedTree; +import com.sun.source.tree.PrimitiveTypeTree; +import com.sun.source.tree.StatementTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.TypeCastTree; +import com.sun.source.tree.VariableTree; +import com.sun.source.util.TreePath; +import com.sun.source.util.Trees; +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.code.Symbol.MethodSymbol; +import com.sun.tools.javac.tree.JCTree; + +/** + * A utility class made for helping to analyze a given {@code Tree}. + */ +// TODO: This class needs significant restructuring +public final class TreeUtils { + + // Class cannot be instantiated. + private TreeUtils() { throw new AssertionError("Class TreeUtils cannot be instantiated."); } + + /** + * Checks if the provided method is a constructor method or no. + * + * @param tree + * a tree defining the method + * @return true iff tree describes a constructor + */ + public static boolean isConstructor(final MethodTree tree) { + return tree.getName().contentEquals("<init>"); + } + + /** + * Checks if the method invocation is a call to super. + * + * @param tree + * a tree defining a method invocation + * + * @return true iff tree describes a call to super + */ + public static boolean isSuperCall(MethodInvocationTree tree) { + return isNamedMethodCall("super", tree); + } + + /** + * Checks if the method invocation is a call to this. + * + * @param tree + * a tree defining a method invocation + * + * @return true iff tree describes a call to this + */ + public static boolean isThisCall(MethodInvocationTree tree) { + return isNamedMethodCall("this", tree); + + } + + protected static boolean isNamedMethodCall(String name, MethodInvocationTree tree) { + /*@Nullable*/ ExpressionTree mst = tree.getMethodSelect(); + assert mst != null; /*nninvariant*/ + + if (mst.getKind() == Tree.Kind.IDENTIFIER ) { + return ((IdentifierTree)mst).getName().contentEquals(name); + } + + if (mst.getKind() == Tree.Kind.MEMBER_SELECT) { + MemberSelectTree selectTree = (MemberSelectTree)mst; + + if (selectTree.getExpression().getKind() != Tree.Kind.IDENTIFIER) { + return false; + } + + return ((IdentifierTree) selectTree.getExpression()).getName() + .contentEquals(name); + } + + return false; + } + + /** + * Returns true if the tree is a tree that 'looks like' either an access + * of a field or an invocation of a method that are owned by the same + * accessing instance. + * + * It would only return true if the access tree is of the form: + * <pre> + * field + * this.field + * + * method() + * this.method() + * </pre> + * + * It does not perform any semantical check to differentiate between + * fields and local variables; local methods or imported static methods. + * + * @param tree expression tree representing an access to object member + * @return {@code true} iff the member is a member of {@code this} instance + */ + public static boolean isSelfAccess(final ExpressionTree tree) { + ExpressionTree tr = TreeUtils.skipParens(tree); + // If method invocation check the method select + if (tr.getKind() == Tree.Kind.ARRAY_ACCESS) { + return false; + } + + if (tree.getKind() == Tree.Kind.METHOD_INVOCATION) { + tr = ((MethodInvocationTree)tree).getMethodSelect(); + } + tr = TreeUtils.skipParens(tr); + if (tr.getKind() == Tree.Kind.TYPE_CAST) { + tr = ((TypeCastTree)tr).getExpression(); + } + tr = TreeUtils.skipParens(tr); + + if (tr.getKind() == Tree.Kind.IDENTIFIER) { + return true; + } + + if (tr.getKind() == Tree.Kind.MEMBER_SELECT) { + tr = ((MemberSelectTree)tr).getExpression(); + if (tr.getKind() == Tree.Kind.IDENTIFIER) { + Name ident = ((IdentifierTree)tr).getName(); + return ident.contentEquals("this") || + ident.contentEquals("super"); + } + } + + return false; + } + + /** + * Gets the first enclosing tree in path, of the specified kind. + * + * @param path the path defining the tree node + * @param kind the kind of the desired tree + * @return the enclosing tree of the given type as given by the path + */ + public static Tree enclosingOfKind(final TreePath path, final Tree.Kind kind) { + return enclosingOfKind(path, EnumSet.of(kind)); + } + + /** + * Gets the first enclosing tree in path, with any one of the specified kinds. + * + * @param path the path defining the tree node + * @param kinds the set of kinds of the desired tree + * @return the enclosing tree of the given type as given by the path + */ + public static Tree enclosingOfKind(final TreePath path, final Set<Tree.Kind> kinds) { + TreePath p = path; + + while (p != null) { + Tree leaf = p.getLeaf(); + assert leaf != null; /*nninvariant*/ + if (kinds.contains(leaf.getKind())) { + return leaf; + } + p = p.getParentPath(); + } + + return null; + } + + /** + * Gets path to the first enclosing class tree, where class is + * defined by the classTreeKinds method. + * + * @param path the path defining the tree node + * @return the path to the enclosing class tree + */ + public static TreePath pathTillClass(final TreePath path) { + return pathTillOfKind(path, classTreeKinds()); + } + + /** + * Gets path to the first enclosing tree of the specified kind. + * + * @param path the path defining the tree node + * @param kind the kind of the desired tree + * @return the path to the enclosing tree of the given type + */ + public static TreePath pathTillOfKind(final TreePath path, final Tree.Kind kind) { + return pathTillOfKind(path, EnumSet.of(kind)); + } + + /** + * Gets path to the first enclosing tree with any one of the specified kinds. + * + * @param path the path defining the tree node + * @param kinds the set of kinds of the desired tree + * @return the path to the enclosing tree of the given type + */ + public static TreePath pathTillOfKind(final TreePath path, final Set<Tree.Kind> kinds) { + TreePath p = path; + + while (p != null) { + Tree leaf = p.getLeaf(); + assert leaf != null; /*nninvariant*/ + if (kinds.contains(leaf.getKind())) { + return p; + } + p = p.getParentPath(); + } + + return null; + } + + /** + * Gets the first enclosing tree in path, of the specified class + * + * @param path the path defining the tree node + * @param treeClass the class of the desired tree + * @return the enclosing tree of the given type as given by the path + */ + public static <T extends Tree> T enclosingOfClass(final TreePath path, final Class<T> treeClass) { + TreePath p = path; + + while (p != null) { + Tree leaf = p.getLeaf(); + if (treeClass.isInstance(leaf)) { + return treeClass.cast(leaf); + } + p = p.getParentPath(); + } + + return null; + } + + /** + * Gets the enclosing class of the tree node defined by the given + * {@code {@link TreePath}}. It returns a {@link Tree}, from which + * {@code checkers.types.AnnotatedTypeMirror} or {@link Element} can be + * obtained. + * + * @param path the path defining the tree node + * @return the enclosing class (or interface) as given by the path, or null + * if one does not exist + */ + public static /*@Nullable*/ ClassTree enclosingClass(final /*@Nullable*/ TreePath path) { + return (ClassTree) enclosingOfKind(path, classTreeKinds()); + } + + /** + * Gets the enclosing variable of a tree node defined by the given + * {@link TreePath}. + * + * @param path the path defining the tree node + * @return the enclosing variable as given by the path, or null if one does not exist + */ + public static VariableTree enclosingVariable(final TreePath path) { + return (VariableTree) enclosingOfKind(path, Tree.Kind.VARIABLE); + } + + /** + * Gets the enclosing method of the tree node defined by the given + * {@code {@link TreePath}}. It returns a {@link Tree}, from which an + * {@code checkers.types.AnnotatedTypeMirror} or {@link Element} can be + * obtained. + * + * @param path the path defining the tree node + * @return the enclosing method as given by the path, or null if one does + * not exist + */ + public static /*@Nullable*/ MethodTree enclosingMethod(final /*@Nullable*/ TreePath path) { + return (MethodTree) enclosingOfKind(path, Tree.Kind.METHOD); + } + + public static /*@Nullable*/ BlockTree enclosingTopLevelBlock(TreePath path) { + TreePath parpath = path.getParentPath(); + while (parpath!=null && parpath.getLeaf().getKind() != Tree.Kind.CLASS) { + path = parpath; + parpath = parpath.getParentPath(); + } + if (path.getLeaf().getKind() == Tree.Kind.BLOCK) { + return (BlockTree) path.getLeaf(); + } + return null; + } + + + /** + * If the given tree is a parenthesized tree, it returns the enclosed + * non-parenthesized tree. Otherwise, it returns the same tree. + * + * @param tree an expression tree + * @return the outermost non-parenthesized tree enclosed by the given tree + */ + public static ExpressionTree skipParens(final ExpressionTree tree) { + ExpressionTree t = tree; + while (t.getKind() == Tree.Kind.PARENTHESIZED) + t = ((ParenthesizedTree)t).getExpression(); + return t; + } + + /** + * Returns the tree with the assignment context for the treePath + * leaf node. (Does not handle pseudo-assignment of an argument to + * a parameter or a receiver expression to a receiver.) + * + * The assignment context for the {@code treePath} is the leaf of its parent, + * if the leaf is one of the following trees: + * <ul> + * <li>AssignmentTree </li> + * <li>CompoundAssignmentTree </li> + * <li>MethodInvocationTree</li> + * <li>NewArrayTree</li> + * <li>NewClassTree</li> + * <li>ReturnTree</li> + * <li>VariableTree</li> + * </ul> + * + * If the leaf is a ConditionalExpressionTree or ParenthesizedTree, then recur on the leaf. + * + * Otherwise, null is returned. + * + * @return the assignment context as described + */ + public static Tree getAssignmentContext(final TreePath treePath) { + TreePath parentPath = treePath.getParentPath(); + + if (parentPath == null) { + return null; + } + + Tree parent = parentPath.getLeaf(); + switch (parent.getKind()) { + case PARENTHESIZED: + case CONDITIONAL_EXPRESSION: + return getAssignmentContext(parentPath); + case ASSIGNMENT: + case METHOD_INVOCATION: + case NEW_ARRAY: + case NEW_CLASS: + case RETURN: + case VARIABLE: + return parent; + default: + // 11 Tree.Kinds are CompoundAssignmentTrees, + // so use instanceof rather than listing all 11. + if (parent instanceof CompoundAssignmentTree) { + return parent; + } + return null; + } + } + + /** + * Gets the element for a class corresponding to a declaration. + * + * @return the element for the given class + */ + public static final TypeElement elementFromDeclaration(ClassTree node) { + TypeElement elt = (TypeElement) InternalUtils.symbol(node); + return elt; + } + + /** + * Gets the element for a method corresponding to a declaration. + * + * @return the element for the given method + */ + public static final ExecutableElement elementFromDeclaration(MethodTree node) { + ExecutableElement elt = (ExecutableElement) InternalUtils.symbol(node); + return elt; + } + + /** + * Gets the element for a variable corresponding to its declaration. + * + * @return the element for the given variable + */ + public static final VariableElement elementFromDeclaration(VariableTree node) { + VariableElement elt = (VariableElement) InternalUtils.symbol(node); + return elt; + } + + /** + * Gets the element for the declaration corresponding to this use of an element. + * To get the element for a declaration, use {@link + * Trees#getElement(TreePath)} instead. + * + * TODO: remove this method, as it really doesn't do anything. + * + * @param node the tree corresponding to a use of an element + * @return the element for the corresponding declaration + */ + public static final Element elementFromUse(ExpressionTree node) { + return InternalUtils.symbol(node); + } + + // Specialization for return type. + public static final ExecutableElement elementFromUse(MethodInvocationTree node) { + return (ExecutableElement) elementFromUse((ExpressionTree) node); + } + + // Specialization for return type. + public static final ExecutableElement elementFromUse(NewClassTree node) { + return (ExecutableElement) elementFromUse((ExpressionTree) node); + } + + + /** + * Determine whether the given ExpressionTree has an underlying element. + * + * @param node the ExpressionTree to test + * @return whether the tree refers to an identifier, member select, or method invocation + */ + public static final boolean isUseOfElement(ExpressionTree node) { + node = TreeUtils.skipParens(node); + switch (node.getKind()) { + case IDENTIFIER: + case MEMBER_SELECT: + case METHOD_INVOCATION: + case NEW_CLASS: + return true; + default: + return false; + } + } + + /** + * @return the name of the invoked method + */ + public static final Name methodName(MethodInvocationTree node) { + ExpressionTree expr = node.getMethodSelect(); + if (expr.getKind() == Tree.Kind.IDENTIFIER) { + return ((IdentifierTree)expr).getName(); + } else if (expr.getKind() == Tree.Kind.MEMBER_SELECT) { + return ((MemberSelectTree)expr).getIdentifier(); + } + ErrorReporter.errorAbort("TreeUtils.methodName: cannot be here: " + node); + return null; // dead code + } + + /** + * @return true if the first statement in the body is a self constructor + * invocation within a constructor + */ + public static final boolean containsThisConstructorInvocation(MethodTree node) { + if (!TreeUtils.isConstructor(node) + || node.getBody().getStatements().isEmpty()) + return false; + + StatementTree st = node.getBody().getStatements().get(0); + if (!(st instanceof ExpressionStatementTree) + || !(((ExpressionStatementTree)st).getExpression() instanceof MethodInvocationTree)) + return false; + + MethodInvocationTree invocation = (MethodInvocationTree) + ((ExpressionStatementTree)st).getExpression(); + + return "this".contentEquals(TreeUtils.methodName(invocation)); + } + + public static final Tree firstStatement(Tree tree) { + Tree first; + if (tree.getKind() == Tree.Kind.BLOCK) { + BlockTree block = (BlockTree)tree; + if (block.getStatements().isEmpty()) { + first = block; + } else { + first = block.getStatements().iterator().next(); + } + } else { + first = tree; + } + return first; + } + + /** + * Determine whether the given class contains an explicit constructor. + * + * @param node a class tree + * @return true, iff there is an explicit constructor + */ + public static boolean hasExplicitConstructor(ClassTree node) { + TypeElement elem = TreeUtils.elementFromDeclaration(node); + + for ( ExecutableElement ee : ElementFilter.constructorsIn(elem.getEnclosedElements())) { + MethodSymbol ms = (MethodSymbol) ee; + long mod = ms.flags(); + + if ((mod & Flags.SYNTHETIC) == 0) { + return true; + } + } + return false; + } + + /** + * Returns true if the tree is of a diamond type. + * In contrast to the implementation in TreeInfo, this version + * works on Trees. + * + * @see com.sun.tools.javac.tree.TreeInfo#isDiamond(JCTree) + */ + public static final boolean isDiamondTree(Tree tree) { + switch (tree.getKind()) { + case ANNOTATED_TYPE: return isDiamondTree(((AnnotatedTypeTree)tree).getUnderlyingType()); + case PARAMETERIZED_TYPE: return ((ParameterizedTypeTree)tree).getTypeArguments().isEmpty(); + case NEW_CLASS: return isDiamondTree(((NewClassTree)tree).getIdentifier()); + default: return false; + } + } + + /** + * Returns true if the tree represents a {@code String} concatenation + * operation + */ + public static final boolean isStringConcatenation(Tree tree) { + return (tree.getKind() == Tree.Kind.PLUS + && TypesUtils.isString(InternalUtils.typeOf(tree))); + } + + /** + * Returns true if the compound assignment tree is a string concatenation + */ + public static final boolean isStringCompoundConcatenation(CompoundAssignmentTree tree) { + return (tree.getKind() == Tree.Kind.PLUS_ASSIGNMENT + && TypesUtils.isString(InternalUtils.typeOf(tree))); + } + + /** + * Returns true if the node is a constant-time expression. + * + * A tree is a constant-time expression if it is: + * <ol> + * <li>a literal tree + * <li>a reference to a final variable initialized with a compile time + * constant + * <li>a String concatenation of two compile time constants + * </ol> + */ + public static boolean isCompileTimeString(ExpressionTree node) { + ExpressionTree tree = TreeUtils.skipParens(node); + if (tree instanceof LiteralTree) { + return true; + } + + if (TreeUtils.isUseOfElement(tree)) { + Element elt = TreeUtils.elementFromUse(tree); + return ElementUtils.isCompileTimeConstant(elt); + } else if (TreeUtils.isStringConcatenation(tree)) { + BinaryTree binOp = (BinaryTree) tree; + return isCompileTimeString(binOp.getLeftOperand()) + && isCompileTimeString(binOp.getRightOperand()); + } else { + return false; + } + } + + /** + * Returns the receiver tree of a field access or a method invocation + */ + public static ExpressionTree getReceiverTree(ExpressionTree expression) { + ExpressionTree receiver = TreeUtils.skipParens(expression); + + if (!(receiver.getKind() == Tree.Kind.METHOD_INVOCATION + || receiver.getKind() == Tree.Kind.MEMBER_SELECT + || receiver.getKind() == Tree.Kind.IDENTIFIER + || receiver.getKind() == Tree.Kind.ARRAY_ACCESS)) { + // No receiver tree for anything but these four kinds. + return null; + } + + if (receiver.getKind() == Tree.Kind.METHOD_INVOCATION) { + // Trying to handle receiver calls to trees of the form + // ((m).getArray()) + // returns the type of 'm' in this case + receiver = ((MethodInvocationTree)receiver).getMethodSelect(); + + if (receiver.getKind() == Tree.Kind.IDENTIFIER) { + // It's a method call "m(foo)" without an explicit receiver + return null; + } else if (receiver.getKind() == Tree.Kind.MEMBER_SELECT) { + receiver = ((MemberSelectTree)receiver).getExpression(); + } else { + // Otherwise, e.g. a NEW_CLASS: nothing to do. + } + } else if (receiver.getKind() == Tree.Kind.IDENTIFIER) { + // It's a field access on implicit this or a local variable/parameter. + return null; + } else if (receiver.getKind() == Tree.Kind.ARRAY_ACCESS) { + return TreeUtils.skipParens(((ArrayAccessTree)receiver).getExpression()); + } else if (receiver.getKind() == Tree.Kind.MEMBER_SELECT) { + receiver = ((MemberSelectTree)receiver).getExpression(); + // Avoid int.class + if (receiver instanceof PrimitiveTypeTree) { + return null; + } + } + + // Receiver is now really just the receiver tree. + return TreeUtils.skipParens(receiver); + } + + // TODO: What about anonymous classes? + // Adding Tree.Kind.NEW_CLASS here doesn't work, because then a + // tree gets cast to ClassTree when it is actually a NewClassTree, + // for example in enclosingClass above. + private final static Set<Tree.Kind> classTreeKinds = EnumSet.of( + Tree.Kind.CLASS, + Tree.Kind.ENUM, + Tree.Kind.INTERFACE, + Tree.Kind.ANNOTATION_TYPE + ); + + public static Set<Tree.Kind> classTreeKinds() { + return classTreeKinds; + } + + /** + * Is the given tree kind a class, i.e. a class, enum, + * interface, or annotation type. + * + * @param tree the tree to test + * @return true, iff the given kind is a class kind + */ + public static boolean isClassTree(Tree tree) { + return classTreeKinds().contains(tree.getKind()); + } + + private final static Set<Tree.Kind> typeTreeKinds = EnumSet.of( + Tree.Kind.PRIMITIVE_TYPE, + Tree.Kind.PARAMETERIZED_TYPE, + Tree.Kind.TYPE_PARAMETER, + Tree.Kind.ARRAY_TYPE, + Tree.Kind.UNBOUNDED_WILDCARD, + Tree.Kind.EXTENDS_WILDCARD, + Tree.Kind.SUPER_WILDCARD, + Tree.Kind.ANNOTATED_TYPE + ); + + public static Set<Tree.Kind> typeTreeKinds() { + return typeTreeKinds; + } + + /** + * Is the given tree a type instantiation? + * + * TODO: this is an under-approximation: e.g. an identifier could + * be either a type use or an expression. How can we distinguish. + * + * @param tree the tree to test + * @return true, iff the given tree is a type + */ + public static boolean isTypeTree(Tree tree) { + return typeTreeKinds().contains(tree.getKind()); + } + + /** + * Returns true if the given element is an invocation of the method, or + * of any method that overrides that one. + */ + public static boolean isMethodInvocation(Tree tree, ExecutableElement method, ProcessingEnvironment env) { + if (!(tree instanceof MethodInvocationTree)) { + return false; + } + MethodInvocationTree methInvok = (MethodInvocationTree)tree; + ExecutableElement invoked = TreeUtils.elementFromUse(methInvok); + return isMethod(invoked, method, env); + } + + /** Returns true if the given element is, or overrides, method. */ + private static boolean isMethod(ExecutableElement questioned, ExecutableElement method, ProcessingEnvironment env) { + return (questioned.equals(method) + || env.getElementUtils().overrides(questioned, method, + (TypeElement)questioned.getEnclosingElement())); + } + + /** + * Returns the ExecutableElement for a method declaration of + * methodName, in class typeName, with params parameters. + * + * TODO: to precisely resolve method overloading, we should use parameter types and not just + * the number of parameters! + */ + public static ExecutableElement getMethod(String typeName, String methodName, int params, ProcessingEnvironment env) { + TypeElement mapElt = env.getElementUtils().getTypeElement(typeName); + for (ExecutableElement exec : ElementFilter.methodsIn(mapElt.getEnclosedElements())) { + if (exec.getSimpleName().contentEquals(methodName) + && exec.getParameters().size() == params) + return exec; + } + ErrorReporter.errorAbort("TreeUtils.getMethod: shouldn't be here!"); + return null; // dead code + } + + /** + * Determine whether the given expression is either "this" or an outer + * "C.this". + * + * <p> + * TODO: Should this also handle "super"? + */ + public static final boolean isExplicitThisDereference(ExpressionTree tree) { + if (tree.getKind() == Tree.Kind.IDENTIFIER + && ((IdentifierTree)tree).getName().contentEquals("this")) { + // Explicit this reference "this" + return true; + } + + if (tree.getKind() != Tree.Kind.MEMBER_SELECT) { + return false; + } + + MemberSelectTree memSelTree = (MemberSelectTree) tree; + if (memSelTree.getIdentifier().contentEquals("this")) { + // Outer this reference "C.this" + return true; + } + return false; + } + + /** + * Determine whether {@code tree} is a class literal, such + * as + * + * <pre> + * <em>Object</em> . <em>class</em> + * </pre> + * + * @return true iff if tree is a class literal + */ + public static boolean isClassLiteral(Tree tree) { + if (tree.getKind() != Tree.Kind.MEMBER_SELECT) { + return false; + } + return "class".equals(((MemberSelectTree) tree).getIdentifier().toString()); + } + + /** + * Determine whether {@code tree} is a field access expressions, such + * as + * + * <pre> + * <em>f</em> + * <em>obj</em> . <em>f</em> + * </pre> + * + * @return true iff if tree is a field access expression (implicit or + * explicit) + */ + public static boolean isFieldAccess(Tree tree) { + if (tree.getKind().equals(Tree.Kind.MEMBER_SELECT)) { + // explicit field access + MemberSelectTree memberSelect = (MemberSelectTree) tree; + Element el = TreeUtils.elementFromUse(memberSelect); + return el.getKind().isField(); + } else if (tree.getKind().equals(Tree.Kind.IDENTIFIER)) { + // implicit field access + IdentifierTree ident = (IdentifierTree) tree; + Element el = TreeUtils.elementFromUse(ident); + return el.getKind().isField() + && !ident.getName().contentEquals("this") && !ident.getName().contentEquals("super"); + } + return false; + } + + /** + * Compute the name of the field that the field access {@code tree} + * accesses. Requires {@code tree} to be a field access, as determined + * by {@code isFieldAccess}. + * + * @return the name of the field accessed by {@code tree}. + */ + public static String getFieldName(Tree tree) { + assert isFieldAccess(tree); + if (tree.getKind().equals(Tree.Kind.MEMBER_SELECT)) { + MemberSelectTree mtree = (MemberSelectTree) tree; + return mtree.getIdentifier().toString(); + } else { + IdentifierTree itree = (IdentifierTree) tree; + return itree.getName().toString(); + } + } + + /** + * Determine whether {@code tree} refers to a method element, such + * as + * + * <pre> + * <em>m</em>(...) + * <em>obj</em> . <em>m</em>(...) + * </pre> + * + * @return true iff if tree is a method access expression (implicit or + * explicit) + */ + public static boolean isMethodAccess(Tree tree) { + if (tree.getKind().equals(Tree.Kind.MEMBER_SELECT)) { + // explicit method access + MemberSelectTree memberSelect = (MemberSelectTree) tree; + Element el = TreeUtils.elementFromUse(memberSelect); + return el.getKind() == ElementKind.METHOD + || el.getKind() == ElementKind.CONSTRUCTOR; + } else if (tree.getKind().equals(Tree.Kind.IDENTIFIER)) { + // implicit method access + IdentifierTree ident = (IdentifierTree) tree; + // The field "super" and "this" are also legal methods + if (ident.getName().contentEquals("super") + || ident.getName().contentEquals("this")) { + return true; + } + Element el = TreeUtils.elementFromUse(ident); + return el.getKind() == ElementKind.METHOD + || el.getKind() == ElementKind.CONSTRUCTOR; + } + return false; + } + + /** + * Compute the name of the method that the method access {@code tree} + * accesses. Requires {@code tree} to be a method access, as determined + * by {@code isMethodAccess}. + * + * @return the name of the method accessed by {@code tree}. + */ + public static String getMethodName(Tree tree) { + assert isMethodAccess(tree); + if (tree.getKind().equals(Tree.Kind.MEMBER_SELECT)) { + MemberSelectTree mtree = (MemberSelectTree) tree; + return mtree.getIdentifier().toString(); + } else { + IdentifierTree itree = (IdentifierTree) tree; + return itree.getName().toString(); + } + } + + /** + * @return {@code true} if and only if {@code tree} can have a type + * annotation. + * + * TODO: is this implementation precise enough? E.g. does + * a .class literal work correctly? + */ + public static boolean canHaveTypeAnnotation(Tree tree) { + return ((JCTree) tree).type != null; + } + + /** + * Returns true if and only if the given {@code tree} represents a field + * access of the given {@link VariableElement}. + */ + public static boolean isSpecificFieldAccess(Tree tree, VariableElement var) { + if (tree instanceof MemberSelectTree) { + MemberSelectTree memSel = (MemberSelectTree) tree; + Element field = TreeUtils.elementFromUse(memSel); + return field.equals(var); + } else if (tree instanceof IdentifierTree) { + IdentifierTree idTree = (IdentifierTree) tree; + Element field = TreeUtils.elementFromUse(idTree); + return field.equals(var); + } else { + return false; + } + } + + /** + * Returns the VariableElement for a field declaration. + * + * @param typeName the class where the field is declared + * @param fieldName the name of the field + * @param env the processing environment + * @return the VariableElement for typeName.fieldName + */ + public static VariableElement getField(String typeName, String fieldName, ProcessingEnvironment env) { + TypeElement mapElt = env.getElementUtils().getTypeElement(typeName); + for (VariableElement var : ElementFilter.fieldsIn(mapElt.getEnclosedElements())) { + if (var.getSimpleName().contentEquals(fieldName)) { + return var; + } + } + ErrorReporter.errorAbort("TreeUtils.getField: shouldn't be here!"); + return null; // dead code + } + + /** Determine whether the given tree represents an ExpressionTree. + * + * TODO: is there a nicer way than an instanceof? + * + * @param tree the Tree to test + * @return whether the tree is an ExpressionTree + */ + public static boolean isExpressionTree(Tree tree) { + return tree instanceof ExpressionTree; + } + + /** + * @param node the method invocation to check + * @return true if this is a super call to the {@link Enum} constructor + */ + public static boolean isEnumSuper(MethodInvocationTree node) { + ExecutableElement ex = TreeUtils.elementFromUse(node); + Name name = ElementUtils.getQualifiedClassName(ex); + boolean correctClass = "java.lang.Enum".contentEquals(name); + boolean correctMethod = "<init>".contentEquals(ex.getSimpleName()); + return correctClass && correctMethod; + } + + /** Determine whether the given tree represents a declaration of a type + * (including type parameters). + * + * @param node the Tree to test + * @return true if the tree is a type declaration + */ + public static boolean isTypeDeclaration(Tree node) { + switch (node.getKind()) { + // These tree kinds are always declarations. Uses of the declared + // types have tree kind IDENTIFIER. + case ANNOTATION_TYPE: + case CLASS: + case ENUM: + case INTERFACE: + case TYPE_PARAMETER: + return true; + + default: + return false; + } + } + + /** + * @see Object#getClass() + * @return true iff invocationTree is an instance of getClass() + */ + public static boolean isGetClassInvocation(MethodInvocationTree invocationTree) { + final Element declarationElement = elementFromUse(invocationTree); + String ownerName = ElementUtils.getQualifiedClassName(declarationElement.getEnclosingElement()).toString(); + return ownerName.equals("java.lang.Object") + && declarationElement.getSimpleName().toString().equals("getClass"); + } +} diff --git a/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/TypeAnnotationUtils.java b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/TypeAnnotationUtils.java new file mode 100644 index 0000000000..c7faceb53a --- /dev/null +++ b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/TypeAnnotationUtils.java @@ -0,0 +1,627 @@ +package org.checkerframework.javacutil; + +import java.lang.reflect.InvocationTargetException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Map; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.AnnotationValueVisitor; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; + +import com.sun.tools.javac.code.Attribute; +import com.sun.tools.javac.code.Attribute.TypeCompound; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.TargetType; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.code.TypeAnnotationPosition; +import com.sun.tools.javac.processing.JavacProcessingEnvironment; +import com.sun.tools.javac.tree.JCTree.JCLambda; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.Name; +import com.sun.tools.javac.util.Pair; + +/** + * A collection of helper methods related to type annotation handling. + * + * @see AnnotationUtils + */ +public class TypeAnnotationUtils { + + // Class cannot be instantiated. + private TypeAnnotationUtils() { throw new AssertionError("Class TypeAnnotationUtils cannot be instantiated."); } + + /** + * Check whether a TypeCompound is contained in a list of TypeCompounds. + * + * @param list the input list of TypeCompounds + * @param tc the TypeCompound to find + * @return true, iff a TypeCompound equal to tc is contained in list + */ + public static boolean isTypeCompoundContained(Types types, List<TypeCompound> list, TypeCompound tc) { + for (Attribute.TypeCompound rawat : list) { + if (contentEquals(rawat.type.tsym.name, tc.type.tsym.name) && + // TODO: in previous line, it would be nicer to use reference equality: + // rawat.type == tc.type && + // or at least "isSameType": + // types.isSameType(rawat.type, tc.type) && + // but each fails in some cases. + rawat.values.equals(tc.values) && + isSameTAPositionExceptTreePos(rawat.position, tc.position)) { + return true; + } + } + return false; + } + + private static boolean contentEquals(Name n1, Name n2) { + // Views of underlying bytes, not copies as with Name#contentEquals + ByteBuffer b1 = ByteBuffer.wrap(n1.getByteArray(), n1.getByteOffset(), n1.getByteLength()); + ByteBuffer b2 = ByteBuffer.wrap(n2.getByteArray(), n2.getByteOffset(), n2.getByteLength()); + + return b1.equals(b2); + } + + /** + * Compare two TypeAnnotationPositions for equality. + * + * @param p1 the first position + * @param p2 the second position + * @return true, iff the two positions are equal + */ + public static boolean isSameTAPosition(TypeAnnotationPosition p1, + TypeAnnotationPosition p2) { + return isSameTAPositionExceptTreePos(p1, p2) && p1.pos == p2.pos; + } + + public static boolean isSameTAPositionExceptTreePos(TypeAnnotationPosition p1, + TypeAnnotationPosition p2) { + return p1.isValidOffset == p2.isValidOffset && + p1.bound_index == p2.bound_index && + p1.exception_index == p2.exception_index && + p1.location.equals(p2.location) && + Arrays.equals(p1.lvarIndex, p2.lvarIndex) && + Arrays.equals(p1.lvarLength, p2.lvarLength) && + Arrays.equals(p1.lvarOffset, p2.lvarOffset) && + p1.offset == p2.offset && + p1.onLambda == p2.onLambda && + p1.parameter_index == p2.parameter_index && + p1.type == p2.type && + p1.type_index == p2.type_index; + } + + /** + * Returns a newly created Attribute.Compound corresponding to an + * argument AnnotationMirror. + * + * @param am an AnnotationMirror, which may be part of an AST or an internally + * created subclass + * @return a new Attribute.Compound corresponding to the AnnotationMirror + */ + public static Attribute.Compound createCompoundFromAnnotationMirror(ProcessingEnvironment env, + AnnotationMirror am) { + // Create a new Attribute to match the AnnotationMirror. + List<Pair<Symbol.MethodSymbol, Attribute>> values = List.nil(); + for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : + am.getElementValues().entrySet()) { + Attribute attribute = attributeFromAnnotationValue(env, entry.getKey(), entry.getValue()); + values = values.append(new Pair<>((Symbol.MethodSymbol)entry.getKey(), + attribute)); + } + return new Attribute.Compound((Type.ClassType)am.getAnnotationType(), values); + } + + /** + * Returns a newly created Attribute.TypeCompound corresponding to an + * argument AnnotationMirror. + * + * @param am an AnnotationMirror, which may be part of an AST or an internally + * created subclass + * @param tapos the type annotation position to use + * @return a new Attribute.TypeCompound corresponding to the AnnotationMirror + */ + public static Attribute.TypeCompound createTypeCompoundFromAnnotationMirror(ProcessingEnvironment env, + AnnotationMirror am, TypeAnnotationPosition tapos) { + // Create a new Attribute to match the AnnotationMirror. + List<Pair<Symbol.MethodSymbol, Attribute>> values = List.nil(); + for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : + am.getElementValues().entrySet()) { + Attribute attribute = attributeFromAnnotationValue(env, entry.getKey(), entry.getValue()); + values = values.append(new Pair<>((Symbol.MethodSymbol)entry.getKey(), + attribute)); + } + return new Attribute.TypeCompound((Type.ClassType)am.getAnnotationType(), values, tapos); + } + + /** + * Returns a newly created Attribute corresponding to an argument + * AnnotationValue. + * + * @param meth the ExecutableElement that is assigned the value, needed for empty arrays + * @param av an AnnotationValue, which may be part of an AST or an internally + * created subclass + * @return a new Attribute corresponding to the AnnotationValue + */ + public static Attribute attributeFromAnnotationValue(ProcessingEnvironment env, ExecutableElement meth, AnnotationValue av) { + return av.accept(new AttributeCreator(env, meth), null); + } + + private static class AttributeCreator implements AnnotationValueVisitor<Attribute, Void> { + private final ProcessingEnvironment processingEnv; + private final Types modelTypes; + private final Elements elements; + private final com.sun.tools.javac.code.Types javacTypes; + + private final ExecutableElement meth; + + public AttributeCreator(ProcessingEnvironment env, ExecutableElement meth) { + this.processingEnv = env; + Context context = ((JavacProcessingEnvironment)env).getContext(); + this.elements = env.getElementUtils(); + this.modelTypes = env.getTypeUtils(); + this.javacTypes = com.sun.tools.javac.code.Types.instance(context); + + this.meth = meth; + } + + @Override + public Attribute visit(AnnotationValue av, Void p) { + return av.accept(this, p); + } + + @Override + public Attribute visit(AnnotationValue av) { + return visit(av, null); + } + + @Override + public Attribute visitBoolean(boolean b, Void p) { + TypeMirror booleanType = modelTypes.getPrimitiveType(TypeKind.BOOLEAN); + return new Attribute.Constant((Type) booleanType, b ? 1 : 0); + } + + @Override + public Attribute visitByte(byte b, Void p) { + TypeMirror byteType = modelTypes.getPrimitiveType(TypeKind.BYTE); + return new Attribute.Constant((Type)byteType, b); + } + + @Override + public Attribute visitChar(char c, Void p) { + TypeMirror charType = modelTypes.getPrimitiveType(TypeKind.CHAR); + return new Attribute.Constant((Type)charType, c); + } + + @Override + public Attribute visitDouble(double d, Void p) { + TypeMirror doubleType = modelTypes.getPrimitiveType(TypeKind.DOUBLE); + return new Attribute.Constant((Type)doubleType, d); + } + + @Override + public Attribute visitFloat(float f, Void p) { + TypeMirror floatType = modelTypes.getPrimitiveType(TypeKind.FLOAT); + return new Attribute.Constant((Type)floatType, f); + } + + @Override + public Attribute visitInt(int i, Void p) { + TypeMirror intType = modelTypes.getPrimitiveType(TypeKind.INT); + return new Attribute.Constant((Type)intType, i); + } + + @Override + public Attribute visitLong(long i, Void p) { + TypeMirror longType = modelTypes.getPrimitiveType(TypeKind.LONG); + return new Attribute.Constant((Type)longType, i); + } + + @Override + public Attribute visitShort(short s, Void p) { + TypeMirror shortType = modelTypes.getPrimitiveType(TypeKind.SHORT); + return new Attribute.Constant((Type)shortType, s); + } + + @Override + public Attribute visitString(String s, Void p) { + TypeMirror stringType = elements.getTypeElement("java.lang.String").asType(); + return new Attribute.Constant((Type)stringType, s); + } + + @Override + public Attribute visitType(TypeMirror t, Void p) { + if (t instanceof Type) { + return new Attribute.Class(javacTypes, (Type)t); + } + + assert false : "Unexpected type of TypeMirror: " + t.getClass(); + return null; + } + + @Override + public Attribute visitEnumConstant(VariableElement c, Void p) { + if (c instanceof Symbol.VarSymbol) { + Symbol.VarSymbol sym = (Symbol.VarSymbol) c; + if (sym.getKind() == ElementKind.ENUM_CONSTANT) { + return new Attribute.Enum(sym.type, sym); + } + } + + assert false : "Unexpected type of VariableElement: " + c.getClass(); + return null; + } + + @Override + public Attribute visitAnnotation(AnnotationMirror a, Void p) { + return createCompoundFromAnnotationMirror(processingEnv, a); + } + + @Override + public Attribute visitArray(java.util.List<? extends AnnotationValue> vals, Void p) { + if (!vals.isEmpty()) { + List<Attribute> valAttrs = List.nil(); + for (AnnotationValue av : vals) { + valAttrs = valAttrs.append(av.accept(this, p)); + } + ArrayType arrayType = modelTypes.getArrayType(valAttrs.get(0).type); + return new Attribute.Array((Type)arrayType, valAttrs); + } else { + return new Attribute.Array((Type) meth.getReturnType(), List.<Attribute>nil()); + } + } + + @Override + public Attribute visitUnknown(AnnotationValue av, Void p) { + assert false : "Unexpected type of AnnotationValue: " + av.getClass(); + return null; + } + } + + /** + * An interface to abstract a Java 8 and a Java 9 version of how + * to get a RET reference. + * These methods must then be implemented using reflection in order to + * compile in either setting. + * Note that we cannot use lambda for this as long as we want to + * support Java 7. + */ + interface Call8or9<RET> { + RET call8() throws Throwable; + RET call9() throws Throwable; + } + + /** + * Use the SourceVersion to decide whether to call the Java 8 or Java 9 version. + * Catch all exceptions and abort if one occurs - the reflection code should + * never break once fully debugged. + * + * @param tc the TAPCall abstraction to encapsulate two methods + * @return the created TypeAnnotationPosition + */ + private static <RET> RET call8or9(Call8or9<RET> tc) { + try { + boolean hasNine; + try { + hasNine = SourceVersion.valueOf("RELEASE_9") != null; + } catch (IllegalArgumentException iae) { + hasNine = false; + } + if (hasNine) { + return tc.call9(); + } else { + boolean hasEight; + try { + hasEight = SourceVersion.valueOf("RELEASE_8") != null; + } catch (IllegalArgumentException iae) { + hasEight = false; + } + if (hasEight) { + return tc.call8(); + } else { + assert false : "Checker Framework needs a Java 8 or 9 javac."; + return null; + } + } + } catch (Throwable t) { + assert false : "Checker Framework internal error: " + t; + t.printStackTrace(); + return null; + } + } + + public static TypeAnnotationPosition unknownTAPosition() { + return call8or9( + new Call8or9<TypeAnnotationPosition>() { + @Override + public TypeAnnotationPosition call8() throws InstantiationException, IllegalAccessException { + return TypeAnnotationPosition.class.newInstance(); + } + @Override + public TypeAnnotationPosition call9() throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException { + return (TypeAnnotationPosition) TypeAnnotationPosition.class + .getField("unknown") + .get(null); + } + } + ); + } + + public static TypeAnnotationPosition methodReturnTAPosition(final int pos) { + return call8or9( + new Call8or9<TypeAnnotationPosition>() { + @Override + public TypeAnnotationPosition call8() throws InstantiationException, IllegalAccessException, IllegalArgumentException, NoSuchFieldException, SecurityException { + TypeAnnotationPosition tapos = TypeAnnotationPosition.class.newInstance(); + TypeAnnotationPosition.class.getField("type").set(tapos, TargetType.METHOD_RETURN); + TypeAnnotationPosition.class.getField("pos").set(tapos, pos); + return tapos; + } + @Override + public TypeAnnotationPosition call9() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { + return (TypeAnnotationPosition) TypeAnnotationPosition.class + .getMethod("methodReturn", int.class) + .invoke(null, pos); + } + } + ); + } + + public static TypeAnnotationPosition methodReceiverTAPosition(final int pos) { + return call8or9( + new Call8or9<TypeAnnotationPosition>() { + @Override + public TypeAnnotationPosition call8() throws InstantiationException, IllegalAccessException, IllegalArgumentException, NoSuchFieldException, SecurityException { + TypeAnnotationPosition tapos = TypeAnnotationPosition.class.newInstance(); + TypeAnnotationPosition.class.getField("type").set(tapos, TargetType.METHOD_RECEIVER); + TypeAnnotationPosition.class.getField("pos").set(tapos, pos); + return tapos; + } + @Override + public TypeAnnotationPosition call9() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { + return (TypeAnnotationPosition) TypeAnnotationPosition.class + .getMethod("methodReceiver", int.class) + .invoke(null, pos); + } + } + ); + } + + public static TypeAnnotationPosition methodParameterTAPosition(final int pidx, final int pos) { + return call8or9( + new Call8or9<TypeAnnotationPosition>() { + @Override + public TypeAnnotationPosition call8() throws InstantiationException, IllegalAccessException, IllegalArgumentException, NoSuchFieldException, SecurityException { + TypeAnnotationPosition tapos = TypeAnnotationPosition.class.newInstance(); + TypeAnnotationPosition.class.getField("type").set(tapos, TargetType.METHOD_FORMAL_PARAMETER); + TypeAnnotationPosition.class.getField("parameter_index").set(tapos, pidx); + TypeAnnotationPosition.class.getField("pos").set(tapos, pos); + return tapos; + } + @Override + public TypeAnnotationPosition call9() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { + return (TypeAnnotationPosition) TypeAnnotationPosition.class + .getMethod("methodParameter", int.class, int.class) + .invoke(null, pidx, pos); + } + } + ); + } + + public static TypeAnnotationPosition methodThrowsTAPosition(final int tidx, final int pos) { + return call8or9( + new Call8or9<TypeAnnotationPosition>() { + @Override + public TypeAnnotationPosition call8() throws InstantiationException, IllegalAccessException, IllegalArgumentException, NoSuchFieldException, SecurityException { + TypeAnnotationPosition tapos = TypeAnnotationPosition.class.newInstance(); + TypeAnnotationPosition.class.getField("type").set(tapos, TargetType.THROWS); + TypeAnnotationPosition.class.getField("type_index").set(tapos, tidx); + TypeAnnotationPosition.class.getField("pos").set(tapos, pos); + return tapos; + } + @Override + public TypeAnnotationPosition call9() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, NoSuchFieldException { + return (TypeAnnotationPosition) TypeAnnotationPosition.class + .getMethod("methodThrows", List.class, JCLambda.class, int.class, int.class) + .invoke(null, TypeAnnotationPosition.class.getField("emptyPath").get(null), null, tidx, pos); + } + } + ); + } + + public static TypeAnnotationPosition fieldTAPosition(final int pos) { + return call8or9( + new Call8or9<TypeAnnotationPosition>() { + @Override + public TypeAnnotationPosition call8() throws InstantiationException, IllegalAccessException, IllegalArgumentException, NoSuchFieldException, SecurityException { + TypeAnnotationPosition tapos = TypeAnnotationPosition.class.newInstance(); + TypeAnnotationPosition.class.getField("type").set(tapos, TargetType.FIELD); + TypeAnnotationPosition.class.getField("pos").set(tapos, pos); + return tapos; + } + @Override + public TypeAnnotationPosition call9() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { + return (TypeAnnotationPosition) TypeAnnotationPosition.class + .getMethod("field", int.class) + .invoke(null, pos); + } + } + ); + } + + public static TypeAnnotationPosition classExtendsTAPosition(final int implidx, final int pos) { + return call8or9( + new Call8or9<TypeAnnotationPosition>() { + @Override + public TypeAnnotationPosition call8() throws InstantiationException, IllegalAccessException, IllegalArgumentException, NoSuchFieldException, SecurityException { + TypeAnnotationPosition tapos = TypeAnnotationPosition.class.newInstance(); + TypeAnnotationPosition.class.getField("type").set(tapos, TargetType.CLASS_EXTENDS); + TypeAnnotationPosition.class.getField("type_index").set(tapos, implidx); + TypeAnnotationPosition.class.getField("pos").set(tapos, pos); + return tapos; + } + @Override + public TypeAnnotationPosition call9() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { + return (TypeAnnotationPosition) TypeAnnotationPosition.class + .getMethod("classExtends", int.class, int.class) + .invoke(null, implidx, pos); + } + } + ); + } + + public static TypeAnnotationPosition typeParameterTAPosition(final int tpidx, final int pos) { + return call8or9( + new Call8or9<TypeAnnotationPosition>() { + @Override + public TypeAnnotationPosition call8() throws InstantiationException, IllegalAccessException, IllegalArgumentException, NoSuchFieldException, SecurityException { + TypeAnnotationPosition tapos = TypeAnnotationPosition.class.newInstance(); + TypeAnnotationPosition.class.getField("type").set(tapos, TargetType.CLASS_TYPE_PARAMETER); + TypeAnnotationPosition.class.getField("parameter_index").set(tapos, tpidx); + TypeAnnotationPosition.class.getField("pos").set(tapos, pos); + return tapos; + } + @Override + public TypeAnnotationPosition call9() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, NoSuchFieldException { + return (TypeAnnotationPosition) TypeAnnotationPosition.class + .getMethod("typeParameter", List.class, JCLambda.class, int.class, int.class) + .invoke(null, TypeAnnotationPosition.class.getField("emptyPath").get(null), null, tpidx, pos); + } + } + ); + } + + public static TypeAnnotationPosition methodTypeParameterTAPosition(final int tpidx, final int pos) { + return call8or9( + new Call8or9<TypeAnnotationPosition>() { + @Override + public TypeAnnotationPosition call8() throws InstantiationException, IllegalAccessException, IllegalArgumentException, NoSuchFieldException, SecurityException { + TypeAnnotationPosition tapos = TypeAnnotationPosition.class.newInstance(); + TypeAnnotationPosition.class.getField("type").set(tapos, TargetType.METHOD_TYPE_PARAMETER); + TypeAnnotationPosition.class.getField("parameter_index").set(tapos, tpidx); + TypeAnnotationPosition.class.getField("pos").set(tapos, pos); + return tapos; + } + @Override + public TypeAnnotationPosition call9() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, NoSuchFieldException { + return (TypeAnnotationPosition) TypeAnnotationPosition.class + .getMethod("methodTypeParameter", List.class, JCLambda.class, int.class, int.class) + .invoke(null, TypeAnnotationPosition.class.getField("emptyPath").get(null), null, tpidx, pos); + } + } + ); + } + + public static TypeAnnotationPosition typeParameterBoundTAPosition(final int tpidx, final int bndidx, final int pos) { + return call8or9( + new Call8or9<TypeAnnotationPosition>() { + @Override + public TypeAnnotationPosition call8() throws InstantiationException, IllegalAccessException, IllegalArgumentException, NoSuchFieldException, SecurityException { + TypeAnnotationPosition tapos = TypeAnnotationPosition.class.newInstance(); + TypeAnnotationPosition.class.getField("type").set(tapos, TargetType.CLASS_TYPE_PARAMETER_BOUND); + TypeAnnotationPosition.class.getField("parameter_index").set(tapos, tpidx); + TypeAnnotationPosition.class.getField("bound_index").set(tapos, bndidx); + TypeAnnotationPosition.class.getField("pos").set(tapos, pos); + return tapos; + } + @Override + public TypeAnnotationPosition call9() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, NoSuchFieldException { + return (TypeAnnotationPosition) TypeAnnotationPosition.class + .getMethod("typeParameterBound", List.class, JCLambda.class, int.class, int.class, int.class) + .invoke(null, TypeAnnotationPosition.class.getField("emptyPath").get(null), null, tpidx, bndidx, pos); + } + } + ); + } + + public static TypeAnnotationPosition methodTypeParameterBoundTAPosition(final int tpidx, final int bndidx, final int pos) { + return call8or9( + new Call8or9<TypeAnnotationPosition>() { + @Override + public TypeAnnotationPosition call8() throws InstantiationException, IllegalAccessException, IllegalArgumentException, NoSuchFieldException, SecurityException { + TypeAnnotationPosition tapos = TypeAnnotationPosition.class.newInstance(); + TypeAnnotationPosition.class.getField("type").set(tapos, TargetType.METHOD_TYPE_PARAMETER_BOUND); + TypeAnnotationPosition.class.getField("parameter_index").set(tapos, tpidx); + TypeAnnotationPosition.class.getField("bound_index").set(tapos, bndidx); + TypeAnnotationPosition.class.getField("pos").set(tapos, pos); + return tapos; + } + @Override + public TypeAnnotationPosition call9() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, NoSuchFieldException { + return (TypeAnnotationPosition) TypeAnnotationPosition.class + .getMethod("methodTypeParameterBound", List.class, JCLambda.class, int.class, int.class, int.class) + .invoke(null, TypeAnnotationPosition.class.getField("emptyPath").get(null), null, tpidx, bndidx, pos); + } + } + ); + } + + public static TypeAnnotationPosition copyTAPosition(final TypeAnnotationPosition tapos) { + return call8or9( + new Call8or9<TypeAnnotationPosition>() { + @Override + public TypeAnnotationPosition call8() throws InstantiationException, IllegalAccessException, IllegalArgumentException, NoSuchFieldException, SecurityException { + return copyTAPosition8(tapos); + } + @Override + public TypeAnnotationPosition call9() throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException, InvocationTargetException, NoSuchMethodException { + return (TypeAnnotationPosition) TypeAnnotationPosition.class + .getMethod("copy", TypeAnnotationPosition.class) + .invoke(null, tapos); + } + } + ); + } + + private static TypeAnnotationPosition copyTAPosition8(TypeAnnotationPosition tapos) throws InstantiationException, IllegalAccessException, IllegalArgumentException, NoSuchFieldException, SecurityException { + TypeAnnotationPosition res = TypeAnnotationPosition.class.newInstance(); + res.isValidOffset = tapos.isValidOffset; + TypeAnnotationPosition.class.getField("bound_index").set(res, tapos.bound_index); + res.exception_index = tapos.exception_index; + res.location = List.from(tapos.location); + if (tapos.lvarIndex != null) { + res.lvarIndex = Arrays.copyOf(tapos.lvarIndex, tapos.lvarIndex.length); + } + if (tapos.lvarLength != null) { + res.lvarLength = Arrays.copyOf(tapos.lvarLength, tapos.lvarLength.length); + } + if (tapos.lvarOffset != null) { + res.lvarOffset = Arrays.copyOf(tapos.lvarOffset, tapos.lvarOffset.length); + } + res.offset = tapos.offset; + TypeAnnotationPosition.class.getField("onLambda").set(res, tapos.onLambda); + TypeAnnotationPosition.class.getField("parameter_index").set(res, tapos.parameter_index); + TypeAnnotationPosition.class.getField("pos").set(res, tapos.pos); + TypeAnnotationPosition.class.getField("type").set(res, tapos.type); + TypeAnnotationPosition.class.getField("type_index").set(res, tapos.type_index); + return res; + } + + public static Type unannotatedType(final Type in) { + return call8or9( + new Call8or9<Type>() { + @Override + public Type call8() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { + return (Type) Type.class + .getMethod("unannotatedType") + .invoke(in); + } + @Override + public Type call9() { + return in; + } + } + ); + } + +} diff --git a/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/TypesUtils.java b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/TypesUtils.java new file mode 100644 index 0000000000..949bf1362f --- /dev/null +++ b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/TypesUtils.java @@ -0,0 +1,446 @@ +package org.checkerframework.javacutil; + +import com.sun.tools.javac.code.Symtab; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.code.TypeTag; +import com.sun.tools.javac.model.JavacTypes; +import com.sun.tools.javac.processing.JavacProcessingEnvironment; +import com.sun.tools.javac.util.Context; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.Name; +import javax.lang.model.element.NestingKind; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.type.WildcardType; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; + +import static com.sun.tools.javac.code.TypeTag.WILDCARD; + +/** + * A utility class that helps with {@link TypeMirror}s. + * + */ +// TODO: This class needs significant restructuring +public final class TypesUtils { + + // Class cannot be instantiated + private TypesUtils() { throw new AssertionError("Class TypesUtils cannot be instantiated."); } + + /** + * Gets the fully qualified name for a provided type. It returns an empty + * name if type is an anonymous type. + * + * @param type the declared type + * @return the name corresponding to that type + */ + public static Name getQualifiedName(DeclaredType type) { + TypeElement element = (TypeElement) type.asElement(); + return element.getQualifiedName(); + } + + /** + * Checks if the type represents a java.lang.Object declared type. + * + * @param type the type + * @return true iff type represents java.lang.Object + */ + public static boolean isObject(TypeMirror type) { + return isDeclaredOfName(type, "java.lang.Object"); + } + + /** + * Checks if the type represents a java.lang.Class declared type. + * + * @param type the type + * @return true iff type represents java.lang.Class + */ + public static boolean isClass(TypeMirror type) { + return isDeclaredOfName(type, "java.lang.Class"); + } + + /** + * Checks if the type represents a java.lang.String declared type. + * TODO: it would be cleaner to use String.class.getCanonicalName(), but + * the two existing methods above don't do that, I guess for performance reasons. + * + * @param type the type + * @return true iff type represents java.lang.String + */ + public static boolean isString(TypeMirror type) { + return isDeclaredOfName(type, "java.lang.String"); + } + + /** + * Checks if the type represents a boolean type, that is either boolean + * (primitive type) or java.lang.Boolean. + * + * @param type the type to test + * @return true iff type represents a boolean type + */ + public static boolean isBooleanType(TypeMirror type) { + return isDeclaredOfName(type, "java.lang.Boolean") + || type.getKind().equals(TypeKind.BOOLEAN); + } + + /** + * Check if the type represent a declared type of the given qualified name + * + * @param type the type + * @return type iff type represents a declared type of the qualified name + */ + public static boolean isDeclaredOfName(TypeMirror type, CharSequence qualifiedName) { + return type.getKind() == TypeKind.DECLARED + && getQualifiedName((DeclaredType) type).contentEquals(qualifiedName); + } + + public static boolean isBoxedPrimitive(TypeMirror type) { + if (type.getKind() != TypeKind.DECLARED) { + return false; + } + + String qualifiedName = getQualifiedName((DeclaredType)type).toString(); + + return (qualifiedName.equals("java.lang.Boolean") + || qualifiedName.equals("java.lang.Byte") + || qualifiedName.equals("java.lang.Character") + || qualifiedName.equals("java.lang.Short") + || qualifiedName.equals("java.lang.Integer") + || qualifiedName.equals("java.lang.Long") + || qualifiedName.equals("java.lang.Double") + || qualifiedName.equals("java.lang.Float")); + } + + /** @return type represents a Throwable type (e.g. Exception, Error) **/ + public static boolean isThrowable(TypeMirror type) { + while (type != null && type.getKind() == TypeKind.DECLARED) { + DeclaredType dt = (DeclaredType) type; + TypeElement elem = (TypeElement) dt.asElement(); + Name name = elem.getQualifiedName(); + if ("java.lang.Throwable".contentEquals(name)) { + return true; + } + type = elem.getSuperclass(); + } + return false; + } + + /** + * Returns true iff the argument is an anonymous type. + * + * @return whether the argument is an anonymous type + */ + public static boolean isAnonymous(TypeMirror type) { + return (type instanceof DeclaredType) + && (((TypeElement) ((DeclaredType) type).asElement()).getNestingKind() + .equals(NestingKind.ANONYMOUS)); + } + + /** + * Returns true iff the argument is a primitive type. + * + * @return whether the argument is a primitive type + */ + public static boolean isPrimitive(TypeMirror type) { + switch (type.getKind()) { + case BOOLEAN: + case BYTE: + case CHAR: + case DOUBLE: + case FLOAT: + case INT: + case LONG: + case SHORT: + return true; + default: + return false; + } + } + + /** + * Returns true iff the arguments are both the same primitive types. + * + * @return whether the arguments are the same primitive types + */ + public static boolean areSamePrimitiveTypes(TypeMirror left, TypeMirror right) { + if (!isPrimitive(left) || !isPrimitive(right)) { + return false; + } + + return (left.getKind() == right.getKind()); + } + + /** + * Returns true iff the argument is a primitive numeric type. + * + * @return whether the argument is a primitive numeric type + */ + public static boolean isNumeric(TypeMirror type) { + switch (type.getKind()) { + case BYTE: + case CHAR: + case DOUBLE: + case FLOAT: + case INT: + case LONG: + case SHORT: + return true; + default: + return false; + } + } + + /** + * Returns true iff the argument is an integral type. + * + * @return whether the argument is an integral type + */ + public static boolean isIntegral(TypeMirror type) { + switch (type.getKind()) { + case BYTE: + case CHAR: + case INT: + case LONG: + case SHORT: + return true; + default: + return false; + } + } + + /** + * Returns true iff the argument is a floating point type. + * + * @return whether the argument is a floating point type + */ + public static boolean isFloating(TypeMirror type) { + switch (type.getKind()) { + case DOUBLE: + case FLOAT: + return true; + default: + return false; + } + } + + /** + * Returns the widened numeric type for an arithmetic operation + * performed on a value of the left type and the right type. + * Defined in JLS 5.6.2. We return a {@link TypeKind} because + * creating a {@link TypeMirror} requires a {@link Types} object + * from the {@link javax.annotation.processing.ProcessingEnvironment}. + * + * @return the result of widening numeric conversion, or NONE when + * the conversion cannot be performed + */ + public static TypeKind widenedNumericType(TypeMirror left, TypeMirror right) { + if (!isNumeric(left) || !isNumeric(right)) { + return TypeKind.NONE; + } + + TypeKind leftKind = left.getKind(); + TypeKind rightKind = right.getKind(); + + if (leftKind == TypeKind.DOUBLE || rightKind == TypeKind.DOUBLE) { + return TypeKind.DOUBLE; + } + + if (leftKind == TypeKind.FLOAT || rightKind == TypeKind.FLOAT) { + return TypeKind.FLOAT; + } + + if (leftKind == TypeKind.LONG || rightKind == TypeKind.LONG) { + return TypeKind.LONG; + } + + return TypeKind.INT; + } + + /** + * If the argument is a bounded TypeVariable or WildcardType, + * return its non-variable, non-wildcard upper bound. Otherwise, + * return the type itself. + * + * @param type a type + * @return the non-variable, non-wildcard upper bound of a type, + * if it has one, or itself if it has no bounds + */ + public static TypeMirror upperBound(TypeMirror type) { + do { + if (type instanceof TypeVariable) { + TypeVariable tvar = (TypeVariable) type; + if (tvar.getUpperBound() != null) { + type = tvar.getUpperBound(); + } else { + break; + } + } else if (type instanceof WildcardType) { + WildcardType wc = (WildcardType) type; + if (wc.getExtendsBound() != null) { + type = wc.getExtendsBound(); + } else { + break; + } + } else { + break; + } + } while (true); + return type; + } + + /** + * Get the type parameter for this wildcard from the underlying type's bound field + * This field is sometimes null, in that case this method will return null + * @return the TypeParameterElement the wildcard is an argument to + */ + public static TypeParameterElement wildcardToTypeParam(final Type.WildcardType wildcard) { + + final Element typeParamElement; + if (wildcard.bound != null) { + typeParamElement = wildcard.bound.asElement(); + } else { + typeParamElement = null; + } + + return (TypeParameterElement) typeParamElement; + } + + /** + * Version of com.sun.tools.javac.code.Types.wildUpperBound(Type) + * that works with both jdk8 (called upperBound there) and jdk8u. + */ + // TODO: contrast to upperBound. + public static Type wildUpperBound(ProcessingEnvironment env, TypeMirror tm) { + Type t = (Type) tm; + if (t.hasTag(TypeTag.WILDCARD)) { + Context context = ((JavacProcessingEnvironment) env).getContext(); + Type.WildcardType w = (Type.WildcardType) TypeAnnotationUtils.unannotatedType(t); + if (w.isSuperBound()) { + Symtab syms = Symtab.instance(context); + return w.bound == null ? syms.objectType : w.bound.bound; + } else { + return wildUpperBound(env, w.type); + } + } else { + return TypeAnnotationUtils.unannotatedType(t); + } + } + + /** + * Version of com.sun.tools.javac.code.Types.wildLowerBound(Type) + * that works with both jdk8 (called upperBound there) and jdk8u. + */ + public static Type wildLowerBound(ProcessingEnvironment env, TypeMirror tm) { + Type t = (Type) tm; + if (t.hasTag(WILDCARD)) { + Context context = ((JavacProcessingEnvironment) env).getContext(); + Symtab syms = Symtab.instance(context); + Type.WildcardType w = (Type.WildcardType) TypeAnnotationUtils.unannotatedType(t); + return w.isExtendsBound() ? syms.botType : wildLowerBound(env, w.type); + } else { + return t.unannotatedType(); + } + } + /** + * Returns the {@link TypeMirror} for a given {@link Class}. + */ + public static TypeMirror typeFromClass(Types types, Elements elements, Class<?> clazz) { + if (clazz == void.class) { + return types.getNoType(TypeKind.VOID); + } else if (clazz.isPrimitive()) { + String primitiveName = clazz.getName().toUpperCase(); + TypeKind primitiveKind = TypeKind.valueOf(primitiveName); + return types.getPrimitiveType(primitiveKind); + } else if (clazz.isArray()) { + TypeMirror componentType = typeFromClass(types, elements, clazz.getComponentType()); + return types.getArrayType(componentType); + } else { + TypeElement element = elements.getTypeElement(clazz.getCanonicalName()); + if (element == null) { + ErrorReporter.errorAbort("Unrecognized class: " + clazz); + return null; // dead code + } + return element.asType(); + } + } + + /** + * Returns an {@link ArrayType} with elements of type {@code componentType}. + */ + public static ArrayType createArrayType(Types types, TypeMirror componentType) { + JavacTypes t = (JavacTypes) types; + return t.getArrayType(componentType); + } + + /** + * Returns true if declaredType is a Class that is used to box primitive type + * (e.g. declaredType=java.lang.Double and primitiveType=22.5d ) + */ + public static boolean isBoxOf(TypeMirror declaredType, TypeMirror primitiveType) { + if (declaredType.getKind() != TypeKind.DECLARED) { + return false; + } + + final String qualifiedName = getQualifiedName((DeclaredType) declaredType).toString(); + switch (primitiveType.getKind()) { + case BOOLEAN: return qualifiedName.equals("java.lang.Boolean"); + case BYTE: return qualifiedName.equals("java.lang.Byte"); + case CHAR: return qualifiedName.equals("java.lang.Character"); + case DOUBLE: return qualifiedName.equals("java.lang.Double"); + case FLOAT: return qualifiedName.equals("java.lang.Float"); + case INT: return qualifiedName.equals("java.lang.Integer"); + case LONG: return qualifiedName.equals("java.lang.Long"); + case SHORT: return qualifiedName.equals("java.lang.Short"); + + default: + return false; + } + } + + /** + * Given a bounded type (wildcard or typevar) get the concrete type of its upper bound. If + * the bounded type extends other bounded types, this method will iterate through their bounds + * until a class, interface, or intersection is found. + * @return a type that is not a wildcard or typevar, or null if this type is an unbounded wildcard + */ + public static TypeMirror findConcreteUpperBound(final TypeMirror boundedType) { + TypeMirror effectiveUpper = boundedType; + outerLoop : while (true) { + switch (effectiveUpper.getKind()) { + case WILDCARD: + effectiveUpper = ((javax.lang.model.type.WildcardType) effectiveUpper).getExtendsBound(); + if (effectiveUpper == null) { + return null; + } + break; + + case TYPEVAR: + effectiveUpper = ((TypeVariable) effectiveUpper).getUpperBound(); + break; + + default: + break outerLoop; + } + } + return effectiveUpper; + } + + /** + * Returns true if the erased type of subtype is a subtype of the erased type of supertype. + * + * @param types Types + * @param subtype possible subtype + * @param supertype possible supertype + * @return true if the erased type of subtype is a subtype of the erased type of supertype + */ + public static boolean isErasedSubtype(Types types, TypeMirror subtype, TypeMirror supertype) { + return types.isSubtype(types.erasure(subtype), types.erasure(supertype)); + } +} diff --git a/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/dist/ManualTaglet.java b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/dist/ManualTaglet.java new file mode 100644 index 0000000000..e7df75ff54 --- /dev/null +++ b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/dist/ManualTaglet.java @@ -0,0 +1,129 @@ +package org.checkerframework.javacutil.dist; + +import java.util.Map; + +import com.sun.javadoc.Tag; +import com.sun.tools.doclets.Taglet; + +/** + * A taglet for processing the {@code @checker_framework.manual} javadoc block tag, which inserts + * references to the Checker Framework manual into javadoc. + * + * <p> + * + * The {@code @checker_framework.manual} tag is used as follows: + * + * <ul> + * <li>{@code @checker_framework.manual #} expands to a top-level link to the Checker Framework manual + * <li>{@code @checker_framework.manual #anchor text} expands to a link with some text to a + * particular part of the manual + * </ul> + */ +public class ManualTaglet implements Taglet { + + @Override + public String getName() { + return "checker_framework.manual"; + } + + @Override + public boolean inConstructor() { + return true; + } + + @Override + public boolean inField() { + return true; + } + + @Override + public boolean inMethod() { + return true; + } + + @Override + public boolean inOverview() { + return true; + } + + @Override + public boolean inPackage() { + return true; + } + + @Override + public boolean inType() { + return true; + } + + @Override + public boolean isInlineTag() { + return false; + } + + /** + * Formats a link, given an array of tokens. + * + * @param parts the array of tokens + * @return a link to the manual top-level if the array size is one, or a + * link to a part of the manual if it's larger than one + */ + private String formatLink(String[] parts) { + String anchor, text; + if (parts.length < 2) { + anchor = ""; + text = "Checker Framework"; + } else { + anchor = parts[0]; + text = parts[1]; + } + return String.format( + "<A HREF=\"http://types.cs.washington.edu/checker-framework/current/checker-framework-manual.html%s\">%s</A>", + anchor, text); + } + + /** + * Formats the {@code @checker_framework.manual} tag, prepending the tag header to the + * tag content. + * + * @param text the tag content + * @return the formatted tag + */ + private String formatHeader(String text) { + return String.format( + "<DT><B>See the Checker Framework Manual:</B><DD>%s<BR>", + text); + } + + @Override + public String toString(Tag tag) { + String[] split = tag.text().split(" ", 2); + return formatHeader(formatLink(split)); + } + + @Override + public String toString(Tag[] tags) { + if (tags.length == 0) { + return ""; + } + StringBuilder sb = new StringBuilder(); + for (Tag t : tags) { + String[] split = t.text().split(" ", 2); + if (t != tags[0]) { + sb.append(", "); + } + sb.append(formatLink(split)); + } + return formatHeader(sb.toString()); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static void register(Map tagletMap) { + ManualTaglet tag = new ManualTaglet(); + Taglet t = (Taglet) tagletMap.get(tag.getName()); + if (t != null) { + tagletMap.remove(tag.getName()); + } + tagletMap.put(tag.getName(), tag); + } +} diff --git a/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/trees/DetachedVarSymbol.java b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/trees/DetachedVarSymbol.java new file mode 100644 index 0000000000..0edf3944e3 --- /dev/null +++ b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/trees/DetachedVarSymbol.java @@ -0,0 +1,45 @@ +package org.checkerframework.javacutil.trees; + +import com.sun.source.tree.VariableTree; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.util.Name; + +/*>>> +import org.checkerframework.checker.nullness.qual.Nullable; +*/ + +/** + * A DetachedVarSymbol represents a variable that is not part of any + * AST Tree. DetachedVarSymbols are created when desugaring source + * code constructs and they carry important type information, but some + * methods such as TreeInfo.declarationFor do not work on them. + */ + +public class DetachedVarSymbol extends Symbol.VarSymbol { + + protected /*@Nullable*/ VariableTree decl; + + /** + * Construct a detached variable symbol, given its flags, name, + * type and owner. + */ + public DetachedVarSymbol(long flags, Name name, Type type, Symbol owner) { + super(flags, name, type, owner); + this.decl = null; + } + + /** + * Set the declaration tree for the variable. + */ + public void setDeclaration(VariableTree decl) { + this.decl = decl; + } + + /** + * Get the declaration tree for the variable. + */ + public /*@Nullable*/ VariableTree getDeclaration() { + return decl; + } +} diff --git a/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/trees/TreeBuilder.java b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/trees/TreeBuilder.java new file mode 100644 index 0000000000..2623f2f2e7 --- /dev/null +++ b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/trees/TreeBuilder.java @@ -0,0 +1,687 @@ +package org.checkerframework.javacutil.trees; + +import java.util.List; + +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.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; + +import org.checkerframework.javacutil.InternalUtils; +import org.checkerframework.javacutil.TypesUtils; + +import com.sun.source.tree.ArrayAccessTree; +import com.sun.source.tree.AssignmentTree; +import com.sun.source.tree.BinaryTree; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.LiteralTree; +import com.sun.source.tree.MemberSelectTree; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.StatementTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.TypeCastTree; +import com.sun.source.tree.VariableTree; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Symtab; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.processing.JavacProcessingEnvironment; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.TreeInfo; +import com.sun.tools.javac.tree.TreeMaker; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.Names; + +/** + * The TreeBuilder permits the creation of new AST Trees using the + * non-public Java compiler API TreeMaker. + */ + +public class TreeBuilder { + protected final Elements elements; + protected final Types modelTypes; + protected final com.sun.tools.javac.code.Types javacTypes; + protected final TreeMaker maker; + protected final Names names; + protected final Symtab symtab; + protected final ProcessingEnvironment env; + + public TreeBuilder(ProcessingEnvironment env) { + this.env = env; + Context context = ((JavacProcessingEnvironment)env).getContext(); + elements = env.getElementUtils(); + modelTypes = env.getTypeUtils(); + javacTypes = com.sun.tools.javac.code.Types.instance(context); + maker = TreeMaker.instance(context); + names = Names.instance(context); + symtab = Symtab.instance(context); + } + + /** + * Builds an AST Tree to access the iterator() method of some iterable + * expression. + * + * @param iterableExpr an expression whose type is a subtype of Iterable + * @return a MemberSelectTree that accesses the iterator() method of + * the expression + */ + public MemberSelectTree buildIteratorMethodAccess(ExpressionTree iterableExpr) { + DeclaredType exprType = + (DeclaredType)TypesUtils.upperBound(InternalUtils.typeOf(iterableExpr)); + assert exprType != null : "expression must be of declared type Iterable<>"; + + TypeElement exprElement = (TypeElement)exprType.asElement(); + + // Find the iterator() method of the iterable type + Symbol.MethodSymbol iteratorMethod = null; + + for (ExecutableElement method : + ElementFilter.methodsIn(elements.getAllMembers(exprElement))) { + Name methodName = method.getSimpleName(); + + if (method.getParameters().size() == 0) { + if (methodName.contentEquals("iterator")) { + iteratorMethod = (Symbol.MethodSymbol)method; + } + } + } + + assert iteratorMethod != null : "no iterator method declared for expression type"; + + Type.MethodType methodType = (Type.MethodType)iteratorMethod.asType(); + Symbol.TypeSymbol methodClass = methodType.asElement(); + DeclaredType iteratorType = (DeclaredType)methodType.getReturnType(); + TypeMirror elementType; + + if (iteratorType.getTypeArguments().size() > 0) { + elementType = iteratorType.getTypeArguments().get(0); + // Remove captured type from a wildcard. + if (elementType instanceof Type.CapturedType) { + elementType = ((Type.CapturedType)elementType).wildcard; + } + + iteratorType = + modelTypes.getDeclaredType((TypeElement)modelTypes.asElement(iteratorType), + elementType); + } + + + // Replace the iterator method's generic return type with + // the actual element type of the expression. + Type.MethodType updatedMethodType = + new Type.MethodType(com.sun.tools.javac.util.List.<Type>nil(), + (Type)iteratorType, + com.sun.tools.javac.util.List.<Type>nil(), + methodClass); + + JCTree.JCFieldAccess iteratorAccess = + (JCTree.JCFieldAccess) + maker.Select((JCTree.JCExpression)iterableExpr, + iteratorMethod); + iteratorAccess.setType(updatedMethodType); + + return iteratorAccess; + } + + /** + * Builds an AST Tree to access the hasNext() method of an iterator. + * + * @param iteratorExpr an expression whose type is a subtype of Iterator + * @return a MemberSelectTree that accesses the hasNext() method of + * the expression + */ + public MemberSelectTree buildHasNextMethodAccess(ExpressionTree iteratorExpr) { + DeclaredType exprType = (DeclaredType)InternalUtils.typeOf(iteratorExpr); + assert exprType != null : "expression must be of declared type Iterator<>"; + + TypeElement exprElement = (TypeElement)exprType.asElement(); + + // Find the hasNext() method of the iterator type + Symbol.MethodSymbol hasNextMethod = null; + + for (ExecutableElement method : + ElementFilter.methodsIn(elements.getAllMembers(exprElement))) { + Name methodName = method.getSimpleName(); + + if (method.getParameters().size() == 0) { + if (methodName.contentEquals("hasNext")) { + hasNextMethod = (Symbol.MethodSymbol)method; + } + } + } + + assert hasNextMethod != null : "no hasNext method declared for expression type"; + + JCTree.JCFieldAccess hasNextAccess = + (JCTree.JCFieldAccess) + maker.Select((JCTree.JCExpression)iteratorExpr, + hasNextMethod); + hasNextAccess.setType(hasNextMethod.asType()); + + return hasNextAccess; + } + + /** + * Builds an AST Tree to access the next() method of an iterator. + * + * @param iteratorExpr an expression whose type is a subtype of Iterator + * @return a MemberSelectTree that accesses the next() method of + * the expression + */ + public MemberSelectTree buildNextMethodAccess(ExpressionTree iteratorExpr) { + DeclaredType exprType = (DeclaredType)InternalUtils.typeOf(iteratorExpr); + assert exprType != null : "expression must be of declared type Iterator<>"; + + TypeElement exprElement = (TypeElement)exprType.asElement(); + + // Find the next() method of the iterator type + Symbol.MethodSymbol nextMethod = null; + + for (ExecutableElement method : + ElementFilter.methodsIn(elements.getAllMembers(exprElement))) { + Name methodName = method.getSimpleName(); + + if (method.getParameters().size() == 0) { + if (methodName.contentEquals("next")) { + nextMethod = (Symbol.MethodSymbol)method; + } + } + } + + assert nextMethod != null : "no next method declared for expression type"; + + Type.MethodType methodType = (Type.MethodType)nextMethod.asType(); + Symbol.TypeSymbol methodClass = methodType.asElement(); + Type elementType; + + if (exprType.getTypeArguments().size() > 0) { + elementType = (Type)exprType.getTypeArguments().get(0); + } else { + elementType = symtab.objectType; + } + + // Replace the next method's generic return type with + // the actual element type of the expression. + Type.MethodType updatedMethodType = + new Type.MethodType(com.sun.tools.javac.util.List.<Type>nil(), + elementType, + com.sun.tools.javac.util.List.<Type>nil(), + methodClass); + + JCTree.JCFieldAccess nextAccess = + (JCTree.JCFieldAccess) + maker.Select((JCTree.JCExpression)iteratorExpr, + nextMethod); + nextAccess.setType(updatedMethodType); + + return nextAccess; + } + + /** + * Builds an AST Tree to dereference the length field of an array + * + * @param expression the array expression whose length is being accessed + * @return a MemberSelectTree to dereference the length of the array + */ + public MemberSelectTree buildArrayLengthAccess(ExpressionTree expression) { + + return (JCTree.JCFieldAccess) + maker.Select((JCTree.JCExpression)expression, symtab.lengthVar); + } + + /** + * Builds an AST Tree to call a method designated by the argument expression. + * + * @param methodExpr an expression denoting a method with no arguments + * @return a MethodInvocationTree to call the argument method + */ + public MethodInvocationTree buildMethodInvocation(ExpressionTree methodExpr) { + return maker.App((JCTree.JCExpression)methodExpr); + } + + /** + * Builds an AST Tree to call a method designated by methodExpr, + * with one argument designated by argExpr. + * + * @param methodExpr an expression denoting a method with one argument + * @param argExpr an expression denoting an argument to the method + * @return a MethodInvocationTree to call the argument method + */ + public MethodInvocationTree buildMethodInvocation(ExpressionTree methodExpr, + ExpressionTree argExpr) { + return maker.App((JCTree.JCExpression)methodExpr, + com.sun.tools.javac.util.List.of((JCTree.JCExpression)argExpr)); + } + + /** + * Builds an AST Tree to declare and initialize a variable, with no modifiers. + * + * @param type the type of the variable + * @param name the name of the variable + * @param owner the element containing the new symbol + * @param initializer the initializer expression + * @return a VariableDeclTree declaring the new variable + */ + public VariableTree buildVariableDecl(TypeMirror type, + String name, + Element owner, + ExpressionTree initializer) { + DetachedVarSymbol sym = + new DetachedVarSymbol(0, names.fromString(name), + (Type)type, (Symbol)owner); + VariableTree tree = maker.VarDef(sym, (JCTree.JCExpression)initializer); + sym.setDeclaration(tree); + return tree; + } + + /** + * Builds an AST Tree to declare and initialize a variable. The + * type of the variable is specified by a Tree. + * + * @param type the type of the variable, as a Tree + * @param name the name of the variable + * @param owner the element containing the new symbol + * @param initializer the initializer expression + * @return a VariableDeclTree declaring the new variable + */ + public VariableTree buildVariableDecl(Tree type, + String name, + Element owner, + ExpressionTree initializer) { + Type typeMirror = (Type)InternalUtils.typeOf(type); + DetachedVarSymbol sym = + new DetachedVarSymbol(0, names.fromString(name), + typeMirror, (Symbol)owner); + JCTree.JCModifiers mods = maker.Modifiers(0); + JCTree.JCVariableDecl decl = maker.VarDef(mods, sym.name, + (JCTree.JCExpression)type, + (JCTree.JCExpression)initializer); + decl.setType(typeMirror); + decl.sym = sym; + sym.setDeclaration(decl); + return decl; + } + + /** + * Builds an AST Tree to refer to a variable. + * + * @param decl the declaration of the variable + * @return an IdentifierTree to refer to the variable + */ + public IdentifierTree buildVariableUse(VariableTree decl) { + return (IdentifierTree)maker.Ident((JCTree.JCVariableDecl)decl); + } + + /** + * Builds an AST Tree to cast the type of an expression. + * + * @param type the type to cast to + * @param expr the expression to be cast + * @return a cast of the expression to the type + */ + public TypeCastTree buildTypeCast(TypeMirror type, + ExpressionTree expr) { + return maker.TypeCast((Type)type, (JCTree.JCExpression)expr); + } + + /** + * Builds an AST Tree to assign an expression to a variable. + * + * @param variable the declaration of the variable to assign to + * @param expr the expression to be assigned + * @return a statement assigning the expression to the variable + */ + public StatementTree buildAssignment(VariableTree variable, + ExpressionTree expr) { + return maker.Assignment(TreeInfo.symbolFor((JCTree)variable), + (JCTree.JCExpression)expr); + } + + /** + * Builds an AST Tree to assign an RHS expression to an LHS expression. + * + * @param lhs the expression to be assigned to + * @param rhs the expression to be assigned + * @return a statement assigning the expression to the variable + */ + public AssignmentTree buildAssignment(ExpressionTree lhs, + ExpressionTree rhs) { + JCTree.JCAssign assign = + maker.Assign((JCTree.JCExpression)lhs, (JCTree.JCExpression)rhs); + assign.setType((Type)InternalUtils.typeOf(lhs)); + return assign; + } + + /** + * Builds an AST Tree representing a literal value of primitive + * or String type. + */ + public LiteralTree buildLiteral(Object value) { + return maker.Literal(value); + } + + /** + * Builds an AST Tree to compare two operands with less than. + * + * @param left the left operand tree + * @param right the right operand tree + * @return a Tree representing "left < right" + */ + public BinaryTree buildLessThan(ExpressionTree left, ExpressionTree right) { + JCTree.JCBinary binary = + maker.Binary(JCTree.Tag.LT, (JCTree.JCExpression)left, + (JCTree.JCExpression)right); + binary.setType((Type)modelTypes.getPrimitiveType(TypeKind.BOOLEAN)); + return binary; + } + + /** + * Builds an AST Tree to dereference an array. + * + * @param array the array to dereference + * @param index the index at which to dereference + * @return a Tree representing the dereference + */ + public ArrayAccessTree buildArrayAccess(ExpressionTree array, + ExpressionTree index) { + ArrayType arrayType = (ArrayType)InternalUtils.typeOf(array); + JCTree.JCArrayAccess access = + maker.Indexed((JCTree.JCExpression)array, (JCTree.JCExpression)index); + access.setType((Type)arrayType.getComponentType()); + return access; + } + + /** + * Builds an AST Tree to refer to a class name. + * + * @param elt an element representing the class + * @return an IdentifierTree referring to the class + */ + public IdentifierTree buildClassUse(Element elt) { + return maker.Ident((Symbol)elt); + } + + /** + * Builds an AST Tree to access the valueOf() method of boxed type + * such as Short or Float. + * + * @param expr an expression whose type is a boxed type + * @return a MemberSelectTree that accesses the valueOf() method of + * the expression + */ + public MemberSelectTree buildValueOfMethodAccess(Tree expr) { + TypeMirror boxedType = InternalUtils.typeOf(expr); + + assert TypesUtils.isBoxedPrimitive(boxedType); + + // Find the valueOf(unboxedType) method of the boxed type + Symbol.MethodSymbol valueOfMethod = getValueOfMethod(env, boxedType); + + Type.MethodType methodType = (Type.MethodType)valueOfMethod.asType(); + + JCTree.JCFieldAccess valueOfAccess = + (JCTree.JCFieldAccess) + maker.Select((JCTree.JCExpression)expr, valueOfMethod); + valueOfAccess.setType(methodType); + + return valueOfAccess; + } + + /** + * Returns the valueOf method of a boxed type such as Short or Float. + */ + public static Symbol.MethodSymbol getValueOfMethod(ProcessingEnvironment env, TypeMirror boxedType) { + Symbol.MethodSymbol valueOfMethod = null; + + TypeMirror unboxedType = env.getTypeUtils().unboxedType(boxedType); + TypeElement boxedElement = (TypeElement)((DeclaredType)boxedType).asElement(); + for (ExecutableElement method : + ElementFilter.methodsIn(env.getElementUtils().getAllMembers(boxedElement))) { + Name methodName = method.getSimpleName(); + + if (methodName.contentEquals("valueOf")) { + List<? extends VariableElement> params = method.getParameters(); + if (params.size() == 1 && env.getTypeUtils().isSameType(params.get(0).asType(), unboxedType)) { + valueOfMethod = (Symbol.MethodSymbol)method; + } + } + } + + assert valueOfMethod != null : "no valueOf method declared for boxed type"; + return valueOfMethod; + } + + /** + * Builds an AST Tree to access the *Value() method of a + * boxed type such as Short or Float, where * is the corresponding + * primitive type (i.e. shortValue or floatValue). + * + * @param expr an expression whose type is a boxed type + * @return a MemberSelectTree that accesses the *Value() method of + * the expression + */ + public MemberSelectTree buildPrimValueMethodAccess(Tree expr) { + TypeMirror boxedType = InternalUtils.typeOf(expr); + TypeElement boxedElement = (TypeElement)((DeclaredType)boxedType).asElement(); + + assert TypesUtils.isBoxedPrimitive(boxedType); + TypeMirror unboxedType = modelTypes.unboxedType(boxedType); + + // Find the *Value() method of the boxed type + String primValueName = unboxedType.toString() + "Value"; + Symbol.MethodSymbol primValueMethod = null; + + for (ExecutableElement method : + ElementFilter.methodsIn(elements.getAllMembers(boxedElement))) { + Name methodName = method.getSimpleName(); + + if (methodName.contentEquals(primValueName) && + method.getParameters().size() == 0) { + primValueMethod = (Symbol.MethodSymbol)method; + } + } + + assert primValueMethod != null : "no *Value method declared for boxed type"; + + Type.MethodType methodType = (Type.MethodType)primValueMethod.asType(); + + JCTree.JCFieldAccess primValueAccess = + (JCTree.JCFieldAccess) + maker.Select((JCTree.JCExpression)expr, primValueMethod); + primValueAccess.setType(methodType); + + return primValueAccess; + } + + /** + * Map public AST Tree.Kinds to internal javac JCTree.Tags. + */ + public JCTree.Tag kindToTag(Tree.Kind kind) { + switch (kind) { + case AND: + return JCTree.Tag.BITAND; + case AND_ASSIGNMENT: + return JCTree.Tag.BITAND_ASG; + case ANNOTATION: + return JCTree.Tag.ANNOTATION; + case ANNOTATION_TYPE: + return JCTree.Tag.TYPE_ANNOTATION; + case ARRAY_ACCESS: + return JCTree.Tag.INDEXED; + case ARRAY_TYPE: + return JCTree.Tag.TYPEARRAY; + case ASSERT: + return JCTree.Tag.ASSERT; + case ASSIGNMENT: + return JCTree.Tag.ASSIGN; + case BITWISE_COMPLEMENT: + return JCTree.Tag.COMPL; + case BLOCK: + return JCTree.Tag.BLOCK; + case BREAK: + return JCTree.Tag.BREAK; + case CASE: + return JCTree.Tag.CASE; + case CATCH: + return JCTree.Tag.CATCH; + case CLASS: + return JCTree.Tag.CLASSDEF; + case CONDITIONAL_AND: + return JCTree.Tag.AND; + case CONDITIONAL_EXPRESSION: + return JCTree.Tag.CONDEXPR; + case CONDITIONAL_OR: + return JCTree.Tag.OR; + case CONTINUE: + return JCTree.Tag.CONTINUE; + case DIVIDE: + return JCTree.Tag.DIV; + case DIVIDE_ASSIGNMENT: + return JCTree.Tag.DIV_ASG; + case DO_WHILE_LOOP: + return JCTree.Tag.DOLOOP; + case ENHANCED_FOR_LOOP: + return JCTree.Tag.FOREACHLOOP; + case EQUAL_TO: + return JCTree.Tag.EQ; + case EXPRESSION_STATEMENT: + return JCTree.Tag.EXEC; + case FOR_LOOP: + return JCTree.Tag.FORLOOP; + case GREATER_THAN: + return JCTree.Tag.GT; + case GREATER_THAN_EQUAL: + return JCTree.Tag.GE; + case IDENTIFIER: + return JCTree.Tag.IDENT; + case IF: + return JCTree.Tag.IF; + case IMPORT: + return JCTree.Tag.IMPORT; + case INSTANCE_OF: + return JCTree.Tag.TYPETEST; + case LABELED_STATEMENT: + return JCTree.Tag.LABELLED; + case LEFT_SHIFT: + return JCTree.Tag.SL; + case LEFT_SHIFT_ASSIGNMENT: + return JCTree.Tag.SL_ASG; + case LESS_THAN: + return JCTree.Tag.LT; + case LESS_THAN_EQUAL: + return JCTree.Tag.LE; + case LOGICAL_COMPLEMENT: + return JCTree.Tag.NOT; + case MEMBER_SELECT: + return JCTree.Tag.SELECT; + case METHOD: + return JCTree.Tag.METHODDEF; + case METHOD_INVOCATION: + return JCTree.Tag.APPLY; + case MINUS: + return JCTree.Tag.MINUS; + case MINUS_ASSIGNMENT: + return JCTree.Tag.MINUS_ASG; + case MODIFIERS: + return JCTree.Tag.MODIFIERS; + case MULTIPLY: + return JCTree.Tag.MUL; + case MULTIPLY_ASSIGNMENT: + return JCTree.Tag.MUL_ASG; + case NEW_ARRAY: + return JCTree.Tag.NEWARRAY; + case NEW_CLASS: + return JCTree.Tag.NEWCLASS; + case NOT_EQUAL_TO: + return JCTree.Tag.NE; + case OR: + return JCTree.Tag.BITOR; + case OR_ASSIGNMENT: + return JCTree.Tag.BITOR_ASG; + case PARENTHESIZED: + return JCTree.Tag.PARENS; + case PLUS: + return JCTree.Tag.PLUS; + case PLUS_ASSIGNMENT: + return JCTree.Tag.PLUS_ASG; + case POSTFIX_DECREMENT: + return JCTree.Tag.POSTDEC; + case POSTFIX_INCREMENT: + return JCTree.Tag.POSTINC; + case PREFIX_DECREMENT: + return JCTree.Tag.PREDEC; + case PREFIX_INCREMENT: + return JCTree.Tag.PREINC; + case REMAINDER: + return JCTree.Tag.MOD; + case REMAINDER_ASSIGNMENT: + return JCTree.Tag.MOD_ASG; + case RETURN: + return JCTree.Tag.RETURN; + case RIGHT_SHIFT: + return JCTree.Tag.SR; + case RIGHT_SHIFT_ASSIGNMENT: + return JCTree.Tag.SR_ASG; + case SWITCH: + return JCTree.Tag.SWITCH; + case SYNCHRONIZED: + return JCTree.Tag.SYNCHRONIZED; + case THROW: + return JCTree.Tag.THROW; + case TRY: + return JCTree.Tag.TRY; + case TYPE_CAST: + return JCTree.Tag.TYPECAST; + case TYPE_PARAMETER: + return JCTree.Tag.TYPEPARAMETER; + case UNARY_MINUS: + return JCTree.Tag.NEG; + case UNARY_PLUS: + return JCTree.Tag.POS; + case UNION_TYPE: + return JCTree.Tag.TYPEUNION; + case UNSIGNED_RIGHT_SHIFT: + return JCTree.Tag.USR; + case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: + return JCTree.Tag.USR_ASG; + case VARIABLE: + return JCTree.Tag.VARDEF; + case WHILE_LOOP: + return JCTree.Tag.WHILELOOP; + case XOR: + return JCTree.Tag.BITXOR; + case XOR_ASSIGNMENT: + return JCTree.Tag.BITXOR_ASG; + default: + return JCTree.Tag.NO_TAG; + } + } + + /** + * Builds an AST Tree to perform a binary operation. + * + * @param type result type of the operation + * @param op AST Tree operator + * @param left the left operand tree + * @param right the right operand tree + * @return a Tree representing "left < right" + */ + public BinaryTree buildBinary(TypeMirror type, Tree.Kind op, ExpressionTree left, ExpressionTree right) { + JCTree.Tag jcOp = kindToTag(op); + JCTree.JCBinary binary = + maker.Binary(jcOp, (JCTree.JCExpression)left, + (JCTree.JCExpression)right); + binary.setType((Type)type); + return binary; + } + +} diff --git a/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/trees/TreeParser.java b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/trees/TreeParser.java new file mode 100644 index 0000000000..1ac9d09309 --- /dev/null +++ b/third_party/checker_framework_javacutil/java/org/checkerframework/javacutil/trees/TreeParser.java @@ -0,0 +1,144 @@ +package org.checkerframework.javacutil.trees; + +import java.util.StringTokenizer; + +import javax.annotation.processing.ProcessingEnvironment; + +import com.sun.source.tree.ExpressionTree; +import com.sun.tools.javac.processing.JavacProcessingEnvironment; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.TreeMaker; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Names; + +/** + * A Utility class for parsing Java expression snippets, and converting them + * to proper Javac AST nodes. + * + * This is useful for parsing {@code EnsuresNonNull*}, + * and {@code KeyFor} values. + * + * Currently, it handles four tree types only: + * <ul> + * <li>Identifier tree (e.g. {@code id})</li> + * <li>Literal tree (e.g. 2, 3)</li> + * <li>Method invocation tree (e.g. {@code method(2, 3)})</li> + * <li>Member select tree (e.g. {@code Class.field}, {@code instance.method()}) + * <li>Array access tree (e.g. {@code array[id]})</li> + * </ul> + * + * Notable limitation: Doesn't handle spaces, or non-method-argument + * parenthesis. + * + * It's implemented via a Recursive-Descend parser. + */ +public class TreeParser { + private static final String DELIMS = ".[](),"; + private static final String SENTINAL = ""; + + private final TreeMaker maker; + private final Names names; + + public TreeParser(ProcessingEnvironment env) { + Context context = ((JavacProcessingEnvironment)env).getContext(); + maker = TreeMaker.instance(context); + names = Names.instance(context); + } + + /** + * Parses the snippet in the string as an internal Javac AST expression + * node + * + * @param s the java snippet + * @return the AST corresponding to the snippet + */ + public ExpressionTree parseTree(String s) { + tokenizer = new StringTokenizer(s, DELIMS, true); + token = tokenizer.nextToken(); + + try { + return parseExpression(); + } catch (Exception e) { + throw new ParseError(e); + } finally { + tokenizer = null; + token = null; + } + } + + StringTokenizer tokenizer = null; + String token = null; + + private String nextToken() { + token = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : SENTINAL; + return token; + } + + JCExpression fromToken(String token) { + // Optimization + if ("true".equals(token)) { + return maker.Literal(true); + } else if ("false".equals(token)) { + return maker.Literal(false); + } + + if (Character.isLetter(token.charAt(0))) { + return maker.Ident(names.fromString(token)); + } + + Object value = null; + try { + value = Integer.valueOf(token); + } catch (Exception e2) { try { + value = Double.valueOf(token); + } catch (Exception ef) {}} + assert value != null; + return maker.Literal(value); + } + + JCExpression parseExpression() { + JCExpression tree = fromToken(token); + + while (tokenizer.hasMoreTokens()) { + String delim = nextToken(); + if (".".equals(delim)) { + nextToken(); + tree = maker.Select(tree, + names.fromString(token)); + } else if ("(".equals(delim)) { + nextToken(); + ListBuffer<JCExpression> args = new ListBuffer<>(); + while (!")".equals(token)) { + JCExpression arg = parseExpression(); + args.append(arg); + if (",".equals(token)) { + nextToken(); + } + } + // For now, handle empty args only + assert ")".equals(token); + tree = maker.Apply(List.<JCExpression>nil(), + tree, args.toList()); + } else if ("[".equals(token)) { + nextToken(); + JCExpression index = parseExpression(); + assert "]".equals(token); + tree = maker.Indexed(tree, index); + } else { + return tree; + } + } + + return tree; + } + + class ParseError extends RuntimeException { + private static final long serialVersionUID = 1887754619522101929L; + + ParseError(Throwable cause) { + super(cause); + } + } +} |