diff --git a/settings.gradle b/settings.gradle index afd285d56f972e27d7f51a7a66a8b502dbe4df75..aba0955c8a958936beded53e73cf0147d398ee27 100644 --- a/settings.gradle +++ b/settings.gradle @@ -11,6 +11,7 @@ rootProject.name = 'sle19-impl' include 'statemachine' include 'dg' +include 'simplecfg' include 'extendj' include 'extendj:java4' include 'extendj:java5' diff --git a/simplecfg/.gitignore b/simplecfg/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e6c14cd07d2acf3256143cb39267fee74d2265c3 --- /dev/null +++ b/simplecfg/.gitignore @@ -0,0 +1,20 @@ +# gradle build +/build/ +/.gradle/ + +# generated files +/*.jar +/src/gen/ + +# generated graphs +/testdata/*.png + +# vim +*.swp + +# eclipse +/.project +/.classpath +/.settings/ +/bin/ + diff --git a/simplecfg/CONTRIBUTING.md b/simplecfg/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..1ba853922fb804fd5265e877f3e8a4e1d6615448 --- /dev/null +++ b/simplecfg/CONTRIBUTING.md @@ -0,0 +1,24 @@ +Want to contribute? Great! First, read this page (including the small print at the end). + +### Before you contribute +Before we can use your code, you must sign the +[Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual?csw=1) +(CLA), which you can do online. The CLA is necessary mainly because you own the +copyright to your changes, even after your contribution becomes part of our +codebase, so we need your permission to use and distribute your code. We also +need to be sure of various other things—for instance that you'll tell us if you +know that your code infringes on other people's patents. You don't have to sign +the CLA until after you've submitted your code for review and a member has +approved it, but you must do it before we can put your code into our codebase. +Before you start working on a larger contribution, you should get in touch with +us first through the issue tracker with your idea so that we can help out and +possibly guide you. Coordinating up front makes it much easier to avoid +frustration later on. + +### Code reviews +All submissions, including submissions by project members, require review. We +use Github pull requests for this purpose. + +### The small print +Contributions made by corporations are covered by a different agreement than +the one above, the Software Grant and Corporate Contributor License Agreement. diff --git a/simplecfg/LICENSE b/simplecfg/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..d645695673349e3947e8e5ae42332d0ac3164cd7 --- /dev/null +++ b/simplecfg/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/simplecfg/README.md b/simplecfg/README.md new file mode 100644 index 0000000000000000000000000000000000000000..bb0a66bcccf5b897fe276bbe28e7cb99814c06cd --- /dev/null +++ b/simplecfg/README.md @@ -0,0 +1,70 @@ +Simple CFG module for ExtendJ +============================= + +This is a Control Flow Graph (CFG) module for the ExtendJ compiler for building +simplified CFGs. This module builds CFGs for Java methods where only branches +and method calls have are included. These simple CFGs provide enough +information to perform intraprocedural static analyses on Java code. + +This repository also includes two sample Java static analyzers based on the this +CFG module. One analyzer checks for additional calls to a +java.io.Reader/java.io.Writer after `close()` was called on the same instance. +The other analyzer checks for potential `null` dereferences on paramters +annotated with javax.annotation.Nullable. + +Disclaimer +---------- + +This is not an official Google product (experimental or otherwise), it is just +code that happens to be owned by Google. + +Shipshape Module +---------------- + +The demo analyzers can be plugged into the [Shipshape][1] pipeline. The +Shipshape integration is currently experimental. + +Dependencies +------------ + +To build the Simplified CFG generator you need the following dependencies: + +* Git +* Gradle 2.4 +* ExtendJ + +This repository has a submodule for the ExtendJ compiler. If you did not clone +this repository with the `--recursive` flag you will have to run `git submodule +init` followed by `git submodule update`, this will clone a specific commit from +the ExtendJ repository into the `third_party/extendj/` directory. + +Building +-------- + +Note that you must have the Git submodule `third_party/extendj/git` in +order to build SimpleCFG. To download the submodule, use the following commands: + + git submodule init + git submodule update + + +Build the Simplified CFG generator Jar file by running the following Gradle command: + + gradle jar + + +Testing +------- + +The tests may be run by issuing the following command: + + gradle test + +Most tests check that a well-formed SimpleCFG is built for each Java file in the +testdata directory. The tests are structured so that they test the successors of +each node in the resulting CFG for the single block/method in each of the Java files. + +You can generate images for the CFGs in each test file by running the `graph.sh` +shell script. + +[1]: https://github.com/google/shipshape diff --git a/simplecfg/build.gradle b/simplecfg/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..45deaad9613999f038146ab8300b717967db3d1b --- /dev/null +++ b/simplecfg/build.gradle @@ -0,0 +1,107 @@ +buildscript { + repositories.mavenLocal() + repositories.mavenCentral() + dependencies { + classpath 'org.jastadd:jastaddgradle:1.13.3' + } +} + +apply plugin: 'java' +apply plugin: 'application' +apply plugin: 'jastadd' +apply plugin: 'idea' + +repositories { + mavenLocal() +} + +idea { + module { + generatedSourceDirs += file('./src/gen/java') + } +} + +//sourceSets { +// main { +// java.srcDirs "src/gen/java" +// java.srcDirs "../extendj/src/frontend" +// } +//} +sourceSets.main { + java { + + srcDirs "src/gen/java" + srcDirs '../extendj/src/frontend' + } + resources { + srcDir '../extendj/src/res' + srcDir jastadd.buildInfoDir + } +} + +dependencies { +// compile group: 'com.google.guava', name: 'guava', version: '18.0' + testCompile group: 'com.google.truth', name: 'truth', version: '0.27' +} + +jastadd { + configureModuleBuild() + modules { + + include("../extendj/jastadd_modules") + + module "simplecfg", { + + imports "java8 frontend" + + jastadd { + basedir "src/main/jastadd/" + include "**/*.ast" + include "**/*.jadd" + include "**/*.jrag" + } + + java { + basedir "src/main/java/" + include "**/*.java" + } + + } + + } + + + // Target module to build: + module = 'simplecfg' + + astPackage = 'org.extendj.ast' + parser.name = 'JavaParser' + scanner.name = 'OriginalScanner' +// + genDir = 'src/gen/java' + + + parser.genDir = 'src/gen/java/org/extendj/parser' + scanner.genDir = 'src/gen/java/org/extendj/scanner' +// +// if (project.hasProperty('extraJastAddOptions')) { +// extraJastAddOptions += project.extraJastAddOptions.split(',') as List +// print("options: ${extraJastAddOptions}") +// } + +} + + +test { + inputs.dir file('testdata') +} + +mainClassName = 'com.google.simplecfg.PrintCfg' +jar.manifest.attributes 'Main-Class': mainClassName +jar.destinationDir = projectDir + +sourceCompatibility = '1.8' +targetCompatibility = '1.8' + +// fix broken scanner dependencies +//scanner.outputs.upToDateWhen {false} diff --git a/simplecfg/gentest.sh b/simplecfg/gentest.sh new file mode 100755 index 0000000000000000000000000000000000000000..388f72d85e009b96014ee3dba6a59f5824445d65 --- /dev/null +++ b/simplecfg/gentest.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# Copyright 2014 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -e + +if [ $# -lt "1" ]; then + echo "Error: no input program specified." + exit 1 +else + gradle jar + for src in "$@"; do + java -cp simplecfg.jar com.google.simplecfg.TestGenerator "$src" + done +fi + +echo "done" diff --git a/simplecfg/gradle/wrapper/gradle-wrapper.jar b/simplecfg/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..3ebf88af3b642f4f202c85c16a3815fce3e9590d Binary files /dev/null and b/simplecfg/gradle/wrapper/gradle-wrapper.jar differ diff --git a/simplecfg/gradle/wrapper/gradle-wrapper.properties b/simplecfg/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000000000000000000000000000000000..019065d1d650ce87992f9d60b7a162f7f2f8caf9 --- /dev/null +++ b/simplecfg/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip diff --git a/simplecfg/graph.sh b/simplecfg/graph.sh new file mode 100755 index 0000000000000000000000000000000000000000..c15fd3c9bbc80e6999f9fa8792cf5d9b885549b9 --- /dev/null +++ b/simplecfg/graph.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +# Copyright 2015 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -e + +gradle jar + +gengraph() +{ + OUT="${1%%\.javax}.png" + echo "$1 -> $OUT" + java -jar simplecfg.jar "$1" | dot -Tpng > "$OUT" +} + +if [ $# -lt "1" ]; then + for f in testdata/*.javax; do + gengraph $f + done +else + gengraph $1 +fi + +echo "done" diff --git a/simplecfg/modules b/simplecfg/modules new file mode 100644 index 0000000000000000000000000000000000000000..52ac3fcb925cde71aa8354d5ce0b51c98404e0b2 --- /dev/null +++ b/simplecfg/modules @@ -0,0 +1,83 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + + +// // Custom version of the Java 8 frontend without JavaScanner (compile error due to moved class). +// module("java8 frontend mod") { +// moduleName "Java SE 8" +// moduleVariant "frontend" +// +// def java8 = "../extendj/java8" +// +// imports "java7 frontend" +// +// jastadd { +// basedir java8 +// include "grammar/*.ast" +// include "frontend/*.jadd" +// include "frontend/*.jrag" +// +// excludeFrom "java7 frontend", "frontend/JavaVersion.jrag" +// +// exclude "frontend/FrontendMain.jrag" +// excludeFrom "java5 frontend", "frontend/BytecodeReader.jrag" +// excludeFrom "java7 frontend", "frontend/Variable.jadd" +// +// } +// +// scanner { +// basedir java8 +// include "scanner/Separators.flex" +// include "scanner/Operators.flex" +// +// excludeFrom "java4 frontend", "scanner/Preamble.flex" +// } +// +// parser { +// basedir java8 +// include "parser/*" +// } +// } + +module("simplecfg") { + + imports "java8 frontend" + + jastadd { + basedir "src/main/jastadd/" + include "**/*.ast" + include "**/*.jadd" + include "**/*.jrag" + } + + java { + basedir "src/main/java/" + include "**/*.java" + } + + scanner { + include "src/main/scanner/Header.flex", [-4] + excludeFrom "java4 frontend", "scanner/Header.flex" + } + + parser { + // Replace parser package declaration. + include "src/main/parser/Header.parser", [-2] + excludeFrom "java4 frontend", "parser/Header.parser" + } +} + diff --git a/simplecfg/src/main/jastadd/AlreadyClosedAnalysis.jrag b/simplecfg/src/main/jastadd/AlreadyClosedAnalysis.jrag new file mode 100644 index 0000000000000000000000000000000000000000..47d49883d9f55261f2966855698bc59195d3a5cd --- /dev/null +++ b/simplecfg/src/main/jastadd/AlreadyClosedAnalysis.jrag @@ -0,0 +1,90 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Adds an analysis checking for calls to an instance of java.io.Writer or java.io.Reader after + * {@code close()} has been called on the same instance. + * + * <p>For each method call to an effectively final variable of type java.io.Closeable, + * we do a breadth-first search on the reverse CFG from the location of that call to see if a + * call to {@code close()} was made on the same instance previously. + */ +aspect AlreadyClosedAnalysis { + + MethodAccess contributes alreadyClosedFinding() + when alreadyClosedStream() + to CompilationUnit.findings() + for compilationUnit(); + + /** Allow MethodAccess to lookup the enclosing compilation unit. */ + inh CompilationUnit MethodAccess.compilationUnit(); + + /** Generate a finding for method call after close() call. */ + syn lazy ExtendJFinding MethodAccess.alreadyClosedFinding() = + finding("AlreadyClosed", String.format( + "close() may have already been called on %s at this point", + prevExpr().prettyPrint())); + + /** Check if the reciever of this method access was already closed. */ + syn boolean MethodAccess.alreadyClosedStream() { + // The receiver must be an effectively final local variable (or parameter). + if (name().equals("close") // Don't check for repeated close() calls. + || name().equals("toString") || name().equals("toByteArray") + || !hasPrevExpr() // Does not have a variable or parameter receiver. + || prevExpr().varDecl() == null // Receiver is not a variable/parameter. + || !(prevExpr().varDecl().isFinal() + || prevExpr().varDecl().isEffectivelyFinal()) // Receiver might have changed. + || !prevExpr().type().isCloseable()) { // Receiver is not instance of java.io.Closeable. + return false; + } + final Variable receiver = prevExpr().varDecl(); + return null != call().reverseBfs(new CfgVisitor() { + @Override + public SearchAction processEdge(CfgNode pred, CfgNode succ) { + if (succ.isCloseCall(receiver)) { + if (pred.isException()) { + // This call is interrupted by an exception. Don't continue the search from this node. + return SearchAction.SKIP; + } else { + return SearchAction.SUCCESSOR_MATCH; + } + } + if (succ.isCall(receiver)) { + // Already analyzed from this point. + return SearchAction.SKIP; + } + if (succ.isDeclarationOf(receiver)) { + // Skip this node and don't reconsider any other path that encounters the node. + return SearchAction.IGNORE; + } + return SearchAction.CONTINUE; + } + }); + } + + /** Test if the CFG node is a call node with the given variable as receiver. */ + syn boolean CfgNode.isCall(Variable receiver) = false; + eq CfgMethodCall.isCall(Variable receiver) = methodAccess().hasReceiver(receiver); + + /** Test if the CFG node is a call node for receiver.close(). */ + syn boolean CfgNode.isCloseCall(Variable receiver) = false; + eq CfgMethodCall.isCloseCall(Variable receiver) = + methodAccess().hasReceiver(receiver) && methodAccess().getID().equals("close"); + + /** Check if this is a Reader or Writer. */ + syn boolean TypeDecl.isCloseable() = + !isUnknown() && instanceOf(lookupType("java.io", "Closeable")); +} diff --git a/simplecfg/src/main/jastadd/CFG.ast b/simplecfg/src/main/jastadd/CFG.ast new file mode 100644 index 0000000000000000000000000000000000000000..b1e7cbe5349021cc8790f4a6ee2613ee6b1eb168 --- /dev/null +++ b/simplecfg/src/main/jastadd/CFG.ast @@ -0,0 +1,36 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** A node in a Control Flow Graph (CFG). */ +abstract CfgNode; + +/** The CFG entry node. */ +CfgEntry : CfgNode ::= <Succ:CfgNode>; + +/** The CFG exit node. */ +CfgExit : CfgNode; + +/** A method call in the CFG. */ +CfgMethodCall : CfgNode; + +/** A conditional branch in the CFG. */ +CfgBranch : CfgNode; + +/** A branch in the CFG caused by potential thrown exceptions. */ +CfgException : CfgNode; + +/** A marker node used to mark try block entry points or the end of if-statement branches. */ +CfgMarker : CfgNode; diff --git a/simplecfg/src/main/jastadd/CfgSearch.jrag b/simplecfg/src/main/jastadd/CfgSearch.jrag new file mode 100644 index 0000000000000000000000000000000000000000..e950ffe2f24654847136b0fd7f5b8f8c90a804cd --- /dev/null +++ b/simplecfg/src/main/jastadd/CfgSearch.jrag @@ -0,0 +1,149 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This aspect adds an API for searching for nodes matching some property in a Control Flow Graph. + */ +aspect CfgSearch { + enum SearchAction { + /** + * The search is complete because the currently processed edge successor matches the search. + */ + SUCCESSOR_MATCH, + /** + * The search is complete because the currently processed edge predecessor matches the search. + */ + PREDECESSOR_MATCH, + /** + * The search should skip adding the successors from the successor of the current edge. + */ + SKIP, + /** + * The search continues as normal, adding successors from the target of the current edge. + */ + CONTINUE, + /** + * The search does not process additional edges to the target of the current edge, and the + * successors of the target node are not added to the work queue. + */ + IGNORE + } + + /** A CFG visitor decides which nodes to process based on the current edge (pred, succ). */ + interface CfgVisitor { + /** + * Returns the action a Breadth-First search should take for this edge. + * @param pred Edge source. + * @param succ Edge destination. + */ + public SearchAction processEdge(CfgNode pred, CfgNode succ); + } + + /** + * Performs a Breadth-First Search over the CFG successors starting from this node. + * @param visitor a visitor that decides when the search is terminated and which + * successors to process + * @return {@code null} if no match was found + */ + public CfgNode CfgNode.bfs(CfgVisitor visitor) { + Set<CfgNode> visited = Collections.newSetFromMap( + new IdentityHashMap<CfgNode, Boolean>()); + Queue<CfgNode> work = new LinkedList<CfgNode>(); + work.add(this); + while (!work.isEmpty()) { + CfgNode node = work.poll(); + for (CfgNode succ : node.successors()) { + if (!visited.contains(succ)) { + switch (visitor.processEdge(node, succ)) { + case SUCCESSOR_MATCH: + return succ; + case PREDECESSOR_MATCH: + return node; + case SKIP: + continue; + case CONTINUE: + work.add(succ); + visited.add(succ); + break; + case IGNORE: + visited.add(succ); + break; + } + } + } + } + // The search matched nothing and we exhausted all successors. + return null; + } + + inh lazy CfgEntry CfgNode.cfg(); + + /** + * Performs a Breadth-First Search over the CFG predecessors starting from this node. + * @param visitor a visitor that decides when the search is terminated and which + * predecessors to process + * @return {@code null} if no match was found + */ + public CfgNode CfgNode.reverseBfs(CfgVisitor visitor) { + Set<CfgNode> visited = Collections.newSetFromMap( + new IdentityHashMap<CfgNode, Boolean>()); + cfg().initPredecessors(); + Queue<CfgNode> work = new LinkedList<CfgNode>(); + work.add(this); + while (!work.isEmpty()) { + CfgNode node = work.poll(); + for (CfgNode pred : node.predecessors) { + if (!visited.contains(pred)) { + switch (visitor.processEdge(node, pred)) { + case SUCCESSOR_MATCH: + return pred; + case PREDECESSOR_MATCH: + return node; + case SKIP: + continue; + case CONTINUE: + work.add(pred); + visited.add(pred); + break; + case IGNORE: + visited.add(pred); + break; + } + } + } + } + // The search matched nothing and we exhausted all predecessors. + return null; + } + + /** A matcher used to search for particular nodes in a CFG. */ + interface CfgMatcher { + public boolean match(CfgNode node); + } + + /** Search for a previous node matching the matcher, starting from the current CFG node. */ + public CfgNode CfgNode.findPreviousNode(final CfgMatcher matcher) { + return reverseBfs(new CfgVisitor() { + @Override + public SearchAction processEdge(CfgNode pred, CfgNode succ) { + return matcher.match(succ) ? SearchAction.SUCCESSOR_MATCH : SearchAction.CONTINUE; + } + }); + } + + /** Check if there is a previous node matching the matcher, starting from the current CFG node. */ + syn boolean CfgNode.hasPreviousNode(CfgMatcher matcher) = null != findPreviousNode(matcher); +} diff --git a/simplecfg/src/main/jastadd/ClassPathFilter.ast b/simplecfg/src/main/jastadd/ClassPathFilter.ast new file mode 100644 index 0000000000000000000000000000000000000000..0acfbecbf4c23a749f940a1d7f682e930ef939f8 --- /dev/null +++ b/simplecfg/src/main/jastadd/ClassPathFilter.ast @@ -0,0 +1,22 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * PlaceholderTypeDecl is used to avoid locating and parsing source or class files when + * analyzing a single file. + * Represents a type that is known to exist but which we don't know anything else about. + */ +PlaceholderTypeDecl : ClassDecl; diff --git a/simplecfg/src/main/jastadd/ClassPathFilter.jrag b/simplecfg/src/main/jastadd/ClassPathFilter.jrag new file mode 100644 index 0000000000000000000000000000000000000000..33e9b33fd79d0e27f80816cf444966374a6998c3 --- /dev/null +++ b/simplecfg/src/main/jastadd/ClassPathFilter.jrag @@ -0,0 +1,235 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Replace type lookup to avoid parsing extra sources. + * All sources other than the one that is being analyzed is replaced + * by the Unknown type, or a placeholder type - a TypeDecl with just + * a name and package. + */ +aspect ClassPathFilter { + + /** The type lookup filter to use during type lookups. */ + private TypeLookupFilter Program.typeLookupFilter = NO_TYPE_FILTER; + + /** Changes the type lookup filter. */ + public void Program.setTypeLookupFilter(TypeLookupFilter filter) { + typeLookupFilter = filter; + } + + public interface TypeLookupFilter { + /** + * This allows extra source type map initialization. It is run after the default source type map + * initialization code. + */ + void initializeSourceTypeMap(Program program); + /** + * This allows extra library type map initialization. It is run after the default library type + * map initialization code. + */ + void initializeLibraryTypeMap(Program program); + + /** + * Returns the TypeDecl for the requested type. Returns UnknownType if the type was not + * found, or if the type was filtered out by this filter. + */ + TypeDecl lookupSourceType(Program program, String packageName, String typeName); + + /** + * Returns the TypeDecl for the requested type. Returns UnknownType if the type was not + * found, or if the type was filtered out by this filter. + */ + TypeDecl lookupLibraryType(Program program, String packageName, String typeName); + } + + /** This type filter does not perform any filtering. */ + public static final TypeLookupFilter Program.NO_TYPE_FILTER = new TypeLookupFilter() { + @Override + public void initializeSourceTypeMap(Program program) { + } + + @Override + public void initializeLibraryTypeMap(Program program) { + } + + @Override + public TypeDecl lookupSourceType(Program program, String packageName, String typeName) { + return program.lookupSourceType(packageName, typeName); + } + + @Override + public TypeDecl lookupLibraryType(Program program, String packageName, String typeName) { + return program.lookupLibraryType(packageName, typeName); + } + }; + + /** This type filter filters out library types. */ + public static final TypeLookupFilter Program.BASE_LIBRARY_FILTER = new TypeLookupFilter() { + @Override + public void initializeSourceTypeMap(Program program) { + } + + @Override + public void initializeLibraryTypeMap(Program program) { + // All types that need to be distinguishable in the code being analyzed + // should be added as placeholders here. + // This list contains all types which are looked up explicitly in the + // ExtendJ frontend code with lookupType(pkg, name) All type lookups that + // don't match a placeholder type get mapped to the Unknown type. + program.addPlaceholderType("java.lang", "Object"); + program.addPlaceholderType("java.lang", "AutoCloseable"); + program.addPlaceholderType("java.lang", "Class"); + program.addPlaceholderType("java.lang", "Cloneable"); + program.addPlaceholderType("java.lang", "Error"); + program.addPlaceholderType("java.lang", "Exception"); + program.addPlaceholderType("java.lang", "FunctionalInterface"); + program.addPlaceholderType("java.lang", "NullPointerException"); + program.addPlaceholderType("java.lang", "Throwable"); + program.addPlaceholderType("java.lang", "Enum"); + program.addPlaceholderType("java.lang", "Iterable"); + program.addPlaceholderType("java.lang", "Iterator"); + program.addPlaceholderType("java.lang", "RuntimeException"); + + // Annotations and boxed primitive types are required + // to do some simple type analysis. + + // Add annotation types. + program.addPlaceholderType("java.lang.annotation", "Target"); + program.addPlaceholderType("java.lang.annotation", "Retention"); + program.addPlaceholderType("java.lang.annotation", "Inherited"); + program.addPlaceholderType("java.lang", "SuppressWarnings"); + program.addPlaceholderType("java.lang", "Override"); + program.addPlaceholderType("java.lang", "Serializable"); + + // Boxed primitive types. + program.addPlaceholderType("java.lang", "Integer"); + program.addPlaceholderType("java.lang", "Float"); + program.addPlaceholderType("java.lang", "Short"); + program.addPlaceholderType("java.lang", "Byte"); + program.addPlaceholderType("java.lang", "Character"); + program.addPlaceholderType("java.lang", "Long"); + program.addPlaceholderType("java.lang", "Double"); + program.addPlaceholderType("java.lang", "String"); + program.addPlaceholderType("java.lang", "Boolean"); + program.addPlaceholderType("java.lang", "Void"); + } + + @Override + public TypeDecl lookupSourceType(Program program, String packageName, String typeName) { + return program.lookupSourceType(packageName, typeName); + } + + @Override + public TypeDecl lookupLibraryType(Program program, String packageName, String typeName) { + String fullName = packageName.isEmpty() ? typeName : packageName + "." + typeName; + if (program.libraryTypeMap.containsKey(fullName)) { + return program.libraryTypeMap.get(fullName); + } else { + program.libraryTypeMap.put(fullName, program.unknownType()); + return program.unknownType(); + } + } + }; + + public static final TypeLookupFilter Program.ANALYZER_TYPE_FILTER = new TypeLookupFilter() { + @Override + public void initializeSourceTypeMap(Program program) { + BASE_LIBRARY_FILTER.initializeSourceTypeMap(program); + } + + @Override + public void initializeLibraryTypeMap(Program program) { + BASE_LIBRARY_FILTER.initializeLibraryTypeMap(program); + + // Types needed for read/write after close analysis. + program.addPlaceholderType("java.io", "Writer"); + program.addPlaceholderType("java.io", "Reader"); + + // Types needed for Nullable Dereference analysis. + program.addPlaceholderType("javax.annotation", "Nullable"); + } + + @Override + public TypeDecl lookupSourceType(Program program, String packageName, String typeName) { + return BASE_LIBRARY_FILTER.lookupSourceType(program, packageName, typeName); + } + + @Override + public TypeDecl lookupLibraryType(Program program, String packageName, String typeName) { + return BASE_LIBRARY_FILTER.lookupLibraryType(program, packageName, typeName); + } + }; + + refine LookupFullyQualifiedTypes + protected void Program.initializeSourceTypeMap() { + refined(); + typeLookupFilter.initializeSourceTypeMap(this); + } + + refine LookupFullyQualifiedTypes + protected void Program.initializeLibraryTypeMap() { + refined(); + typeLookupFilter.initializeLibraryTypeMap(this); + } + + refine LookupFullyQualifiedTypes + eq Program.lookupType(String packageName, String typeName) { + TypeDecl sourceType = typeLookupFilter.lookupSourceType(this, packageName, typeName); + if (!sourceType.isUnknown()) { + return sourceType; + } + if (!libraryTypeMapInitialized) { + initializeLibraryTypeMap(); + libraryTypeMapInitialized = true; + } + return typeLookupFilter.lookupLibraryType(this, packageName, typeName); + } + + /** + * Add a placeholder type declaration to the library type map. + * This triggers evaluation of a placeholder type NTA and a placeholder + * compilation unit NTA. + */ + public void Program.addPlaceholderType(String packageName, String typeName) { + String fullName = packageName.equals("") ? typeName : packageName + "." + typeName; + CompilationUnit cu = placeholderCompilationUnit(packageName); + cu.setFromSource(false); + cu.setClassSource(ClassSource.NONE); + TypeDecl placeholder = cu.placeholderTypeDecl(typeName); + libraryTypeMap.put(fullName, placeholder); + } + + /** + * Build a placeholder compilation unit for types in a package. + */ + syn nta CompilationUnit Program.placeholderCompilationUnit(String packageName) { + CompilationUnit u = new CompilationUnit(); + u.setPackageDecl(packageName); + return u; + } + + /** + * Build placeholder type declaration. + */ + syn nta TypeDecl CompilationUnit.placeholderTypeDecl(String typeName) { + PlaceholderTypeDecl decl = new PlaceholderTypeDecl(); + decl.setModifiers(new Modifiers(new List().add(new Modifier("public")))); + decl.setID(typeName); + return decl; + } + +} + diff --git a/simplecfg/src/main/jastadd/Findings.jrag b/simplecfg/src/main/jastadd/Findings.jrag new file mode 100644 index 0000000000000000000000000000000000000000..104905bf4d3267d9d135e467465c2991c31dba01 --- /dev/null +++ b/simplecfg/src/main/jastadd/Findings.jrag @@ -0,0 +1,119 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import java.util.Collection; +import java.util.LinkedList; + +/** + * This aspect provides the general attributes to collect findings for the + * available analyses. + */ +aspect Findings { + /** Collection of API usage findings. */ + coll Collection<ExtendJFinding> CompilationUnit.findings() + [new LinkedList<ExtendJFinding>()] + with add + root CompilationUnit; + + /** Build a new finding with the given subcategory and message. */ + syn ExtendJFinding ASTNode.finding(String subcategory, String message) { + ASTNode location = locationNode(); + return new ExtendJFinding(sourceFile(), subcategory, message, + getLine(location.getStart()), getColumn(location.getStart()), + getLine(location.getEnd()), getColumn(location.getEnd())); + } + + /** Find the closest AST node with source location information. */ + syn ASTNode ASTNode.locationNode() { + ASTNode node = this; + while (node.getParent() != null && node.getStart() == 0) { + node = node.getParent(); + } + return node; + } + + /** Find the indentation for the current statement. */ + inh String Stmt.indentation(); + + /** Find the indentation for the current expression. */ + inh String Expr.indentation(); + + /** Find the indentation for the current type declaration. */ + inh String TypeDecl.indentation(); + + eq Block.getChild().indentation() = indentation() + " "; + eq TypeDecl.getChild().indentation() = indentation() + " "; + + eq CompilationUnit.getChild().indentation() = ""; + eq Program.getChild().indentation() = ""; + + /** A finding produced by an ExtendJ analyzer. */ + public class ExtendJFinding { + + public final String sourcePath; + public final String subcategory; + public final String message; + public final int startLine; + public final int startColumn; + public final int endLine; + public final int endColumn; + public final Collection<ExtendJFix> fixes = new ArrayList<>(); + + /** + * Describes a suggested fix. The suggested fix has a description which does not seem to + * show up in Critique. The new text should end with a newline. + */ + public static class ExtendJFix { + public final String description; + public final int startLine; + public final int endLine; + public final String newText; + + ExtendJFix(String description, int startLine, int endLine, String newText) { + this.description = description; + this.startLine = startLine; + this.endLine = endLine; + this.newText = newText; + } + } + + public ExtendJFinding(String sourcePath, String subcategory, String message, + int startLine, int startColumn, int endLine, int endColumn) { + this.sourcePath = sourcePath; + this.subcategory = subcategory; + this.message = message; + this.startLine = startLine; + this.startColumn = startColumn; + this.endLine = endLine; + this.endColumn = endColumn; + } + + /** + * Set a new suggested fix for this finding. + */ + public ExtendJFinding addFix(String description, int startLine, int endLine, String newText) { + fixes.add(new ExtendJFix(description, startLine, endLine, newText)); + return this; + } + + @Override + public String toString() { + // This message is printed for each finding when running the CLI. + return String.format("%s:%d:%d: %s", sourcePath, startLine, startColumn, message); + } + } +} diff --git a/simplecfg/src/main/jastadd/NullableDereferenceAnalysis.jrag b/simplecfg/src/main/jastadd/NullableDereferenceAnalysis.jrag new file mode 100644 index 0000000000000000000000000000000000000000..db9afd1b5bab4b1dbc43107c23dd6ac4ff13b69c --- /dev/null +++ b/simplecfg/src/main/jastadd/NullableDereferenceAnalysis.jrag @@ -0,0 +1,504 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Adds an analysis that checks for dereferences of a parameter declared nullable. + * + * <p>When a method or constructor parameter is annotated with javax.annotation.Nullable, + * we check that all dereferences of that parameter are guarded by a null-check. The + * analysis is control-flow sensitive in that it will recognize if all control flow paths + * to the dereference are effectively guarded by a null check, for example: {@code + * if (p == null) return; + * p.x(); // Guarded by null check above. + * } + * + * <p>The analysis is not intraprocedural, so in order to avoid false positives + * where a method call guards against nullness the analyzer assumes that + * calling a method with an argument x results in an exception if x is null and + * thus works like an effective null guard for x. + * + * <p>To find potential null dereferences on nullable parameters, the analysis does a forward CFG + * traversal from the entry-point of the method. Note that this is only done whenever a + * {@code @Nullable} parameter has been encountered in the method. The traversal explores all paths + * from the method entry until it finds null guard statements such as {@code if (p != null)}. The + * branches that are protected from nullness get pruned in the search and the search continues on + * other branches. The search is performed by a {@code NullDereferenceLocator}, which implements the + * {@code CfgSearch} interface. This visitor is invoked for each node in the CFG search, and it + * decides if the search will continue from that node, or if the current edge should be skipped + * (i.e., pruned). + * + * <p>In order to find the position in the CFG where a potential null dereference occurs, CFG marker + * nodes are inserted. This is done by adding a synthesized non-terminal attribute (NTA) for + * a CfgMarker on Dot (which represents Java dot expressions). This marker node appears + * in the CFG as "nullable access" because it is inserted whenever a nullable variable is + * dereferenced. + * + * <p>Dataflow analysis is not used, so in order to analyze a parameter it is required to be + * effectively final, i.e. it is not assigned anywhere in side the body of the method/constructor. + */ +aspect NullableDereferenceAnalysis { + + // Give ParameterDeclaration access to the inherited compilationUnit attribute. + inh CompilationUnit ParameterDeclaration.compilationUnit(); + + ParameterDeclaration contributes nullableDereferenceFinding() + when nullableDereferenceFinding() != null + to CompilationUnit.findings() + for compilationUnit(); + + /** + * Generate a NullableDereference finding for this dot expression, + * if no finding should be reported this attribute returns {@code null}. + */ + syn lazy ExtendJFinding ParameterDeclaration.nullableDereferenceFinding() { + if (!getModifiers().hasNullableAnnotation()) { + return null; + } + if (!isFinal() && !isEffectivelyFinal()) { + // Do not analyze non-effectively final parameters. + return null; + } + Expr location = findNullableDereference(this); + if (location == null) { + return null; + } + ExtendJFinding finding = location.finding("NullableDereference", String.format( + "Dereferencing %s, which was declared @Nullable.", name())); + if (compilationUnit().fromSource()) { + ASTNode modifierLocation = nullableModifierLocation(); + int line = getLine(modifierLocation.getStart()); + int startCol = getColumn(modifierLocation.getStart()); + int endCol = getColumn(modifierLocation.getEnd()); + if (startCol < endCol && line == getLine(modifierLocation.getEnd())) { + try { + InputStream data = compilationUnit().getClassSource().openInputStream(); + java.util.Scanner scanner = new java.util.Scanner(data); + for (int i = 1; i < line && scanner.hasNextLine(); ++i) { + scanner.nextLine(); + } + if (scanner.hasNextLine()) { + String text = scanner.nextLine(); + finding.addFix("Remove the @Nullable annotation.", + line, line, + text.substring(0,startCol-1) + text.substring(endCol+1) + "\n"); + } + } catch (IOException e) { + // Failed to unparse the current line. + // This is not a serious problem; we just don't give a fix suggestion. + } + } + } + return finding; + } + + // Exclude variable arity parameters from Nullable dereference analysis. + // When a variable arity parameter is annotated @Nullable, that will most likely be intended as a + // @Nullable annotation for the individual parameters, not the containing argument array. + eq VariableArityParameterDeclaration.nullableDereferenceFinding() = null; + + /** + * Find the location node for the javax.annotation.Nullable annotation in the modifier list. + * Returns {@code null} if the location of the modifier was not found. + */ + syn ASTNode ParameterDeclaration.nullableModifierLocation() = + getModifiers().nullableModifierLocation(); + + syn ASTNode Modifiers.nullableModifierLocation() { + for (Modifier modifier : getModifierList()) { + if (modifier.isAnnotation("javax.annotation", "Nullable")) { + return modifier.locationNode(); + } + } + return null; + } + + /** + * Find a location, not necessarily the first location, in the host method/constructor where the + * parameter is accessed without a null guard. + */ + inh Expr ParameterDeclaration.findNullableDereference(Variable var); + + eq Program.getChild().findNullableDereference(Variable var) = null; + eq BodyDecl.getChild().findNullableDereference(Variable var) = null; + + eq MethodDecl.getParameter().findNullableDereference(Variable var) { + if (!hasBlock()) { + return null; + } + CfgNode cfgNode = entry().bfs(new NullDereferenceLocator(var)); + return cfgNode == null ? null : cfgNode.receiverExpr(); + } + + eq ConstructorDecl.getParameter().findNullableDereference(Variable var) { + CfgNode cfgNode = entry().bfs(new NullDereferenceLocator(var)); + return cfgNode == null ? null : cfgNode.receiverExpr(); + } + + /** + * A CFG visitor that searches in the forward CFG for a nullable dereference. + * + * <p>The search stops at parts of the search tree guarded by a null check + * on the receiver variable. + */ + class NullDereferenceLocator implements CfgVisitor { + private final Variable var; + + public NullDereferenceLocator(Variable var) { + this.var = var; + } + + @Override public SearchAction processEdge(CfgNode pred, CfgNode succ) { + if (pred.isNullGuard(var, succ)) { + return SearchAction.SKIP; + } + Expr receiver = succ.receiverExpr(); + if (receiver != null && receiver.isVariable(var) && !receiver.hasNullGuard(var)) { + return SearchAction.SUCCESSOR_MATCH; + } + return SearchAction.CONTINUE; + } + } + + /** + * Returns the receiver expression if the CFG node is the child of a dereference expression. + * Returns {@code null} otherwise. + */ + inh Expr CfgNode.receiverExpr(); + eq Program.getChild().receiverExpr() = null; + eq BodyDecl.getChild().receiverExpr() = null; + eq BodyDecl.exit().receiverExpr() = null; + eq TryStmt.tryEntryMarker().receiverExpr() = null; + eq BreakStmt.marker().receiverExpr() = null; + eq ContinueStmt.marker().receiverExpr() = null; + eq ReturnStmt.marker().receiverExpr() = null; + eq MethodAccess.exceptionNode().receiverExpr() = null; + eq MethodAccess.call().receiverExpr() = + hasPrevExpr() + ? prevExpr() + : null; + eq ThrowStmt.exceptionNode().receiverExpr() = null; + eq TryStmt.exceptionNode().receiverExpr() = null; + eq ConditionalExpr.branch().receiverExpr() = null; + eq ConditionalExpr.thenEndMarker().receiverExpr() = null; + eq ConditionalExpr.elseEndMarker().receiverExpr() = null; + eq IfStmt.branch().receiverExpr() = null; + eq IfStmt.thenEndMarker().receiverExpr() = null; + eq IfStmt.elseEndMarker().receiverExpr() = null; + eq ForStmt.branch().receiverExpr() = null; + eq EnhancedForStmt.branch().receiverExpr() = null; + eq WhileStmt.branch().receiverExpr() = null; + eq DoStmt.branch().receiverExpr() = null; + eq SwitchStmt.branch().receiverExpr() = null; + eq LambdaBody.exit().receiverExpr() = null; + eq Dot.nullableDereferenceMarker().receiverExpr() = getLeft(); + + /** Marker node used to find location of a nullable dereference in the CFG. */ + syn nta CfgMarker Dot.nullableDereferenceMarker() = new CfgMarker(); + + /** Insert nullable dereference marker in the CFG. */ + refine SimpleCFG + eq Dot.getLeft().follow() = + getRight().isMethodAccess() + ? refined() + : nullableDereferenceMarker(); + + eq Dot.nullableDereferenceMarker().succ() = Collections.singleton(getRight().entry()); + + syn boolean CfgNode.isNullGuard(Variable var, CfgNode succ) = false; + + /** + * We assume that calling a method with the variable var as an argument + * results in an exception thrown by the method call if var is null. This is + * not true for many methods, but it should reduce the false positive rate + * for the NullableDereference analyzer. + */ + eq CfgMethodCall.isNullGuard(Variable var, CfgNode succ) { + if (succ instanceof CfgException) { + return false; + } + MethodAccess access = methodAccess(); + for (Expr arg : access.getArgList()) { + if (arg.isVariable(var)) { + return true; + } + } + return false; + } + + /** Check if this branch has a null-guarding condition. */ + eq CfgBranch.isNullGuard(Variable var, CfgNode succ) = inNullGuard(var, succ); + + inh boolean CfgBranch.inNullGuard(Variable var, CfgNode succ); + + eq IfStmt.branch().inNullGuard(Variable var, CfgNode succ) = + succ == getThen().entry() + ? getCondition().isNonNullWhenTrue(var) + : getCondition().isNonNullWhenFalse(var); + + eq ConditionalExpr.branch().inNullGuard(Variable var, CfgNode succ) = + succ == getTrueExpr().entry() + ? getCondition().isNonNullWhenTrue(var) + : getCondition().isNonNullWhenFalse(var); + + eq ForStmt.branch().inNullGuard(Variable var, CfgNode succ) = + succ == getStmt().entry() + ? getCondition().isNonNullWhenTrue(var) + : getCondition().isNonNullWhenFalse(var); + + eq WhileStmt.branch().inNullGuard(Variable var, CfgNode succ) = + succ == getStmt().entry() + ? getCondition().isNonNullWhenTrue(var) + : getCondition().isNonNullWhenFalse(var); + + eq EnhancedForStmt.branch().inNullGuard(Variable var, CfgNode succ) = false; + eq DoStmt.branch().inNullGuard(Variable var, CfgNode succ) = false; + eq SwitchStmt.branch().inNullGuard(Variable var, CfgNode succ) = false; + + /** Returns {@code true} if this set of modifiers includes {@code javax.annotation.Nullable}. */ + syn boolean Modifiers.hasNullableAnnotation() = hasAnnotation("javax.annotation", "Nullable"); + + /** Return {@code true} if this expression is guarded by a != null check for var. */ + inh boolean Expr.hasNullGuard(Variable var); + eq Program.getChild().hasNullGuard(Variable var) = false; + eq IfStmt.getThen().hasNullGuard(Variable var) = getCondition().isNonNullWhenTrue(var); + eq IfStmt.getElse().hasNullGuard(Variable var) = getCondition().isNonNullWhenFalse(var); + eq WhileStmt.getStmt().hasNullGuard(Variable var) = getCondition().isNonNullWhenTrue(var); + eq ForStmt.getStmt().hasNullGuard(Variable var) = getCondition().isNonNullWhenTrue(var); + eq ConditionalExpr.getTrueExpr().hasNullGuard(Variable var) = + getCondition().isNonNullWhenTrue(var) || hasNullGuard(var); + eq ConditionalExpr.getFalseExpr().hasNullGuard(Variable var) = + getCondition().isNonNullWhenFalse(var) || hasNullGuard(var); + eq AndLogicalExpr.getRightOperand().hasNullGuard(Variable var) = + getLeftOperand().isNonNullWhenTrue(var) || hasNullGuard(var); + eq AndBitwiseExpr.getRightOperand().hasNullGuard(Variable var) = + getLeftOperand().isNonNullWhenTrue(var) || hasNullGuard(var); + eq OrLogicalExpr.getRightOperand().hasNullGuard(Variable var) = + getLeftOperand().isNonNullWhenFalse(var) || hasNullGuard(var); + + /** @return {@code true} if the variable var is null when this expression is true. */ + syn boolean Expr.isNullWhenTrue(Variable var) = false; + + eq NEExpr.isNullWhenTrue(Variable var) = + getLeftOperand().isTrue() && getRightOperand().isNullWhenFalse(var) + || getRightOperand().isTrue() && getLeftOperand().isNullWhenFalse(var) + || getLeftOperand().isFalse() && getRightOperand().isNullWhenTrue(var) + || getRightOperand().isFalse() && getLeftOperand().isNullWhenTrue(var); + + eq EQExpr.isNullWhenTrue(Variable var) = + getLeftOperand().isNull() && getRightOperand().varDecl() == var + || getRightOperand().isNull() && getLeftOperand().varDecl() == var + || getLeftOperand().isTrue() && getRightOperand().isNullWhenTrue(var) + || getRightOperand().isTrue() && getLeftOperand().isNullWhenTrue(var) + || getLeftOperand().isFalse() && getRightOperand().isNullWhenFalse(var) + || getRightOperand().isFalse() && getLeftOperand().isNullWhenFalse(var); + + eq LogNotExpr.isNullWhenTrue(Variable var) = getOperand().isNullWhenFalse(var); + + eq ParExpr.isNullWhenTrue(Variable var) = getExpr().isNullWhenTrue(var); + + eq AndLogicalExpr.isNullWhenTrue(Variable var) = + getLeftOperand().isNullWhenTrue(var) || getRightOperand().isNullWhenTrue(var); + + eq AndBitwiseExpr.isNullWhenTrue(Variable var) = + getLeftOperand().isNullWhenTrue(var) || getRightOperand().isNullWhenTrue(var); + + eq OrLogicalExpr.isNullWhenTrue(Variable var) = + getLeftOperand().isFalse() && getRightOperand().isNullWhenTrue(var) + || getRightOperand().isFalse() && getLeftOperand().isNullWhenTrue(var); + + eq Dot.isNullWhenTrue(Variable var) = + !getLeft().isVariable(var) && getRight().isNullWhenTrue(var); + + // Assume that a method call to X.isNull_(var) is equivalent to a null test on var. + eq MethodAccess.isNullWhenTrue(Variable var) = + name().startsWith("isNull") && getNumArg() == 1 && getArg(0).isVariable(var); + + eq VarAccess.isNullWhenTrue(Variable var) = decl().isNullWhenTrue(var); + + syn boolean Variable.isNullWhenTrue(Variable var); + eq EnumConstant.isNullWhenTrue(Variable var) = false; + eq ParameterDeclaration.isNullWhenTrue(Variable var) = false; + eq FieldDeclarator.isNullWhenTrue(Variable var) = false; + eq CatchParameterDeclaration.isNullWhenTrue(Variable var) = false; + eq InferredParameterDeclaration.isNullWhenTrue(Variable var) = false; + eq VariableDeclarator.isNullWhenTrue(Variable var) = + type().isBoolean() && hasInit() && isEffectivelyFinal() + ? getInit().isNullWhenTrue(var) + : false; + + /** @return {@code true} if the variable var is null when this expression is false. */ + syn boolean Expr.isNullWhenFalse(Variable var) = false; + + eq NEExpr.isNullWhenFalse(Variable var) = + getLeftOperand().isNull() && getRightOperand().varDecl() == var + || getRightOperand().isNull() && getLeftOperand().varDecl() == var + || getLeftOperand().isTrue() && getRightOperand().isNullWhenTrue(var) + || getRightOperand().isTrue() && getLeftOperand().isNullWhenTrue(var) + || getLeftOperand().isFalse() && getRightOperand().isNullWhenFalse(var) + || getRightOperand().isFalse() && getLeftOperand().isNullWhenFalse(var); + + eq EQExpr.isNullWhenFalse(Variable var) = + getLeftOperand().isTrue() && getRightOperand().isNullWhenFalse(var) + || getRightOperand().isTrue() && getLeftOperand().isNullWhenFalse(var) + || getLeftOperand().isFalse() && getRightOperand().isNullWhenTrue(var) + || getRightOperand().isFalse() && getLeftOperand().isNullWhenTrue(var); + + eq LogNotExpr.isNullWhenFalse(Variable var) = getOperand().isNullWhenTrue(var); + + eq ParExpr.isNullWhenFalse(Variable var) = getExpr().isNullWhenFalse(var); + + eq AndLogicalExpr.isNullWhenFalse(Variable var) = + getLeftOperand().isTrue() && getRightOperand().isNullWhenFalse(var) + || getRightOperand().isTrue() && getLeftOperand().isNullWhenFalse(var); + + eq AndBitwiseExpr.isNullWhenFalse(Variable var) = + getLeftOperand().isTrue() && getRightOperand().isNullWhenFalse(var) + || getRightOperand().isTrue() && getLeftOperand().isNullWhenFalse(var); + + eq OrLogicalExpr.isNullWhenFalse(Variable var) = + getLeftOperand().isNullWhenFalse(var) && getRightOperand().isNullWhenFalse(var); + + eq Dot.isNullWhenFalse(Variable var) = + !getLeft().isVariable(var) && getRight().isNullWhenFalse(var); + + // Assume that a method call to X.isNo{t,n}Null_(var) is equivalent to a non-null test on var. + eq MethodAccess.isNullWhenFalse(Variable var) = + (name().startsWith("isNotNull") || name().startsWith("isNonNull")) + && getNumArg() == 1 && getArg(0).isVariable(var); + + eq VarAccess.isNullWhenFalse(Variable var) = decl().isNullWhenFalse(var); + + syn boolean Variable.isNullWhenFalse(Variable var); + eq EnumConstant.isNullWhenFalse(Variable var) = false; + eq ParameterDeclaration.isNullWhenFalse(Variable var) = false; + eq FieldDeclarator.isNullWhenFalse(Variable var) = false; + eq CatchParameterDeclaration.isNullWhenFalse(Variable var) = false; + eq InferredParameterDeclaration.isNullWhenFalse(Variable var) = false; + eq VariableDeclarator.isNullWhenFalse(Variable var) = + type().isBoolean() && hasInit() && isEffectivelyFinal() + ? getInit().isNullWhenFalse(var) + : false; + + /** @return {@code true} if the variable var is non-null when this expression is true. */ + syn boolean Expr.isNonNullWhenTrue(Variable var) = false; + + eq NEExpr.isNonNullWhenTrue(Variable var) = + getLeftOperand().isNull() && getRightOperand().varDecl() == var + || getRightOperand().isNull() && getLeftOperand().varDecl() == var + || getLeftOperand().isTrue() && getRightOperand().isNonNullWhenFalse(var) + || getRightOperand().isTrue() && getLeftOperand().isNonNullWhenFalse(var) + || getLeftOperand().isFalse() && getRightOperand().isNonNullWhenTrue(var) + || getRightOperand().isFalse() && getLeftOperand().isNonNullWhenTrue(var); + + eq EQExpr.isNonNullWhenTrue(Variable var) = + getLeftOperand().isTrue() && getRightOperand().isNonNullWhenTrue(var) + || getRightOperand().isTrue() && getLeftOperand().isNonNullWhenTrue(var) + || getLeftOperand().isFalse() && getRightOperand().isNonNullWhenFalse(var) + || getRightOperand().isFalse() && getLeftOperand().isNonNullWhenFalse(var); + + eq LogNotExpr.isNonNullWhenTrue(Variable var) = getOperand().isNullWhenTrue(var); + + eq ParExpr.isNonNullWhenTrue(Variable var) = getExpr().isNonNullWhenTrue(var); + + eq AndLogicalExpr.isNonNullWhenTrue(Variable var) = + getLeftOperand().isNonNullWhenTrue(var) || getRightOperand().isNonNullWhenTrue(var); + + eq AndBitwiseExpr.isNonNullWhenTrue(Variable var) = + getLeftOperand().isNonNullWhenTrue(var) || getRightOperand().isNonNullWhenTrue(var); + + eq OrLogicalExpr.isNonNullWhenTrue(Variable var) = + getLeftOperand().isFalse() && getRightOperand().isNonNullWhenTrue(var) + || getRightOperand().isFalse() && getLeftOperand().isNonNullWhenTrue(var); + + eq Dot.isNonNullWhenTrue(Variable var) = + !getLeft().isVariable(var) && getRight().isNonNullWhenTrue(var); + + // Assume that a method call to X.isNo{t,n}Null_(var) is equivalent to a non-null test on var. + eq MethodAccess.isNonNullWhenTrue(Variable var) = + (name().startsWith("isNotNull") || name().startsWith("isNonNull")) + && getNumArg() == 1 && getArg(0).isVariable(var); + + eq VarAccess.isNonNullWhenTrue(Variable var) = decl().isNonNullWhenTrue(var); + + syn boolean Variable.isNonNullWhenTrue(Variable var); + eq EnumConstant.isNonNullWhenTrue(Variable var) = false; + eq ParameterDeclaration.isNonNullWhenTrue(Variable var) = false; + eq FieldDeclarator.isNonNullWhenTrue(Variable var) = false; + eq CatchParameterDeclaration.isNonNullWhenTrue(Variable var) = false; + eq InferredParameterDeclaration.isNonNullWhenTrue(Variable var) = false; + eq VariableDeclarator.isNonNullWhenTrue(Variable var) = + type().isBoolean() && hasInit() && isEffectivelyFinal() + ? getInit().isNonNullWhenTrue(var) + : false; + + // An instanceof check guards against the variable being null. + eq InstanceOfExpr.isNonNullWhenTrue(Variable var) = getExpr().isVariable(var); + + /** @return {@code true} if the variable var is non-null when this expression is false. */ + syn boolean Expr.isNonNullWhenFalse(Variable var) = false; + + eq NEExpr.isNonNullWhenFalse(Variable var) = + getLeftOperand().isTrue() && getRightOperand().isNonNullWhenTrue(var) + || getRightOperand().isTrue() && getLeftOperand().isNonNullWhenTrue(var) + || getLeftOperand().isFalse() && getRightOperand().isNonNullWhenFalse(var) + || getRightOperand().isFalse() && getLeftOperand().isNonNullWhenFalse(var); + + eq EQExpr.isNonNullWhenFalse(Variable var) = + getLeftOperand().isNull() && getRightOperand().varDecl() == var + || getRightOperand().isNull() && getLeftOperand().varDecl() == var + || getLeftOperand().isTrue() && getRightOperand().isNonNullWhenFalse(var) + || getRightOperand().isTrue() && getLeftOperand().isNonNullWhenFalse(var) + || getLeftOperand().isFalse() && getRightOperand().isNonNullWhenTrue(var) + || getRightOperand().isFalse() && getLeftOperand().isNonNullWhenTrue(var); + + eq LogNotExpr.isNonNullWhenFalse(Variable var) = getOperand().isNonNullWhenTrue(var); + + eq ParExpr.isNonNullWhenFalse(Variable var) = getExpr().isNonNullWhenFalse(var); + + eq AndLogicalExpr.isNonNullWhenFalse(Variable var) = + getLeftOperand().isTrue() && getRightOperand().isNonNullWhenFalse(var) + || getRightOperand().isTrue() && getLeftOperand().isNonNullWhenFalse(var); + + eq AndBitwiseExpr.isNonNullWhenFalse(Variable var) = + getLeftOperand().isTrue() && getRightOperand().isNonNullWhenFalse(var) + || getRightOperand().isTrue() && getLeftOperand().isNonNullWhenFalse(var); + + eq OrLogicalExpr.isNonNullWhenFalse(Variable var) = + getLeftOperand().isNonNullWhenFalse(var) || getRightOperand().isNonNullWhenFalse(var); + + eq Dot.isNonNullWhenFalse(Variable var) = + !getLeft().isVariable(var) && getRight().isNonNullWhenFalse(var); + + // Assume that a method call to X.isNull_(var) is equivalent to a null test on var. + eq MethodAccess.isNonNullWhenFalse(Variable var) = + name().startsWith("isNull") && getNumArg() == 1 && getArg(0).isVariable(var); + + eq VarAccess.isNonNullWhenFalse(Variable var) = decl().isNonNullWhenFalse(var); + + syn boolean Variable.isNonNullWhenFalse(Variable var); + eq EnumConstant.isNonNullWhenFalse(Variable var) = false; + eq ParameterDeclaration.isNonNullWhenFalse(Variable var) = false; + eq FieldDeclarator.isNonNullWhenFalse(Variable var) = false; + eq CatchParameterDeclaration.isNonNullWhenFalse(Variable var) = false; + eq InferredParameterDeclaration.isNonNullWhenFalse(Variable var) = false; + eq VariableDeclarator.isNonNullWhenFalse(Variable var) = + type().isBoolean() && hasInit() && isEffectivelyFinal() + ? getInit().isNonNullWhenFalse(var) + : false; + + syn boolean Expr.isNull() = type().isNull(); + eq NullLiteral.isNull() = true; +} diff --git a/simplecfg/src/main/jastadd/PrintCfg.jrag b/simplecfg/src/main/jastadd/PrintCfg.jrag new file mode 100644 index 0000000000000000000000000000000000000000..35b7f87b20941a63f9140fb13ba2e5fa7f21c244 --- /dev/null +++ b/simplecfg/src/main/jastadd/PrintCfg.jrag @@ -0,0 +1,145 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.util.Collections; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.LinkedList; +import java.util.Set; +import java.util.Queue; + +/** Helper attributes used to print a CFG in dot graph format. */ +aspect PrintCfg { + + @Override + public String CfgNode.toString() { + return name(); + } + + public void BodyDecl.printReverseCfg() { + entry().initPredecessors(); + System.out.println("digraph " + graphName() + " {"); + exit().printReverseCfg(); + System.out.println("}"); + } + + public void CfgNode.printReverseCfg() { + Set<CfgNode> visited = Collections.newSetFromMap(new IdentityHashMap<CfgNode, Boolean>()); + Queue<CfgNode> queue = new LinkedList<CfgNode>(); + + // Enqueue this node. + visited.add(this); + queue.add(this); + + while (!queue.isEmpty()) { + CfgNode work = queue.poll(); + + System.out.format(" %s%s;\n", work.dotId(), work.dotAttributes()); + + // Add all out-edges for this node. + for (CfgNode succ : work.predecessors) { + System.out.format(" %s -> %s;\n", work.dotId(), succ.dotId()); + if (!visited.contains(succ)) { + visited.add(succ); + queue.add(succ); + } + } + } + } + + public void BodyDecl.printCfg() { + System.out.println("digraph " + graphName() + " {"); + entry().printCfg(); + System.out.println("}"); + } + + public void CfgNode.printCfg() { + Set<CfgNode> visited = Collections.newSetFromMap(new IdentityHashMap<CfgNode, Boolean>()); + Queue<CfgNode> queue = new LinkedList<CfgNode>(); + + // Enqueue this node. + visited.add(this); + queue.add(this); + + while (!queue.isEmpty()) { + CfgNode work = queue.poll(); + + System.out.format(" %s%s;\n", work.dotId(), work.dotAttributes()); + + // Add all out-edges for this node. + for (CfgNode succ : work.successors()) { + System.out.format(" %s -> %s;\n", work.dotId(), succ.dotId()); + if (!visited.contains(succ)) { + visited.add(succ); + queue.add(succ); + } + } + } + } + + syn String BodyDecl.graphName() = ""; + eq MethodDecl.graphName() = name(); + + /** + * The ID for this node in a dot graph. + */ + syn String CfgNode.dotId() = String.format("n%08X", hashCode()); + + // TODO(joqvist): escape string literals in generated labels. + syn String CfgNode.dotAttributes() = " [label=\"" + name() + "\"]"; + eq CfgBranch.dotAttributes() = " [label=\"" + name() + "\",shape=diamond]"; + eq CfgException.dotAttributes() = " [label=\"" + name() + "\",shape=box]"; + eq CfgMarker.dotAttributes() = " [label=\"" + name() + "\",shape=box]"; + + syn String CfgNode.name(); + eq CfgBranch.name() = branchLabel(); + eq CfgEntry.name() = "entry"; + eq CfgExit.name() = "exit"; + eq CfgException.name() = "exception"; + eq CfgMarker.name() = markerName(); + eq CfgMethodCall.name() = callLabel(); + + inh String CfgMethodCall.callLabel(); + eq MethodAccess.call().callLabel() = name() + "()"; + + inh String CfgBranch.branchLabel(); + eq IfStmt.branch().branchLabel() = "if (" + getCondition().prettyPrint() + ")"; + eq ConditionalExpr.branch().branchLabel() = "if (" + getCondition().prettyPrint() + ")"; + eq ForStmt.branch().branchLabel() = "for (" + getCondition().prettyPrint() + ")"; + eq WhileStmt.branch().branchLabel() = "while (" + getCondition().prettyPrint() + ")"; + eq DoStmt.branch().branchLabel() = "do_while (" + getCondition().prettyPrint() + ")"; + eq EnhancedForStmt.branch().branchLabel() = String.format("for (%s %s : %s)", + getVariableDecl().getTypeAccess().prettyPrint(), + getVariableDecl().getID(), + getExpr().prettyPrint()); + eq SwitchStmt.branch().branchLabel() = "switch (" + getExpr().prettyPrint() + ")"; + + inh String CfgMarker.markerName(); + eq BreakStmt.marker().markerName() = "break"; + eq ContinueStmt.marker().markerName() = "continue"; + eq ConditionalExpr.thenEndMarker().markerName() = "then-end"; + eq ConditionalExpr.elseEndMarker().markerName() = "else-end"; + eq IfStmt.thenEndMarker().markerName() = "then-end"; + eq IfStmt.elseEndMarker().markerName() = "else-end"; + eq ReturnStmt.marker().markerName() = "return"; + eq TryStmt.tryEntryMarker().markerName() = "try"; + eq Program.getChild().markerName() = "marker"; + eq Dot.nullableDereferenceMarker().markerName() = "nullable access"; + eq ForStmt.loopEndMarker().markerName() = "for-end"; + eq EnhancedForStmt.loopEndMarker().markerName() = "for-end"; + eq WhileStmt.loopEndMarker().markerName() = "while-end"; + eq DoStmt.doEntryMarker().markerName() = "do-entry"; +} diff --git a/simplecfg/src/main/jastadd/PrintCfgTest.jrag b/simplecfg/src/main/jastadd/PrintCfgTest.jrag new file mode 100644 index 0000000000000000000000000000000000000000..cc881f8bc2e746752bd5a7e8ed88022c5a3c81da --- /dev/null +++ b/simplecfg/src/main/jastadd/PrintCfgTest.jrag @@ -0,0 +1,179 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.util.Set; + +import java.util.Collections; +import java.util.Queue; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Queue; +import java.util.LinkedList; + +/** Attributes useful for generating a test case from a CFG. */ +aspect PrintCfgTest { + + /** Generate a test case from the CFG of this body decl. */ + public void BodyDecl.printCfgTest() { + CfgNode entry = entry(); + String className = hostType().name(); + String testName = className.substring(0, 1).toLowerCase() + + className.substring(1); + System.out.println(" @Test public void " + testName + "() {"); + System.out.println(" CfgNode entry = parseCfg(\"" + className + "\");"); + Queue<CfgNode> work = new LinkedList<CfgNode>(); + Set<CfgNode> visited = Collections.newSetFromMap( + new IdentityHashMap<CfgNode, Boolean>()); + visited.add(entry); + Map<CfgNode, String> vars = new HashMap<CfgNode, String>(); + vars.put(entry, entry.toString()); + Map<String, Integer> nextIds = new HashMap<String, Integer>(); + nextIds.put("entry", 2); + work.add(entry); + while (!work.isEmpty()) { + CfgNode node = work.poll(); + boolean testSuccs = node.printAssert(visited, vars, nextIds); + if (testSuccs) { + for (CfgNode succ : node.successors()) { + if (!visited.contains(succ)) { + visited.add(succ); + work.add(succ); + } + } + } + } + System.out.println(" }"); + } + + /** + * @return {@code true} if successors should be tested. + */ + protected boolean CfgNode.printAssert( + Set<CfgNode> visited, + Map<CfgNode, String> vars, + Map<String, Integer> nextIds) { + Set<? extends CfgNode> successors = successors(); + if (successors.size() == 1) { + CfgNode succ = successors.iterator().next(); + String nodeName = nextVarName(succ.varName(), nextIds); + if (!visited.contains(succ)) { + vars.put(succ, nodeName); + System.out.println(String.format( + " CfgNode %s = succ(%s, \"%s\");", + nodeName, vars.get(this), succ.name())); + } else { + System.out.println(String.format( + " assertThat(succ(%s, \"%s\")).isSameAs(%s);", + vars.get(this), succ.name(), vars.get(succ))); + } + } else if (successors.size() > 1) { + boolean duplicates = false; + Set<String> dups = new HashSet<String>(); + for (CfgNode succ : successors) { + if (dups.contains(succ.name())) { + duplicates = true; + break; + } + dups.add(succ.name()); + } + if (duplicates) { + System.out.print(String.format( + " assertThat(%s.successors()).containsExactly(\"", + vars.get(this))); + boolean first = true; + for (CfgNode succ : successors) { + if (!first) { + System.out.print("\", \""); + } + first = false; + System.out.print(succ.name()); + } + System.out.println("\");"); + System.out.println(String.format( + " // NOTE Code to test the successors of %s was not " + + "auto-generated\n // due to identical successor names.", + vars.get(this))); + return false; + } else { + String arrayName = nextVarName(this.varName() + "Succ", nextIds); + System.out.print(String.format( + " CfgNode[] %s = succ(%s", + arrayName, vars.get(this))); + int index = 0; + Map<String, CfgNode> succMap = new HashMap<String, CfgNode>(); + for (CfgNode succ : successors) { + System.out.format(", \"%s\"", succ.name()); + succMap.put(arrayName + "[" + index + "]", succ); + index += 1; + } + System.out.println(");"); + for (Map.Entry<String, CfgNode> succ : succMap.entrySet()) { + if (!visited.contains(succ.getValue())) { + vars.put(succ.getValue(), succ.getKey()); + } else { + System.out.println(String.format( + " assertThat(%s).isSameAs(%s);", + succ.getKey(), vars.get(succ.getValue()))); + } + } + } + } + return true; + } + + protected static String CfgNode.nextVarName(String name, + Map<String, Integer> nextIds) { + if (nextIds.containsKey(name)) { + int id = nextIds.get(name); + nextIds.put(name, id+1); + return name + id; + } else { + nextIds.put(name, 2); + return name; + } + } + + syn String CfgNode.varName() = name(); + eq CfgBranch.varName() = branchKind() + "Branch"; + eq CfgException.varName() = "exception"; + eq CfgMarker.varName() = markerVarName(); + eq CfgMethodCall.varName() = methodAccess().getID(); + + inh String CfgBranch.branchKind(); + eq IfStmt.branch().branchKind() = "if"; + eq ConditionalExpr.branch().branchKind() = "if"; + eq ForStmt.branch().branchKind() = "for"; + eq WhileStmt.branch().branchKind() = "while"; + eq DoStmt.branch().branchKind() = "doWhile"; + eq EnhancedForStmt.branch().branchKind() = "for"; + eq SwitchStmt.branch().branchKind() = "switch"; + + inh String CfgMarker.markerVarName(); + eq BreakStmt.marker().markerVarName() = "breakMarker"; + eq ContinueStmt.marker().markerVarName() = "continueMarker"; + eq ConditionalExpr.thenEndMarker().markerVarName() = "thenEnd"; + eq ConditionalExpr.elseEndMarker().markerVarName() = "elseEnd"; + eq IfStmt.thenEndMarker().markerVarName() = "thenEnd"; + eq IfStmt.elseEndMarker().markerVarName() = "elseEnd"; + eq ReturnStmt.marker().markerVarName() = "returnMarker"; + eq TryStmt.tryEntryMarker().markerVarName() = "tryEntry"; + eq Program.getChild().markerVarName() = "marker"; + eq Dot.nullableDereferenceMarker().markerVarName() = "nullable"; + eq ForStmt.loopEndMarker().markerVarName() = "forEnd"; + eq EnhancedForStmt.loopEndMarker().markerVarName() = "forEnd"; + eq WhileStmt.loopEndMarker().markerVarName() = "whileEnd"; + eq DoStmt.doEntryMarker().markerVarName() = "doEntry"; +} diff --git a/simplecfg/src/main/jastadd/SimpleCFG.jrag b/simplecfg/src/main/jastadd/SimpleCFG.jrag new file mode 100644 index 0000000000000000000000000000000000000000..162622bb10fa429e861076736a12e726958f7378 --- /dev/null +++ b/simplecfg/src/main/jastadd/SimpleCFG.jrag @@ -0,0 +1,791 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.util.Collection; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.Set; + +/** + * Attributes to build a simplified Control Flow Graph (CFG) for a methods, + * constructors, and initializers. + * + * <p>The simple CFG contains only method calls and branches. The simple CFG has + * a naive exception model - any exception is assumed to be throwable by any + * statement. + */ +aspect SimpleCFG { + + /** @return {@code true} if this node corresponds to a method call. */ + syn boolean CfgNode.isCall() = false; + eq CfgMethodCall.isCall() = true; + + /** @return {@code true} if this node corresponds to an exception branch. */ + syn boolean CfgNode.isException() = false; + eq CfgException.isException() = true; + + /** @return {@code true} if this node corresponds to a branch. */ + syn boolean CfgNode.isBranch() = false; + eq CfgBranch.isBranch() = true; + + /** + * Successor nodes in the CFG. + */ + syn lazy Set<? extends CfgNode> CfgNode.successors(); + + eq CfgEntry.successors() = Collections.singleton(getSucc()); + + eq CfgExit.successors() = Collections.emptySet(); + + eq CfgMethodCall.successors() = succ(); + + eq CfgBranch.successors() = succ(); + + eq CfgException.successors() = succ(); + + eq CfgMarker.successors() = succ(); + + /** + * Set that contains either one or two unique objects. The objects are + * compared with reference equality. + */ + class IdentityTupleSet<E> implements Set<E> { + final E a, b; + + public IdentityTupleSet(E a, E b) { + this.a = a; + this.b = b; + } + + @Override + public boolean add(E e) { + throw new UnsupportedOperationException(); + } + @Override + public boolean addAll(Collection<? extends E> e) { + throw new UnsupportedOperationException(); + } + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + @Override + public boolean contains(Object o) { + return o == a || o == b; + } + @Override + public boolean containsAll(Collection<?> c) { + for (Object o : c) { + if (!contains(o)) { + return false; + } + } + return true; + } + @Override + public int hashCode() { + return a.hashCode() + b.hashCode(); + } + @Override + public boolean isEmpty() { + return false; + } + @Override + public Iterator<E> iterator() { + return new Iterator<E>() { + int index = 0; + @Override + public boolean hasNext() { + return (a == b && index < 1) + || (a != b && index < 2); + } + @Override + public E next() { + return ++index == 1 ? a : b; + } + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + @Override + public boolean removeAll(Collection<?> c) { + throw new UnsupportedOperationException(); + } + @Override + public boolean retainAll(Collection<?> c) { + throw new UnsupportedOperationException(); + } + @Override + public int size() { + return a == b ? 1 : 2; + } + @Override + public Object[] toArray() { + return new Object[] { a, b }; + } + @Override + public <T> T[] toArray(T[] array) { + array[0] = (T) a; + array[1] = (T) b; + return array; + } + @Override + public String toString() { + if (a == b) { + return "[" + a + "]"; + } else { + return "[" + a + ", " + b + "]"; + } + } + } + + /** Successors to this branch node. */ + inh Set<? extends CfgNode> CfgBranch.succ(); + + /** Successors to this method call node. */ + inh Set<? extends CfgNode> CfgMethodCall.succ(); + + inh Set<? extends CfgNode> CfgException.succ(); + + inh Set<? extends CfgNode> CfgMarker.succ(); + + /** Build a small set with two elements. */ + public <T> Set<T> ASTNode.smallSet(T a, T b) { + return new IdentityTupleSet<T>(a, b); + } + + /** + * The entry CFG node of this statement. This is the next method call, + * branch, or exit node of the simplified CFG following the entry of + * this statement. + */ + syn CfgNode Stmt.entry() = follow(); + + /** + * Find the next CFG node representing the next branch, or the next + * method access following this statement. + */ + inh CfgNode Stmt.follow(); + + inh CfgNode Expr.follow(); + + // Needed for completeness, but never used by anything relevant. + eq Program.getChild().follow() = new CfgExit(); + + /** + * The entry node in a filtered CFG. + */ + syn lazy CfgEntry BodyDecl.entry() = new CfgEntry(exit()); + + eq MethodDecl.entry() = + hasBlock() + ? new CfgEntry(getBlock().entry()) + : new CfgEntry(exit()); + + eq ConstructorDecl.entry() = new CfgEntry(getBlock().entry()); + + eq InstanceInitializer.entry() = new CfgEntry(getBlock().entry()); + + eq StaticInitializer.entry() = new CfgEntry(getBlock().entry()); + + /** + * The exit node in a filtered CFG. + */ + syn nta CfgExit BodyDecl.exit() = new CfgExit(); + + eq Block.entry() { + if (getNumStmt() > 0) { + return getStmt(0).entry(); + } else { + return follow(); + } + } + + eq LabeledStmt.entry() = getStmt().entry(); + + eq VarDeclStmt.entry() = + getNumDeclarator() > 0 + ? getDeclarator(0).entry() + : follow(); + + eq VarDeclStmt.getDeclarator(int index).follow() = + index + 1 < getNumDeclarator() + ? getDeclarator(index + 1).entry() + : follow(); + + inh CfgNode VariableDeclarator.follow(); + + syn CfgNode VariableDeclarator.entry() = + hasInit() + ? getInit().entry() + : follow(); + + eq SynchronizedStmt.entry() = getExpr().entry(); + + eq SynchronizedStmt.getExpr().follow() = getBlock().entry(); + + // Note: catch-all clauses get special treatment! + + eq TryStmt.entry() = tryEntryMarker(); + + eq TryWithResources.entry() = + getNumResource() > 0 + ? getResource(0).entry() + : super.entry(); + + eq TryWithResources.getResource(int index).follow() = + index+1 < getNumResource() + ? getResource(index+1).entry() + : super.entry(); + + /** + * The entry of a try statement has a branch to the block and to each + * catch clause or finally block. + */ + syn nta CfgMarker TryStmt.tryEntryMarker() = new CfgMarker(); + + eq TryStmt.tryEntryMarker().succ() = + joinSets(Collections.singleton(getBlock().entry()), catchBranches()); + + eq MethodAccess.exceptionNode().succ() = exceptionBranches(); + + eq ThrowStmt.exceptionNode().succ() = exceptionBranches(); + + /** + * Gives the set of CFG nodes targeted by an exception thrown at this + * statement. This includes the catch blocks of the enclosing try statement as + * well as the finally block. Does not include any targets following a + * catch of Throwable. + */ + inh Set<? extends CfgNode> MethodAccess.exceptionBranches(); + inh Set<? extends CfgNode> TryStmt.exceptionBranches(); + inh Set<? extends CfgNode> ThrowStmt.exceptionBranches(); + + inh TypeDecl TryStmt.typeThrowable(); + + eq TryStmt.getBlock().exceptionBranches() = catchBranches(); + + eq TryWithResources.getResource().exceptionBranches() = catchBranches(); + + /** + * Gives the set of CFG nodes targeted by an exception thrown inside this try + * statement. This includes the catch blocks of this try statements as + * well as the finally block. Does not include any targets following a + * catch of Throwable. + */ + syn Set<? extends CfgNode> TryStmt.catchBranches() { + // Check for catch-all clauses (catching java.lang.Throwable). + Set<CfgNode> set = Collections.newSetFromMap(new IdentityHashMap<CfgNode, Boolean>()); + for (CatchClause clause : getCatchClauseList()) { + set.add(clause.getBlock().entry()); + if (clause instanceof BasicCatch + && ((BasicCatch) clause).getParameter().type() == typeThrowable()) { + // This is a catch-all clause: no other clauses after this can catch an + // exception. + return set; + } + } + if (hasNonEmptyFinally()) { + set.add(getExceptionHandler().entry()); + } + return set; + } + + eq TryStmt.getExceptionHandler().follow() = exceptionNode(); + eq TryStmt.exceptionNode().succ() = exceptionBranches(); + + eq BodyDecl.getChild().exceptionBranches() = Collections.singleton(exit()); + + eq CompilationUnit.getChild().exceptionBranches() = Collections.emptySet(); + eq TypeDecl.getChild().exceptionBranches() = Collections.emptySet(); + + public <U, V extends U> Set<U> Stmt.joinSets(Set<U> a, Set<V> b) { + Set<U> set = Collections.newSetFromMap( + new IdentityHashMap<U, Boolean>()); + set.addAll(a); + set.addAll(b); + return set; + } + + eq TryStmt.getBlock().follow() = + hasNonEmptyFinally() + ? getFinally().entry() + : follow(); + + eq TryStmt.getCatchClause(int index).follow() = + hasNonEmptyFinally() + ? getFinally().entry() + : follow(); + + /** The CFG marker for this break statement. */ + syn nta CfgMarker BreakStmt.marker() = new CfgMarker(); + + eq BreakStmt.entry() = marker(); + eq BreakStmt.marker().succ() = + hasFinally() + ? Collections.singleton(getFinally().entry()) + : Collections.singleton(targetStmt().follow()); + eq BreakStmt.marker().follow() = + hasFinally() + ? getFinally().entry() + : targetStmt().follow(); + + eq BreakStmt.getFinally().follow() = targetStmt().follow(); + + /** The CFG marker for this continue statement. */ + syn nta CfgMarker ContinueStmt.marker() = new CfgMarker(); + + eq ContinueStmt.entry() = marker(); + eq ContinueStmt.marker().succ() = + hasFinally() + ? Collections.singleton(getFinally().entry()) + : Collections.singleton(targetStmt().entry()); + eq ContinueStmt.marker().follow() = + hasFinally() + ? getFinally().entry() + : targetStmt().entry(); + + eq ContinueStmt.getFinally().follow() = targetStmt().follow(); + + /** The CFG marker for this continue statement. */ + syn nta CfgMarker ReturnStmt.marker() = new CfgMarker(); + + eq ReturnStmt.entry() = marker(); + eq ReturnStmt.marker().succ() = + hasResult() + ? Collections.singleton(getResult().entry()) + : Collections.singleton(returnTarget()); + eq ReturnStmt.marker().follow() = + hasResult() + ? getResult().entry() + : returnTarget(); + + eq ReturnStmt.getResult().follow() = returnTarget(); + + inh CompilationUnit ReturnStmt.compilationUnit(); + + syn CfgNode ReturnStmt.returnTarget() = + hasFinally() + ? getFinally().entry() + : methodExit(); + + eq ReturnStmt.getFinally().follow() = methodExit(); + + /** + * Finds the CFG exit node for the enclosing method, constructor, or + * initializer. + */ + inh CfgNode ReturnStmt.methodExit(); + + eq BodyDecl.getChild().methodExit() = exit(); + + eq LambdaBody.getChild().methodExit() = exit(); + + eq ThrowStmt.entry() = getExpr().entry(); + + // Since we not have precise type lookups we need to approximate the possible + // exception branches. + eq ThrowStmt.getExpr().follow() = exceptionNode(); + + /** + * This node represents the control flow path taken when an exception + * interrupts the call. + */ + syn nta CfgException MethodAccess.exceptionNode() = new CfgException(); + + /** This node represents the control flow following the exception. */ + syn nta CfgException ThrowStmt.exceptionNode() = new CfgException(); + + /** + * This node represents the control flow path taken if an exception + * is thrown in the start of a try statement. + */ + syn nta CfgException TryStmt.exceptionNode() = new CfgException(); + + eq ExprStmt.entry() = getExpr().entry(); + + syn CfgNode Expr.entry(); + + eq ArrayAccess.entry() = getExpr().entry(); + + // All of these nodes are uninteresting for the simple CFG. + // Setting entry() = follow() makes sure the node is not included in the CFG. + eq AbstractWildcard.entry() = follow(); + eq ClassAccess.entry() = follow(); + eq DiamondAccess.entry() = follow(); + eq PackageAccess.entry() = follow(); + eq ParseName.entry() = follow(); // Always rewritten. + eq ParTypeAccess.entry() = follow(); + eq SuperAccess.entry() = follow(); + eq ThisAccess.entry() = follow(); + eq VarAccess.entry() = follow(); + eq TypeAccess.entry() = follow(); + eq SyntheticTypeAccess.entry() = follow(); + eq ClassReference.entry() = follow(); + eq ArrayReference.entry() = follow(); + eq LambdaExpr.entry() = follow(); + eq TypeMethodReference.entry() = follow(); + eq AmbiguousMethodReference.entry() = follow(); + + eq ParExpr.entry() = getExpr().entry(); + eq CastExpr.entry() = getExpr().entry(); + eq IntersectionCastExpr.entry() = getExpr().entry(); + eq Unary.entry() = getOperand().entry(); + + eq ExprMethodReference.entry() = getExpr().entry(); + + /** The method call node for this method access. */ + syn nta CfgMethodCall MethodAccess.call() = new CfgMethodCall(); + + eq MethodAccess.call().succ() = + isInsideTryBlockOrResource() + ? smallSet(exceptionNode(), follow()) + : Collections.singleton(follow()); + + // If there are arguments control flow passes to the arguments first. + eq MethodAccess.entry() = + getNumArg() > 0 + ? getArg(0).entry() + : call(); + + // If we have arguments the CfgMethodCall is placed after the last argument. + eq MethodAccess.getArg(int index).follow() = + index+1 < getNumArg() + ? getArg(index+1).entry() + : call(); + + // If there are arguments control flow passes to the arguments first. + eq ConstructorAccess.entry() = + getNumArg() > 0 + ? getArg(0).entry() + : follow(); + + // If we have arguments the CfgMethodCall is placed after the last argument. + eq ConstructorAccess.getArg(int index).follow() = + index+1 < getNumArg() + ? getArg(index+1).entry() + : follow(); + + eq ClassInstanceExpr.entry() = + getNumArg() > 0 + ? getArg(0).entry() + : getAccess().entry(); + + eq ClassInstanceExpr.getArg(int index).follow() = + index+1 < getNumArg() + ? getArg(index+1).entry() + : getAccess().entry(); + + eq ArrayInit.entry() = + getNumInit() > 0 + ? getInit(0).entry() + : follow(); + + eq ArrayInit.getInit(int index).follow() = + index+1 < getNumInit() + ? getInit(index+1).entry() + : follow(); + + /** @return {@code true} if this statement is inside a try block. */ + inh boolean MethodAccess.isInsideTryBlockOrResource(); + inh boolean Stmt.isInsideTryBlockOrResource(); + + eq CompilationUnit.getChild().isInsideTryBlockOrResource() = false; + eq TypeDecl.getChild().isInsideTryBlockOrResource() = false; + eq BodyDecl.getChild().isInsideTryBlockOrResource() = false; + eq LambdaBody.getChild().isInsideTryBlockOrResource() = false; + eq TryStmt.getBlock().isInsideTryBlockOrResource() = true; + eq TryWithResources.getResource().isInsideTryBlockOrResource() = true; + eq NTAFinallyBlock.getChild().isInsideTryBlockOrResource() = + origin.getFinallyBlock().isInsideTryBlockOrResource(); + eq Program.getChild().isInsideTryBlockOrResource() = false; + + eq InstanceOfExpr.entry() = getExpr().entry(); + + eq AssignExpr.entry() = getSource().entry(); + + eq AssignExpr.getSource().follow() = getDest().entry(); + + eq Literal.entry() = follow(); + + eq ArrayCreationExpr.entry() = + hasArrayInit() + ? getArrayInit().entry() + : follow(); + + eq Binary.entry() = getLeftOperand().entry(); + eq Binary.getLeftOperand().follow() = getRightOperand().entry(); + + eq Dot.entry() = getLeft().entry(); + eq Dot.getLeft().follow() = getRight().entry(); + + /** The branch node for this conditional expression. */ + syn nta CfgBranch ConditionalExpr.branch() = new CfgBranch(); + + /** + * The then-end node is a marker node marking the end of a then-branch in a conditional + * expression. + */ + syn nta CfgMarker ConditionalExpr.thenEndMarker() = new CfgMarker(); + + /** + * The else-end node is a marker node marking the end of a else-branch in a conditional + * expression. + */ + syn nta CfgMarker ConditionalExpr.elseEndMarker() = new CfgMarker(); + + eq ConditionalExpr.entry() = getCondition().entry(); + eq ConditionalExpr.getCondition().follow() = branch(); + eq ConditionalExpr.getTrueExpr().follow() = thenEndMarker(); + eq ConditionalExpr.getFalseExpr().follow() = elseEndMarker(); + eq ConditionalExpr.thenEndMarker().follow() = follow(); + eq ConditionalExpr.elseEndMarker().follow() = follow(); + eq ConditionalExpr.thenEndMarker().succ() = Collections.singleton(follow()); + eq ConditionalExpr.elseEndMarker().succ() = Collections.singleton(follow()); + + eq ConditionalExpr.branch().succ() = + smallSet(getTrueExpr().entry(), getFalseExpr().entry()); + + /** The branch node for this statement. */ + syn nta CfgBranch IfStmt.branch() = new CfgBranch(); + + /** The then-end node is a marker node marking the end of a then-branch in an if statement. */ + syn nta CfgMarker IfStmt.thenEndMarker() = new CfgMarker(); + + /** The else-end node is a marker node marking the end of a else-branch in an if statement. */ + syn nta CfgMarker IfStmt.elseEndMarker() = new CfgMarker(); + + eq IfStmt.entry() = getCondition().entry(); + + eq IfStmt.getCondition().follow() = branch(); + eq IfStmt.getThen().follow() = thenEndMarker(); + eq IfStmt.getElse().follow() = elseEndMarker(); + eq IfStmt.thenEndMarker().follow() = follow(); + eq IfStmt.elseEndMarker().follow() = follow(); + eq IfStmt.thenEndMarker().succ() = Collections.singleton(follow()); + eq IfStmt.elseEndMarker().succ() = Collections.singleton(follow()); + + eq IfStmt.branch().succ() = + hasElse() + ? smallSet(getThen().entry(), getElse().entry()) + : smallSet(getThen().entry(), follow()); + + /** The branch node for this statement. */ + syn nta CfgBranch ForStmt.branch() = new CfgBranch(); + + /** The CFG end marker for this loop. */ + syn nta CfgMarker ForStmt.loopEndMarker() = new CfgMarker(); + + eq ForStmt.entry() = + getNumInitStmt() > 0 + ? getInitStmt(0).entry() + : getCondition().entry(); + + eq ForStmt.getInitStmt(int index).follow() = + index+1 < getNumInitStmt() + ? getInitStmt(index+1).entry() + : getCondition().entry(); + + eq ForStmt.getCondition().follow() = branch(); + + eq ForStmt.getUpdateStmt(int index).follow() = + index+1 < getNumUpdateStmt() + ? getUpdateStmt(index+1).entry() + : getCondition().entry(); + + eq ForStmt.getStmt().follow() = loopEndMarker(); + + eq ForStmt.loopEndMarker().follow() = + getNumUpdateStmt() > 0 + ? getUpdateStmt(0).entry() + : getCondition().entry(); + + eq ForStmt.loopEndMarker().succ() = + getNumUpdateStmt() > 0 + ? Collections.singleton(getUpdateStmt(0).entry()) + : Collections.singleton(getCondition().entry()); + + eq ForStmt.branch().succ() { + if (getCondition().isTrue()) { + return Collections.singleton(getStmt().entry()); + } else if (getCondition().isFalse()) { + return Collections.singleton(follow()); + } else { + return smallSet(getStmt().entry(), follow()); + } + } + + /** The branch node for this statement. */ + syn nta CfgBranch EnhancedForStmt.branch() = new CfgBranch(); + + /** The CFG end marker for this loop. */ + syn nta CfgMarker EnhancedForStmt.loopEndMarker() = new CfgMarker(); + + eq EnhancedForStmt.branch().succ() = + smallSet(getStmt().entry(), follow()); + + eq EnhancedForStmt.entry() = getExpr().entry(); + + eq EnhancedForStmt.getExpr().follow() = branch(); + + eq EnhancedForStmt.getStmt().follow() = loopEndMarker(); + + eq EnhancedForStmt.loopEndMarker().follow() = entry(); // Loop back. + + eq EnhancedForStmt.loopEndMarker().succ() = Collections.singleton(entry()); + + /** The branch node for this statement. */ + syn nta CfgBranch WhileStmt.branch() = new CfgBranch(); + + /** The CFG end marker for this loop. */ + syn nta CfgMarker WhileStmt.loopEndMarker() = new CfgMarker(); + + eq WhileStmt.entry() = getCondition().entry(); + + eq WhileStmt.getCondition().follow() = branch(); + + eq WhileStmt.getStmt().follow() = loopEndMarker(); + + eq WhileStmt.loopEndMarker().follow() = entry(); // Loop back. + + eq WhileStmt.loopEndMarker().succ() = Collections.singleton(entry()); + + eq WhileStmt.branch().succ() { + if (getCondition().isTrue()) { + return Collections.singleton(getStmt().entry()); + } else if (getCondition().isFalse()) { + return Collections.singleton(follow()); + } else { + return smallSet(getStmt().entry(), follow()); + } + } + + /** The branch node for this statement. */ + syn nta CfgBranch DoStmt.branch() = new CfgBranch(); + + /** The CFG entry marker for this loop. */ + syn nta CfgMarker DoStmt.doEntryMarker() = new CfgMarker(); + + eq DoStmt.entry() = doEntryMarker(); + + eq DoStmt.doEntryMarker().follow() = getStmt().entry(); + + eq DoStmt.doEntryMarker().succ() = Collections.singleton(getStmt().entry()); + + eq DoStmt.getStmt().follow() = getCondition().entry(); + + eq DoStmt.getCondition().follow() = branch(); + + // Loop back. + eq DoStmt.branch().succ() { + if (getCondition().isTrue()) { + return Collections.singleton(entry()); + } else if (getCondition().isFalse()) { + return Collections.singleton(follow()); + } else { + return smallSet(entry(), follow()); + } + } + + syn nta CfgBranch SwitchStmt.branch() = new CfgBranch(); + + eq SwitchStmt.entry() = getExpr().entry(); + + eq SwitchStmt.getExpr().follow() = branch(); + + eq SwitchStmt.branch().succ() { + Set<CfgNode> set = Collections.newSetFromMap( + new IdentityHashMap<CfgNode, Boolean>()); + boolean hasDefault = false; + for (Stmt stmt : getBlock().getStmtList()) { + if (stmt instanceof Case) { + set.add(stmt.entry()); + if (stmt instanceof DefaultCase) { + hasDefault = true; + } + } + } + if (!hasDefault) { + set.add(follow()); + } + return set; + } + + eq BodyDecl.getChild().follow() = exit(); + + eq Block.getStmt(int index).follow() = + index+1 < getNumStmt() + ? getStmt(index+1).entry() + : follow(); + + syn lazy CfgEntry LambdaBody.entry(); + eq BlockLambdaBody.entry() = new CfgEntry(getBlock().entry()); + eq ExprLambdaBody.entry() = new CfgEntry(getExpr().entry()); + + syn nta CfgExit LambdaBody.exit() = new CfgExit(); + + eq BlockLambdaBody.getBlock().follow() = exit(); + eq ExprLambdaBody.getExpr().follow() = exit(); + + /** Find the method access which this call node is associated with. */ + inh MethodAccess CfgMethodCall.methodAccess(); + eq MethodAccess.call().methodAccess() = this; + + /** Find the entry node to the CFG this statement belongs to. */ + inh lazy CfgEntry Stmt.cfg(); + inh lazy CfgEntry Expr.cfg(); + + eq BodyDecl.getChild().cfg() = entry(); + eq LambdaBody.getChild().cfg() = entry(); + + eq CompilationUnit.getChild().cfg() { + throw new Error("Not in a CFG."); + } + + /** + * Predecessors of this node in the CFG. Predecessors are filled in when + * accessing the CFG through the cfg() attribute. + */ + protected Collection<CfgNode> CfgNode.predecessors = + new LinkedList<CfgNode>(); + + private boolean CfgEntry.initializedPredecessors = false; + + /** Initializes the predecessor sets for each node in a CFG. */ + protected void CfgEntry.initPredecessors() { + if (!initializedPredecessors) { + initializedPredecessors = true; + Queue<CfgNode> queue = new LinkedList<CfgNode>(); + queue.add(this); + while (!queue.isEmpty()) { + CfgNode node = queue.poll(); + for (CfgNode succ : node.successors()) { + if (succ.predecessors.isEmpty()) { + queue.add(succ); + } + succ.predecessors.add(node); + } + } + } + } +} diff --git a/simplecfg/src/main/jastadd/VariableDeclarationScope.jrag b/simplecfg/src/main/jastadd/VariableDeclarationScope.jrag new file mode 100644 index 0000000000000000000000000000000000000000..653d51169e81577c0611a9970b4c60505790cf36 --- /dev/null +++ b/simplecfg/src/main/jastadd/VariableDeclarationScope.jrag @@ -0,0 +1,75 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This aspect adds attributes to check if a variable was declared inside an a statement represented + * by a particular CFG node. + */ +aspect VariableDeclarationScope { + + /** @return {@code true} if this method access has the given variable as receiver. */ + syn boolean MethodAccess.hasReceiver(Variable receiver) = + hasPrevExpr() && prevExpr().isVariable(receiver); + + /** + * Test if the CFG node is tied to a statement that declares the variable, + * or if the declaration of the variable is somewhere inside the statement this + * branch represents. + */ + syn boolean CfgNode.isDeclarationOf(Variable var) = false; + + eq CfgBranch.isDeclarationOf(Variable var) = + branchDeclaresVariable(var) + || variableDeclaredInsideStatement(var); + + syn boolean CfgBranch.variableDeclaredInsideStatement(Variable var) { + Stmt stmt = hostStatement(); + ASTNode node = (ASTNode) var; + while (node != stmt && node != null) { + node = node.getParent(); + } + return node == stmt; + } + + /** The statement this branch represents. */ + inh Stmt CfgBranch.hostStatement(); + + /** The statement this expression is part of. */ + inh Stmt Expr.hostStatement(); + eq Stmt.getChild().hostStatement() = this; + eq Program.getChild().hostStatement() = null; + + eq IfStmt.branch().hostStatement() = this; + eq ConditionalExpr.branch().hostStatement() = hostStatement(); + eq ForStmt.branch().hostStatement() = this; + eq WhileStmt.branch().hostStatement() = this; + eq DoStmt.branch().hostStatement() = this; + eq EnhancedForStmt.branch().hostStatement() = this; + eq SwitchStmt.branch().hostStatement() = this; + + /** Test if the CFG node is tied to a statement that declares the variable. */ + inh boolean CfgBranch.branchDeclaresVariable(Variable var); + + eq EnhancedForStmt.branch().branchDeclaresVariable(Variable var) = + getVariableDecl() == var; + eq IfStmt.branch().branchDeclaresVariable(Variable var) = false; + eq ConditionalExpr.branch().branchDeclaresVariable(Variable var) = false; + eq ForStmt.branch().branchDeclaresVariable(Variable var) = false; + eq WhileStmt.branch().branchDeclaresVariable(Variable var) = false; + eq DoStmt.branch().branchDeclaresVariable(Variable var) = false; + eq SwitchStmt.branch().branchDeclaresVariable(Variable var) = false; + +} diff --git a/simplecfg/src/main/java/com/google/simplecfg/ExtendJAnalyzerFrontend.java b/simplecfg/src/main/java/com/google/simplecfg/ExtendJAnalyzerFrontend.java new file mode 100644 index 0000000000000000000000000000000000000000..fb9ad9d1fbae37eb5cc851e2b5f80028bf53fa0e --- /dev/null +++ b/simplecfg/src/main/java/com/google/simplecfg/ExtendJAnalyzerFrontend.java @@ -0,0 +1,155 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.simplecfg; + +import org.extendj.ast.*; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; + +/** + * Produces findings using analyzers implemented in the ExtendJ compiler. + */ +public class ExtendJAnalyzerFrontend extends Frontend { + + private final JavaParser javaParser; + private final BytecodeReader bytecodeReader; + private final Collection<ExtendJFinding> findings = new ArrayList<ExtendJFinding>(); + + /** Create new analyzer instance. */ + public ExtendJAnalyzerFrontend() { + super("ExtendJ Analyzer", "v1.0"); + javaParser = new JavaParser() { + @Override + public CompilationUnit parse(InputStream is, String fileName) + throws IOException, beaver.Parser.Exception { + return new org.extendj.parser.JavaParser().parse(is, fileName); + } + }; + bytecodeReader = new BytecodeReader() { + @Override + public CompilationUnit read(InputStream is, String fullName, Program p) + throws FileNotFoundException, IOException { + return new BytecodeParser(is, fullName).parse(null, null, p); + } + }; + } + + /** + * Analyze a single file for findings and return the findings in a collection. + */ + public static Collection<ExtendJFinding> analyzeFile(final String path) throws Error { + ExtendJAnalyzerFrontend checker = new ExtendJAnalyzerFrontend(); + int result = checker.run(new String[]{path}); + if (result != EXIT_SUCCESS) { + throw new Error("exit code: " + result); + } + return checker.findings; + } + + /** + * Returns the list of findings from the analyzed source files. + * + * <p>Used by ExtendJAnalyzerMain to print the generated findings on stdout. + */ + Collection<ExtendJFinding> getFindings() { + return findings; + } + + /** + * Run the Java checker. + * @param args command-line arguments + * @return 0 on success, 1 on error, 2 on configuration error, 3 on system + */ + public int run(String args[]) { + return run(args, bytecodeReader, javaParser); + } + + @Override + protected int processCompilationUnit(CompilationUnit unit) { + if (unit.fromSource()) { + findings.addAll(unit.findings()); + } + return EXIT_SUCCESS; + } + + @Override + public int run(String[] args, BytecodeReader reader, JavaParser parser) { + program.resetStatistics(); + program.setTypeLookupFilter(Program.ANALYZER_TYPE_FILTER); + program.initBytecodeReader(bytecodeReader); + program.initJavaParser(javaParser); + + initOptions(); + int argResult = processArgs(args); + if (argResult != 0) { + return argResult; + } + + if (program.options().hasOption("-version")) { + printVersion(); + return EXIT_SUCCESS; + } + + Collection<String> files = program.options().files(); + if (program.options().hasOption("-help") || files.isEmpty()) { + printUsage(); + return EXIT_SUCCESS; + } + + return run(files); + } + + private int run(Collection<String> files) { + try { + for (String file : files) { + // Calling addSourceFile will parse the file and add it to the program AST. + program.addSourceFile(file); + } + + // Process source compilation units. + int compileResult = EXIT_SUCCESS; + + Iterator<CompilationUnit> iter = program.compilationUnitIterator(); + while (iter.hasNext()) { + CompilationUnit unit = iter.next(); + int result = processCompilationUnit(unit); + if (result != EXIT_SUCCESS) { + compileResult = result; + if (compileResult == EXIT_UNHANDLED_ERROR) { + // Stop immediately when an unhandled error is encountered. + return compileResult; + } + } + } + + if (compileResult != EXIT_SUCCESS) { + return compileResult; + } + } catch (IOException e) { + throw new Error(e); + } finally { + if (program.options().hasOption("-profile")) { + program.printStatistics(System.out); + } + } + return EXIT_SUCCESS; + } +} diff --git a/simplecfg/src/main/java/com/google/simplecfg/ExtendJAnalyzerMain.java b/simplecfg/src/main/java/com/google/simplecfg/ExtendJAnalyzerMain.java new file mode 100644 index 0000000000000000000000000000000000000000..78c7422d7134857755ddb5d67d85107cab81ddf7 --- /dev/null +++ b/simplecfg/src/main/java/com/google/simplecfg/ExtendJAnalyzerMain.java @@ -0,0 +1,40 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.simplecfg; + +import org.extendj.ast.ExtendJFinding; + +/** + * Produces findings using analyzers implemented in the ExtendJ compiler. + */ +public class ExtendJAnalyzerMain { + + /** + * Run the ExtendJ analyzer on the files supplied on the command line. + * @param args command-line arguments + */ + public static void main(String[] args) { + ExtendJAnalyzerFrontend checker = new ExtendJAnalyzerFrontend(); + int result = checker.run(args); + if (result != 0) { + System.exit(result); + } + System.out.println("Found " + checker.getFindings().size() + " findings."); + for (ExtendJFinding finding : checker.getFindings()) { + System.out.println(finding); + } + } +} diff --git a/simplecfg/src/main/java/com/google/simplecfg/PrintCfg.java b/simplecfg/src/main/java/com/google/simplecfg/PrintCfg.java new file mode 100644 index 0000000000000000000000000000000000000000..31c69ffe3e6f2815fd33a1c121c48c5ce7e669d0 --- /dev/null +++ b/simplecfg/src/main/java/com/google/simplecfg/PrintCfg.java @@ -0,0 +1,75 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.simplecfg; + +import org.extendj.ast.BodyDecl; +import org.extendj.ast.CompilationUnit; +import org.extendj.ast.Program; +import org.extendj.ast.TypeDecl; +import org.extendj.parser.JavaParser; + +import java.io.FileInputStream; +import java.util.HashSet; +import java.util.Set; + +/** + * Prints a Simplified Control Flow Graph for the first method in a Java program. + */ +public class PrintCfg { + + public static void main(String args[]) { + int exitCode = new PrintCfg().run(args); + if (exitCode != 0) { + System.exit(exitCode); + } + } + + private int run(String args[]) { + Set<String> argSet = new HashSet<>(); + for (String arg : args) { + argSet.add(arg); + } + boolean reverse = argSet.contains("-reverse"); + for (String path : args) { + if (!path.equals("-reverse")) { + try { + Program program = new Program(); + program.setTypeLookupFilter(Program.BASE_LIBRARY_FILTER); + CompilationUnit unit = new JavaParser().parse(new FileInputStream(path), path); + // Attach the parsed unit to a program node so we have a healthy AST. + program.addCompilationUnit(unit); + // Ensure compilation unit is set to final. This is important to get + // caching to work right in the AST. + unit = program.getCompilationUnit(0); + for (TypeDecl type : unit.getTypeDeclList()) { + for (BodyDecl bd : type.getBodyDeclList()) { + if (reverse) { + bd.printReverseCfg(); + } else { + bd.printCfg(); + } + } + } + } catch (Exception e) { + System.err.println("Failed to parse input file: " + path); + e.printStackTrace(); + return 1; + } + } + } + return 0; + } +} diff --git a/simplecfg/src/main/java/com/google/simplecfg/TestGenerator.java b/simplecfg/src/main/java/com/google/simplecfg/TestGenerator.java new file mode 100644 index 0000000000000000000000000000000000000000..83172eaf4b0b76d19af80af6476ec4fabb661db7 --- /dev/null +++ b/simplecfg/src/main/java/com/google/simplecfg/TestGenerator.java @@ -0,0 +1,66 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.simplecfg; + +import org.extendj.ast.BodyDecl; +import org.extendj.ast.CompilationUnit; +import org.extendj.ast.Program; +import org.extendj.ast.TypeDecl; +import org.extendj.parser.JavaParser; + +import java.io.FileInputStream; + +/** Generate test cases for the first CFG of each input class. */ +class TestGenerator { + + public static void main(String args[]) { + int exitCode = new TestGenerator().run(args); + if (exitCode != 0) { + System.exit(exitCode); + } + } + + private int run(String args[]) { + for (String path : args) { + try { + Program program = new Program(); + program.setTypeLookupFilter(Program.BASE_LIBRARY_FILTER); + CompilationUnit unit = new JavaParser().parse(new FileInputStream(path), path); + // Attach the parsed unit to a program node so we have a healthy AST. + program.addCompilationUnit(unit); + // Ensure compilation unit is set to final. This is important to get + // caching to work right in the AST. + unit = program.getCompilationUnit(0); + if (unit.getNumTypeDecl() < 1) { + System.err.println("Error: no classes declared in file " + path); + return 1; + } + TypeDecl type = unit.getTypeDecl(0); + if (type.getNumBodyDecl() < 1) { + System.err.println("Error: first class has no body decls in file " + path); + return 1; + } + BodyDecl bd = type.getBodyDecl(0); + bd.printCfgTest(); + } catch (Exception e) { + System.err.println("Failed to parse input file: " + path); + e.printStackTrace(); + return 1; + } + } + return 0; + } +} diff --git a/simplecfg/src/test/java/com/google/simplecfg/AlreadyClosedTest.java b/simplecfg/src/test/java/com/google/simplecfg/AlreadyClosedTest.java new file mode 100644 index 0000000000000000000000000000000000000000..687f228b68c4c4335c15c8c051b8c1b9ccac326d --- /dev/null +++ b/simplecfg/src/test/java/com/google/simplecfg/AlreadyClosedTest.java @@ -0,0 +1,64 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.simplecfg; + +import static com.google.common.truth.Truth.assertThat; + +import org.extendj.ast.Program; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.Collection; + +/** Integration tests for the already-closed checker. */ +@RunWith(JUnit4.class) +public class AlreadyClosedTest { + + @Test public void test01() { + Collection<String> findings = StmtCfgTest.findings("Close01", Program.NO_TYPE_FILTER); + assertThat(findings).containsExactly( + "testdata/Close01.javax:23:5: close() may have already been called on writer at this point"); + } + + /** + * Test that an already-closed finding was generated on the correct line for a simple positive + * test case. + * + * <p>This test case effectively checks that the type analysis works because the type used is + * java.io.Writer, and the analyzer will check if that type is a subtype of java.io.Closeable. + */ + @Test public void writer01() { + Collection<String> findings = StmtCfgTest.findings("AlreadyClosedWriter01", + Program.NO_TYPE_FILTER); + assertThat(findings).hasSize(1); + assertThat(findings).containsExactly( + "testdata/AlreadyClosedWriter01.javax:27:5: close() may have already been called on writer at this point"); + } + + @Test public void controlFlow01() { + Collection<Integer> lines = StmtCfgTest.findingLines("AlreadyClosedControlFlow01", + Program.NO_TYPE_FILTER); + assertThat(lines).containsExactly(34, 60, 68, 79, 84, 103, 118); + } + + @Test public void negativeFindings01() { + Collection<String> findings = StmtCfgTest.findings("AlreadyClosedNegativeFindings01", + Program.NO_TYPE_FILTER); + assertThat(findings).isEmpty(); + } +} diff --git a/simplecfg/src/test/java/com/google/simplecfg/NullableDereferenceTest.java b/simplecfg/src/test/java/com/google/simplecfg/NullableDereferenceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5d49861b32151b1edd3f9f983d51712fda724839 --- /dev/null +++ b/simplecfg/src/test/java/com/google/simplecfg/NullableDereferenceTest.java @@ -0,0 +1,145 @@ +/** + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.simplecfg; + +import static com.google.common.truth.Truth.assertThat; + +import org.extendj.ast.CompilationUnit; +import org.extendj.ast.ExtendJFinding; +import org.extendj.ast.Program; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.Collection; + +/** + * Integration tests for the nullable dereference checker. + * + * <p>Tests are grouped by the type of null guard used, and then split arbitrarily into separate + * tests/files in order to not have too many positive/negative finding tests in a single test file. + */ +@RunWith(JUnit4.class) +public class NullableDereferenceTest { + + @Test public void suggestedFixEndsWithNewline() { + CompilationUnit unit = StmtCfgTest.parseFile("NullableNullGuard01", + Program.ANALYZER_TYPE_FILTER); + Collection<ExtendJFinding> findings = unit.findings(); + assertThat(findings).isNotEmpty(); + ExtendJFinding finding = findings.iterator().next(); + assertThat(finding.fixes).hasSize(1); + assertThat(finding.fixes.iterator().next().newText).endsWith("\n"); + } + + @Test public void nullGuards01() { + Collection<String> findings = StmtCfgTest.findings("NullableNullGuard01"); + assertThat(findings).containsExactly( + "testdata/NullableNullGuard01.javax:42:25: Dereferencing p, which was declared @Nullable.", + "testdata/NullableNullGuard01.javax:49:12: Dereferencing p, which was declared @Nullable.", + "testdata/NullableNullGuard01.javax:62:12: Dereferencing p, which was declared @Nullable.", + "testdata/NullableNullGuard01.javax:93:12: Dereferencing q, which was declared @Nullable." + ); + } + + @Test public void nullGuards02() { + Collection<String> findings = StmtCfgTest.findings("NullableNullGuard02"); + assertThat(findings).containsExactly( + "testdata/NullableNullGuard02.javax:49:7: Dereferencing p, which was declared @Nullable.", + "testdata/NullableNullGuard02.javax:54:7: Dereferencing p, which was declared @Nullable." + ); + } + + @Test public void nullGuards03() { + Collection<Integer> lines = StmtCfgTest.findingLines("NullableNullGuard03", + Program.ANALYZER_TYPE_FILTER); + assertThat(lines).containsExactly(28, 34, 41, 48, 113, 119); + } + + @Test public void methodNullGuard01() { + Collection<String> findings = StmtCfgTest.findings("NullableMethodNullGuard01"); + assertThat(findings).isEmpty(); + } + + @Test public void dataflow01() { + Collection<String> findings = StmtCfgTest.findings("NullableDataflow01"); + assertThat(findings).containsExactly( + "testdata/NullableDataflow01.javax:27:7: Dereferencing p, which was declared @Nullable.", + "testdata/NullableDataflow01.javax:35:7: Dereferencing p, which was declared @Nullable." + ); + } + + @Test public void instanceOf() { + Collection<String> findings = StmtCfgTest.findings("NullableInstanceOf"); + assertThat(findings).isEmpty(); + } + + @Test public void variableArity() { + Collection<String> findings = StmtCfgTest.findings("NullableVariableArity"); + assertThat(findings).isEmpty(); + } + + @Test public void nullableDereference01() { + Collection<String> findings = StmtCfgTest.findings("NullableDereference01"); + assertThat(findings).containsExactly( + "testdata/NullableDereference01.javax:27:12: Dereferencing p, which was declared @Nullable.", + "testdata/NullableDereference01.javax:31:12: Dereferencing p, which was declared @Nullable." + ); + } + + /** Test false positive for GitHub issue #10. */ + @Test public void issue10() { + Collection<String> findings = StmtCfgTest.findings("NullableDereferenceIssue10"); + assertThat(findings).containsExactly( + "testdata/NullableDereferenceIssue10.javax:34:31: Dereferencing y, which was declared @Nullable." + ); + } + + @Test public void issue11() { + Collection<String> findings = StmtCfgTest.findings("NullableDereferenceIssue11"); + assertThat(findings).isEmpty(); + } + + @Test public void issue12() { + Collection<String> findings = StmtCfgTest.findings("NullableDereferenceIssue12"); + assertThat(findings).isEmpty(); + } + + @Test public void eqExpr() { + Collection<String> findings = StmtCfgTest.findings("NullableDereferenceEqExpr"); + assertThat(findings).isEmpty(); + } + + @Test public void neExpr() { + Collection<String> findings = StmtCfgTest.findings("NullableDereferenceNeExpr"); + assertThat(findings).isEmpty(); + } + + @Test public void methodCall() { + Collection<String> findings = StmtCfgTest.findings("NullableDereferenceMethodCall"); + assertThat(findings).containsExactly( + "testdata/NullableDereferenceMethodCall.javax:41:16: " + + "Dereferencing p, which was declared @Nullable."); + } + + @Test public void issue13() { + Collection<String> findings = StmtCfgTest.findings("NullableDereferenceIssue13"); + assertThat(findings).containsExactly( + "testdata/NullableDereferenceIssue13.javax:33:7: " + + "Dereferencing obj, which was declared @Nullable."); + } +} diff --git a/simplecfg/src/test/java/com/google/simplecfg/StmtCfgTest.java b/simplecfg/src/test/java/com/google/simplecfg/StmtCfgTest.java new file mode 100644 index 0000000000000000000000000000000000000000..974b17485fea1dffefea174725b1c4ea6303a9e4 --- /dev/null +++ b/simplecfg/src/test/java/com/google/simplecfg/StmtCfgTest.java @@ -0,0 +1,658 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.simplecfg; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; + +import org.extendj.ast.BytecodeParser; +import org.extendj.ast.BytecodeReader; +import org.extendj.ast.CfgNode; +import org.extendj.ast.CompilationUnit; +import org.extendj.ast.ExtendJFinding; +import org.extendj.ast.FileClassSource; +import org.extendj.ast.JavaParser; +import org.extendj.ast.Program; +import org.extendj.ast.SourceFolderPath; +import org.extendj.ast.TypeLookupFilter; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Map; +import java.util.Set; + +/** Tests for simplified Control Flow Graphs built for methods/constructors/initializers. */ +@RunWith(JUnit4.class) +public class StmtCfgTest { + + /** Helper method to parse an ExtendJ compilation unit from a file. */ + protected static CompilationUnit parseFile(String filename, TypeLookupFilter typeFilter) { + String path = "testdata/" + filename + ".javax"; + try { + JavaParser javaParser = new JavaParser() { + @Override + public CompilationUnit parse(java.io.InputStream is, String fileName) + throws IOException, beaver.Parser.Exception { + return new org.extendj.parser.JavaParser().parse(is, fileName); + } + }; + BytecodeReader bytecodeReader = new BytecodeReader() { + @Override + public CompilationUnit read(InputStream is, String fullName, Program p) + throws FileNotFoundException, IOException { + return new BytecodeParser(is, fullName).parse(null, null, p); + } + }; + Program program = new Program(); + program.initBytecodeReader(bytecodeReader); + program.initJavaParser(javaParser); + program.setTypeLookupFilter(typeFilter); + CompilationUnit unit = javaParser.parse(new FileInputStream(path), path); + // Attach the parsed unit to a program node so we have a healthy AST. + program.addCompilationUnit(unit); + // Ensure compilation unit is set to final. This is important to get + // caching to work right in the AST. + unit = program.getCompilationUnit(0); + unit.setClassSource(new FileClassSource(new SourceFolderPath("testdata"), path)); + unit.setFromSource(true); + return unit; + } catch (Exception e) { + e.printStackTrace(); + fail("failed to parse test input file: " + path); + } + // Failed. + return null; + } + + /** Helper to get the findings for a given file. */ + protected static Collection<String> findings(String filename) { + return findings(filename, Program.ANALYZER_TYPE_FILTER); + } + + /** Helper to get the findings for a given file. */ + protected static Collection<String> findings(String filename, TypeLookupFilter typeFilter) { + CompilationUnit unit = StmtCfgTest.parseFile(filename, typeFilter); + Collection<String> findings = new HashSet<String>(); + for (ExtendJFinding finding : unit.findings()) { + findings.add(finding.toString()); + } + return findings; + } + + /** Helper to get the line numbers where findings were reported for a given file. */ + protected static Collection<Integer> findingLines(String filename, TypeLookupFilter typeFilter) { + CompilationUnit unit = StmtCfgTest.parseFile(filename, typeFilter); + Collection<Integer> lines = new LinkedList<>(); + for (ExtendJFinding finding : unit.findings()) { + lines.add(finding.startLine); + } + return lines; + } + + private static CfgNode parseCfg(String filename) { + CompilationUnit unit = parseFile(filename, Program.BASE_LIBRARY_FILTER); + assertThat(unit.getTypeDeclList()).isNotEmpty(); + assertThat(unit.getTypeDecl(0).getBodyDeclList()).isNotEmpty(); + return unit.getTypeDecl(0).getBodyDecl(0).entry(); + } + + /** + * Assert and return a single successor of the node. + */ + private static CfgNode succ(CfgNode node, String successor) { + assertThat(cfgNames(node.successors())).containsExactly(successor); + return node.successors().iterator().next(); + } + + /** + * Assert the successors of the node and return them in an array + * using the same ordering as the input array. + */ + private static CfgNode[] succ(CfgNode node, String... successors) { + assertThat(cfgNames(node.successors())).containsExactly((Object[]) successors); + + // Ensure no duplicate successor names. + Set<String> dups = new HashSet<String>(); + for (String succ : successors) { + if (dups.contains(succ)) { + fail("can not assert successors with duplicate names"); + } + dups.add(succ); + } + + Map<String, CfgNode> successorMap = new HashMap<>(); + for (CfgNode successor : node.successors()) { + successorMap.put(successor.toString(), successor); + } + + CfgNode[] result = new CfgNode[successors.length]; + for (int i = 0; i < successors.length; ++i) { + result[i] = successorMap.get(successors[i]); + } + return result; + } + + /** Convert a collection of CfgNodes to a collection of of the node names. */ + private static Collection<String> cfgNames(Iterable<? extends CfgNode> cfgs) { + // Use a linked list because we need to preserve duplicate names. + // TODO(joqvist): Use Java 8 stream api to map CfgNode -> String. + Collection<String> names = new LinkedList<>(); + for (CfgNode cfg : cfgs) { + names.add(cfg.toString()); + } + return names; + } + + @Test public void ifStmt01() { + CfgNode entry = parseCfg("IfStmt01"); + CfgNode call1 = succ(entry, "call1()"); + CfgNode branch = succ(call1, "if (call1())"); + CfgNode[] targets = succ(branch, "onTrue()", "exit"); + CfgNode thenEnd = succ(targets[0], "then-end"); + assertThat(succ(thenEnd, "exit")).isSameAs(targets[1]); + } + + @Test public void ifStmt02() { + CfgNode entry = parseCfg("IfStmt02"); + CfgNode call1 = succ(entry, "call1()"); + CfgNode branch = succ(call1, "if (call1())"); + CfgNode[] targets = succ(branch, "onTrue()", "onFalse()"); + CfgNode thenEnd = succ(targets[0], "then-end"); + CfgNode elseEnd = succ(targets[1], "else-end"); + assertThat(succ(thenEnd, "exit")).isSameAs(succ(elseEnd, "exit")); + } + + @Test public void ifStmt03() { + CfgNode entry = parseCfg("IfStmt03"); + CfgNode call1 = succ(entry, "call1()"); + CfgNode branch = succ(call1, "if (call1())"); + CfgNode[] targets = succ(branch, "onTrue()", "onFalse()"); + succ(succ(targets[0], "then-end"), "exit"); + succ(targets[1], "call2()"); + } + + @Test public void ifStmt04() { + CfgNode entry = parseCfg("IfStmt04"); + CfgNode call1 = succ(entry, "call1()"); + CfgNode branch = succ(call1, "if (call1())"); + CfgNode[] targets = succ(branch, "onTrue()", "onFalse()"); + succ(targets[0], "call2()"); + succ(succ(targets[1], "else-end"), "exit"); + } + + @Test public void forStmt01() { + CfgNode entry = parseCfg("ForStmt01"); + CfgNode call1 = succ(entry, "call1()"); + CfgNode branch = succ(call1, "for (call1())"); + CfgNode[] targets = succ(branch, "for-end", "exit"); + assertThat(succ(targets[0], "call1()")).isSameAs(call1); + } + + @Test public void forStmt02() { + CfgNode entry = parseCfg("ForStmt02"); + CfgNode call1 = succ(entry, "call1()"); + CfgNode branch = succ(call1, "for (call1())"); + CfgNode[] targets = succ(branch, "call2()", "exit"); + CfgNode forEnd = succ(targets[0], "for-end"); + assertThat(succ(forEnd, "call1()")).isSameAs(call1); + } + + @Test public void forStmt03() { + CfgNode entry = parseCfg("ForStmt03"); + CfgNode init = succ(entry, "init()"); + CfgNode cond = succ(init, "cond()"); + CfgNode branch = succ(cond, "for (cond())"); + CfgNode[] targets = succ(branch, "stmt()", "exit"); + CfgNode stmt = targets[0]; + CfgNode forEnd = succ(stmt, "for-end"); + CfgNode update = succ(forEnd, "update()"); + assertThat(succ(update, "cond()")).isSameAs(cond); + } + + @Test public void forStmt04() { + CfgNode entry = parseCfg("ForStmt04"); + CfgNode i = succ(entry, "i()"); + CfgNode j = succ(i, "j()"); + CfgNode cond = succ(j, "cond()"); + CfgNode branch = succ(cond, "for (cond())"); + CfgNode[] targets = succ(branch, "stmt()", "exit"); + CfgNode stmt = targets[0]; + CfgNode forEnd = succ(stmt, "for-end"); + CfgNode u1 = succ(forEnd, "u1()"); + CfgNode u2 = succ(u1, "u2()"); + CfgNode u3 = succ(u2, "u3()"); + assertThat(succ(u3, "cond()")).isSameAs(cond); + } + + @Test public void forStmt05() { + CfgNode entry = parseCfg("ForStmt05"); + CfgNode i = succ(entry, "i()"); + CfgNode branch = succ(i, "for (false)"); + CfgNode y = succ(branch, "y()"); + succ(y, "exit"); + } + + @Test public void whileStmt01() { + CfgNode entry = parseCfg("WhileStmt01"); + CfgNode branch = succ(entry, "while (true)"); + CfgNode whileEnd = succ(branch, "while-end"); + assertThat(succ(whileEnd, "while (true)")).isSameAs(branch); + } + + @Test public void whileStmt02() { + CfgNode entry = parseCfg("WhileStmt02"); + CfgNode cond = succ(entry, "cond()"); + CfgNode branch = succ(cond, "while (cond())"); + CfgNode[] targets = succ(branch, "while-end", "exit"); + assertThat(succ(targets[0], "cond()")).isSameAs(cond); + } + + @Test public void whileStmt03() { + CfgNode entry = parseCfg("WhileStmt03"); + CfgNode whileBranch = succ(entry, "while (true)"); + CfgNode cond = succ(whileBranch, "cond()"); + CfgNode ifBranch = succ(cond, "if (cond())"); + CfgNode[] whileSucc = succ(ifBranch, "while-end", "break"); + assertThat(succ(whileSucc[0], "while (true)")).isSameAs(whileBranch); + succ(succ(whileSucc[1], "tail()"), "exit"); + } + + @Test public void whileStmt04() { + CfgNode entry = parseCfg("WhileStmt04"); + CfgNode whileBranch = succ(entry, "while (true)"); + CfgNode cond = succ(whileBranch, "cond()"); + CfgNode ifBranch = succ(cond, "if (cond())"); + CfgNode[] ifSucc = succ(ifBranch, "continue", "x()"); + assertThat(succ(ifSucc[0], "while (true)")).isSameAs(whileBranch); + CfgNode y = succ(succ(ifSucc[1], "break"), "y()"); + succ(y, "exit"); + } + + @Test public void whileStmt05() { + CfgNode entry = parseCfg("WhileStmt05"); + CfgNode branch = succ(entry, "while (false)"); + CfgNode y = succ(branch, "y()"); + succ(y, "exit"); + } + + @Test public void doStmt01() { + CfgNode entry = parseCfg("DoStmt01"); + CfgNode doEntry = succ(entry, "do-entry"); + CfgNode x = succ(doEntry, "x()"); + CfgNode y = succ(x, "y()"); + CfgNode branch = succ(y, "do_while (y())"); + CfgNode[] targets = succ(branch, "z()", "do-entry"); + succ(targets[0], "exit"); + assertThat(succ(targets[1], "x()")).isSameAs(x); + } + + @Test public void doStmt02() { + CfgNode entry = parseCfg("DoStmt02"); + CfgNode doEntry = succ(entry, "do-entry"); + CfgNode x = succ(doEntry, "x()"); + CfgNode branch = succ(x, "do_while (false)"); + CfgNode y = succ(branch, "y()"); + succ(y, "exit"); + } + + @Test public void enhancedFor01() { + CfgNode entry = parseCfg("EnhancedFor01"); + CfgNode aList = succ(entry, "aList()"); + CfgNode branch = succ(aList, "for (int a : aList())"); + CfgNode[] targets = succ(branch, "x()", "exit"); + CfgNode forEnd = succ(targets[0], "for-end"); + assertThat(succ(forEnd, "aList()")).isSameAs(aList); + } + + @Test public void methodAccess01() { + CfgNode entry = parseCfg("MethodAccess01"); + CfgNode p1 = succ(entry, "p1()"); + CfgNode p2 = succ(p1, "p2()"); + CfgNode p3 = succ(p2, "p3()"); + CfgNode x = succ(p3, "x()"); + succ(x, "exit"); + } + + @Test public void conditionalExpr01() { + CfgNode entry = parseCfg("ConditionalExpr01"); + CfgNode x = succ(entry, "x()"); + CfgNode branch = succ(x, "if (x())"); + CfgNode[] targets = succ(branch, "y()", "z()"); + CfgNode thenEnd = succ(targets[0], "then-end"); + CfgNode elseEnd = succ(targets[1], "else-end"); + // Assert that the branches converge on exit. + assertThat(succ(thenEnd, "exit")).isSameAs(succ(elseEnd, "exit")); + } + + @Test public void switchStmt01() { + CfgNode entry = parseCfg("SwitchStmt01"); + CfgNode expr = succ(entry, "expr()"); + CfgNode branch = succ(expr, "switch (expr())"); + CfgNode[] targets = succ(branch, "x()", "y()", "z()", "d()"); + assertThat(succ(targets[0], "y()")).isSameAs(targets[1]); + CfgNode exit = succ(succ(targets[1], "break"), "exit"); + assertThat(succ(targets[2], "d()")).isSameAs(targets[3]); + assertThat(succ(targets[3], "exit")).isSameAs(exit); + } + + @Test public void switchStmt02() { + CfgNode entry = parseCfg("SwitchStmt02"); + CfgNode expr = succ(entry, "expr()"); + CfgNode branch = succ(expr, "switch (expr())"); + CfgNode[] targets = succ(branch, "x()", "y()", "z()", "exit"); + assertThat(succ(targets[0], "y()")).isSameAs(targets[1]); + assertThat(succ(succ(targets[1], "break"), "exit")).isSameAs(targets[3]); + assertThat(succ(targets[2], "exit")).isSameAs(targets[3]); + } + + @Test public void tryStmt01() { + CfgNode entry = parseCfg("TryStmt01"); + CfgNode tryBranch = succ(entry, "try"); + CfgNode[] trySucc = succ(tryBranch, "cond()", "x()"); + CfgNode[] condSucc = succ(trySucc[0], "exception", "if (cond())"); + CfgNode x = succ(condSucc[0], "x()"); + assertThat(x).isSameAs(trySucc[1]); + CfgNode exit = succ(succ(x, "exception"), "exit"); + CfgNode[] ifSucc = succ(condSucc[1], "a()", "return"); + CfgNode[] aSucc = succ(ifSucc[0], "exception", "x()"); + assertThat(succ(aSucc[0], "x()")).isSameAs(x); + assertThat(aSucc[1]).isNotSameAs(x); + assertThat(succ(succ(aSucc[1], "y()"), "exit")).isSameAs(exit); + CfgNode x2 = succ(ifSucc[1], "x()"); + assertThat(x2).isNotSameAs(x); + assertThat(succ(x2, "exit")).isSameAs(exit); + } + + @Test public void tryStmt02() { + CfgNode entry = parseCfg("TryStmt02"); + CfgNode tryBranch = succ(entry, "try"); + CfgNode[] tryTargets = succ(tryBranch, "c1()", "c2()", "exit"); + assertThat(succ(tryTargets[0], "exit")).isSameAs(tryTargets[2]); + assertThat(succ(tryTargets[1], "exit")).isSameAs(tryTargets[2]); + } + + @Test public void tryStmt03() { + CfgNode entry = parseCfg("TryStmt03"); + CfgNode tryEntry = succ(entry, "try"); + CfgNode[] targets = succ(tryEntry, "if (condition)", "x()"); + + CfgNode exception = succ(targets[1], "exception"); + CfgNode exit = succ(exception, "exit"); + + CfgNode[] ifSucc = succ(targets[0], "return", "x()"); + assertThat(succ(succ(ifSucc[0], "x()"), "exit")).isSameAs(exit); + assertThat(succ(succ(ifSucc[1], "y()"), "exit")).isSameAs(exit); + } + + @Test public void filtering01() { + CfgNode entry = parseCfg("Filtering01"); + CfgNode i = succ(entry, "i()"); + CfgNode j = succ(i, "j()"); + CfgNode cond = succ(j, "cond()"); + CfgNode forBranch = succ(cond, "for (cond() && c == 3)"); + CfgNode[] forSucc = succ(forBranch, "stmt()", "return"); + CfgNode stmt = forSucc[0]; + CfgNode u1 = succ(succ(stmt, "for-end"), "u1()"); + CfgNode u2 = succ(u1, "u2()"); + CfgNode u3 = succ(u2, "u3()"); + assertThat(succ(u3, "cond()")).isSameAs(cond); + } + + @Test public void throwStmt01() { + CfgNode entry = parseCfg("ThrowStmt01"); + CfgNode x = succ(entry, "x()"); + CfgNode exception = succ(x, "exception"); + succ(exception, "exit"); + } + + // Note: tests should be designed so that there is no need to test duplicate + // successors. Simply insert an extra call at the start of one of the + // branches. + + // Generated tests below here. + // Be extra careful when generating a test: you must manually verify that + // it tests the graph correctly and that the generated graph matches your + // expectations. Watch out for nodes that should be identical but appear to + // be separate: in some cases this is okay but it could also indicate faulty + // caching for NTAs. + + @Test public void genTryStmt01() { + CfgNode entry = parseCfg("GenTryStmt01"); + CfgNode tryEntry = succ(entry, "try"); + CfgNode[] targets = succ(tryEntry, "cond()", "c1()", "c2()"); + CfgNode[] targets2 = succ(targets[0], "exception", "if (cond())"); + CfgNode exit = succ(targets[1], "exit"); + assertThat(succ(targets[2], "exit")).isSameAs(exit); + CfgNode[] targets3 = succ(targets2[0], "c1()", "c2()"); + assertThat(targets3[0]).isSameAs(targets[1]); + assertThat(targets3[1]).isSameAs(targets[2]); + CfgNode[] targets4 = succ(targets2[1], "exception", "x()"); + CfgNode[] targets5 = succ(targets4[0], "c1()", "c2()"); + assertThat(targets5[1]).isSameAs(targets[2]); + assertThat(targets5[0]).isSameAs(targets[1]); + CfgNode[] targets6 = succ(targets4[1], "exception", "exit"); + assertThat(targets6[1]).isSameAs(exit); + CfgNode[] targets7 = succ(targets6[0], "c1()", "c2()"); + assertThat(targets7[0]).isSameAs(targets[1]); + assertThat(targets7[1]).isSameAs(targets[2]); + } + + @Test public void genTryStmt02() { + CfgNode entry = parseCfg("GenTryStmt02"); + CfgNode tryEntry = succ(entry, "try"); + CfgNode[] targets = succ(tryEntry, "tryBlock()", "c2()", "c1()"); + CfgNode[] targets2 = succ(targets[0], "exception", "exit"); + assertThat(succ(targets[1], "exit")).isSameAs(targets2[1]); + assertThat(succ(targets[2], "exit")).isSameAs(targets2[1]); + CfgNode[] targets3 = succ(targets2[0], "c2()", "c1()"); + assertThat(targets3[0]).isSameAs(targets[1]); + assertThat(targets3[1]).isSameAs(targets[2]); + } + + @Test public void genTryStmt03() { + CfgNode entry = parseCfg("GenTryStmt03"); + CfgNode tryEntry = succ(entry, "try"); + CfgNode[] targets = succ(tryEntry, "c1()", "x()"); + CfgNode f = succ(targets[0], "f()"); + CfgNode[] targets2 = succ(targets[1], "exception", "f()"); + assertThat(targets2[1]).isSameAs(f); + CfgNode exit = succ(f, "exit"); + assertThat(succ(targets2[0], "c1()")).isSameAs(targets[0]); + } + + + @Test public void genTryStmt04() { + CfgNode entry = parseCfg("GenTryStmt04"); + CfgNode tryEntry = succ(entry, "try"); + CfgNode[] tryEntrySucc = succ(tryEntry, "if (condition)", "x()"); + CfgNode[] ifBranchSucc = succ(tryEntrySucc[0], "a()", "x()"); + CfgNode exception = succ(tryEntrySucc[1], "exception"); + CfgNode[] aSucc = succ(ifBranchSucc[0], "exception", "return"); + CfgNode y = succ(ifBranchSucc[1], "y()"); + CfgNode exit = succ(exception, "exit"); + assertThat(succ(aSucc[0], "x()")).isSameAs(tryEntrySucc[1]); + CfgNode x2 = succ(aSucc[1], "x()"); + assertThat(succ(y, "exit")).isSameAs(exit); + assertThat(succ(x2, "exit")).isSameAs(exit); + } + + @Test public void genTryStmt05() { + CfgNode entry = parseCfg("GenTryStmt05"); + CfgNode tryEntry = succ(entry, "try"); + CfgNode[] tryEntrySucc = succ(tryEntry, "f2()", "try"); + CfgNode exception = succ(tryEntrySucc[0], "exception"); + CfgNode[] tryEntrySucc2 = succ(tryEntrySucc[1], "if (condition)", "f1()"); + CfgNode exit = succ(exception, "exit"); + CfgNode[] ifBranchSucc = succ(tryEntrySucc2[0], "exception", "f1()"); + CfgNode[] f1Succ = succ(tryEntrySucc2[1], "exception", "return"); + assertThat(succ(ifBranchSucc[0], "f1()")).isSameAs(tryEntrySucc2[1]); + CfgNode[] f1Succ2 = succ(ifBranchSucc[1], "exception", "return"); + assertThat(succ(f1Succ[0], "f2()")).isSameAs(tryEntrySucc[0]); + CfgNode f22 = succ(f1Succ[1], "f2()"); + assertThat(succ(f1Succ2[0], "f2()")).isSameAs(tryEntrySucc[0]); + CfgNode f24 = succ(f1Succ2[1], "f2()"); + assertThat(succ(f22, "exit")).isSameAs(exit); + assertThat(succ(f24, "exit")).isSameAs(exit); + } + + @Test public void genTryStmt06() { + CfgNode entry = parseCfg("GenTryStmt06"); + CfgNode tryEntry = succ(entry, "try"); + CfgNode[] tryEntrySucc = succ(tryEntry, "f2()", "try"); + CfgNode exception = succ(tryEntrySucc[0], "exception"); + CfgNode[] tryEntrySucc2 = succ(tryEntrySucc[1], "s()", "f1()"); + CfgNode exit = succ(exception, "exit"); + CfgNode[] sSucc = succ(tryEntrySucc2[0], "exception", "f1()"); + CfgNode[] f1Succ = succ(tryEntrySucc2[1], "exception", "return"); + assertThat(succ(sSucc[0], "f1()")).isSameAs(tryEntrySucc2[1]); + CfgNode[] f1Succ2 = succ(sSucc[1], "exception", "return"); + assertThat(succ(f1Succ[0], "f2()")).isSameAs(tryEntrySucc[0]); + CfgNode f22 = succ(f1Succ[1], "f2()"); + assertThat(succ(f1Succ2[0], "f2()")).isSameAs(tryEntrySucc[0]); + CfgNode f24 = succ(f1Succ2[1], "f2()"); + assertThat(succ(f22, "exit")).isSameAs(exit); + assertThat(succ(f24, "exit")).isSameAs(exit); + } + + @Test public void genTryStmt07() { + CfgNode entry = parseCfg("GenTryStmt07"); + CfgNode tryEntry = succ(entry, "try"); + CfgNode[] tryEntrySucc = succ(tryEntry, "c1()", "x()"); + CfgNode returnMarker = succ(tryEntrySucc[0], "return"); + CfgNode[] xSucc = succ(tryEntrySucc[1], "exception", "f()"); + CfgNode f = succ(returnMarker, "f()"); + assertThat(succ(xSucc[0], "c1()")).isSameAs(tryEntrySucc[0]); + CfgNode y = succ(xSucc[1], "y()"); + CfgNode exit = succ(f, "exit"); + assertThat(succ(y, "exit")).isSameAs(exit); + } + + @Test public void genForStmt01() { + CfgNode entry = parseCfg("GenForStmt01"); + CfgNode forBranch = succ(entry, "for (i < 100)"); + CfgNode[] forBranchSucc = succ(forBranch, "c()", "fin()"); + CfgNode whileBranch = succ(forBranchSucc[0], "while (c())"); + CfgNode exit = succ(forBranchSucc[1], "exit"); + CfgNode[] whileBranchSucc = succ(whileBranch, "if (i >= 40)", "for-end"); + CfgNode[] ifBranchSucc = succ(whileBranchSucc[0], "break", "while-end"); + CfgNode u = succ(whileBranchSucc[1], "u()"); + assertThat(succ(ifBranchSucc[0], "fin()")).isSameAs(forBranchSucc[1]); + assertThat(succ(ifBranchSucc[1], "c()")).isSameAs(forBranchSucc[0]); + assertThat(succ(u, "for (i < 100)")).isSameAs(forBranch); + } + + @Test public void genForStmt02() { + CfgNode entry = parseCfg("GenForStmt02"); + CfgNode forBranch = succ(entry, "for (i < 100)"); + CfgNode[] forBranchSucc = succ(forBranch, "c()", "fin()"); + CfgNode whileBranch = succ(forBranchSucc[0], "while (c())"); + CfgNode exit = succ(forBranchSucc[1], "exit"); + CfgNode[] whileBranchSucc = succ(whileBranch, "if (i >= 40)", "for-end"); + CfgNode[] ifBranchSucc = succ(whileBranchSucc[0], "continue", "while-end"); + CfgNode u = succ(whileBranchSucc[1], "u()"); + assertThat(succ(ifBranchSucc[0], "for (i < 100)")).isSameAs(forBranch); + assertThat(succ(ifBranchSucc[1], "c()")).isSameAs(forBranchSucc[0]); + assertThat(succ(u, "for (i < 100)")).isSameAs(forBranch); + } + + @Test public void genClassInstance01() { + CfgNode entry = parseCfg("GenClassInstance01"); + CfgNode p1 = succ(entry, "p1()"); + CfgNode p2 = succ(p1, "p2()"); + CfgNode p3 = succ(p2, "p3()"); + CfgNode p4 = succ(p3, "p4()"); + CfgNode exit = succ(p4, "exit"); + } + + @Test public void genSwitchStmt01() { + CfgNode entry = parseCfg("GenSwitchStmt01"); + CfgNode whileBranch = succ(entry, "while (x + y == 400 - z)"); + CfgNode[] whileBranchSucc = succ(whileBranch, "switch (x)", "exit"); + CfgNode[] switchBranchSucc = succ(whileBranchSucc[0], "c6()", "c3()", "break", "c5()", "c1()", "c4()", "while-end", "c2()"); + CfgNode breakMarker = succ(switchBranchSucc[0], "break"); + assertThat(succ(switchBranchSucc[1], "c2()")).isSameAs(switchBranchSucc[7]); + assertThat(succ(switchBranchSucc[2], "while-end")).isSameAs(switchBranchSucc[6]); + CfgNode returnMarker = succ(switchBranchSucc[3], "return"); + CfgNode breakMarker2 = succ(switchBranchSucc[4], "break"); + CfgNode breakMarker3 = succ(switchBranchSucc[5], "break"); + assertThat(succ(switchBranchSucc[6], "while (x + y == 400 - z)")).isSameAs(whileBranch); + CfgNode continueMarker = succ(switchBranchSucc[7], "continue"); + assertThat(succ(breakMarker, "while-end")).isSameAs(switchBranchSucc[6]); + assertThat(succ(returnMarker, "exit")).isSameAs(whileBranchSucc[1]); + assertThat(succ(breakMarker2, "while-end")).isSameAs(switchBranchSucc[6]); + assertThat(succ(breakMarker3, "while-end")).isSameAs(switchBranchSucc[6]); + assertThat(succ(continueMarker, "while (x + y == 400 - z)")).isSameAs(whileBranch); + } + + @Test public void genClassInstance02() { + CfgNode entry = parseCfg("GenClassInstance02"); + CfgNode toString = succ(entry, "toString()"); + CfgNode exit = succ(toString, "exit"); + } + + @Test public void genTryWithResources01() { + CfgNode entry = parseCfg("GenTryWithResources01"); + CfgNode openStream = succ(entry, "openStream()"); + CfgNode[] openStreamSucc = succ(openStream, "exception", "try"); + CfgNode[] exceptionSucc = succ(openStreamSucc[0], "c()", "f()"); + CfgNode[] tryEntrySucc = succ(openStreamSucc[1], "c()", "stmt()", "f()"); + assertThat(tryEntrySucc[2]).isSameAs(exceptionSucc[1]); + assertThat(tryEntrySucc[0]).isSameAs(exceptionSucc[0]); + CfgNode f = succ(exceptionSucc[0], "f()"); + CfgNode exception = succ(exceptionSucc[1], "exception"); + CfgNode[] stmtSucc = succ(tryEntrySucc[1], "exception", "f()"); + assertThat(stmtSucc[1]).isSameAs(f); + CfgNode exit = succ(f, "exit"); + assertThat(succ(exception, "exit")).isSameAs(exit); + CfgNode[] exceptionSucc2 = succ(stmtSucc[0], "c()", "f()"); + assertThat(exceptionSucc2[0]).isSameAs(exceptionSucc[0]); + assertThat(exceptionSucc2[1]).isSameAs(exceptionSucc[1]); + } + + @Test public void genTryWithResources02() { + CfgNode entry = parseCfg("GenTryWithResources02"); + CfgNode o1 = succ(entry, "o1()"); + CfgNode[] o1Succ = succ(o1, "exception", "o2()"); + CfgNode[] exceptionSucc = succ(o1Succ[0], "c()", "f()"); + CfgNode[] o2Succ = succ(o1Succ[1], "exception", "try"); + CfgNode f = succ(exceptionSucc[0], "f()"); + CfgNode exception = succ(exceptionSucc[1], "exception"); + CfgNode[] exceptionSucc2 = succ(o2Succ[0], "c()", "f()"); + assertThat(exceptionSucc2[0]).isSameAs(exceptionSucc[0]); + assertThat(exceptionSucc2[1]).isSameAs(exceptionSucc[1]); + CfgNode[] tryEntrySucc = succ(o2Succ[1], "c()", "stmt()", "f()"); + assertThat(tryEntrySucc[2]).isSameAs(exceptionSucc[1]); + assertThat(tryEntrySucc[0]).isSameAs(exceptionSucc[0]); + CfgNode exit = succ(f, "exit"); + assertThat(succ(exception, "exit")).isSameAs(exit); + CfgNode[] stmtSucc = succ(tryEntrySucc[1], "exception", "f()"); + assertThat(stmtSucc[1]).isSameAs(f); + CfgNode[] exceptionSucc3 = succ(stmtSucc[0], "c()", "f()"); + assertThat(exceptionSucc3[1]).isSameAs(exceptionSucc[1]); + assertThat(exceptionSucc3[0]).isSameAs(exceptionSucc[0]); + } + +} diff --git a/simplecfg/src/test/java/org/extendj/ast/IdentityTupleSetTest.java b/simplecfg/src/test/java/org/extendj/ast/IdentityTupleSetTest.java new file mode 100644 index 0000000000000000000000000000000000000000..417eeb8d6ce2e13e06e3085279c61871eff478fa --- /dev/null +++ b/simplecfg/src/test/java/org/extendj/ast/IdentityTupleSetTest.java @@ -0,0 +1,58 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.extendj.ast; + +import static com.google.common.truth.Truth.assertThat; + +import java.util.Set; + +import org.extendj.ast.IdentityTupleSet; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link IdentityTupleSet}. */ +@RunWith(JUnit4.class) +public class IdentityTupleSetTest { + + // Two unique objects used for testing. + private Object a = new Object(); + private Object b = new Object(); + + @Test public void testSize() { + assertThat(new IdentityTupleSet<>(null, null)).hasSize(1); + assertThat(new IdentityTupleSet<>(a, a)).hasSize(1); + assertThat(new IdentityTupleSet<>(a, b)).hasSize(2); + assertThat(new IdentityTupleSet<>(null, b)).hasSize(2); + } + + @Test public void testIsEmpty() { + assertThat(new IdentityTupleSet<>(null, null)).isNotEmpty(); + assertThat(new IdentityTupleSet<>(a, a)).isNotEmpty(); + assertThat(new IdentityTupleSet<>(a, b)).isNotEmpty(); + assertThat(new IdentityTupleSet<>(null, b)).isNotEmpty(); + } + + @Test public void testDuplicateContains() { + assertThat(new IdentityTupleSet<>(null, null)).containsExactly((Object) null); + assertThat(new IdentityTupleSet<>(a, a)).containsExactly(a); + } + + @Test public void testContains() { + assertThat(new IdentityTupleSet<>(a, b)).containsExactly(a, b); + } +} diff --git a/simplecfg/testdata/AlreadyClosedControlFlow01.javax b/simplecfg/testdata/AlreadyClosedControlFlow01.javax new file mode 100644 index 0000000000000000000000000000000000000000..2463b37f65b00a7ff3f37d6ea2a606dabb23dee8 --- /dev/null +++ b/simplecfg/testdata/AlreadyClosedControlFlow01.javax @@ -0,0 +1,140 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real code! This file is parsed by the Java API analyzer tests + * to check that the expected findings are reported for this file. + */ +import java.io.BufferedInputStream; +import java.io.Closeable; +import java.io.FileInputStream; + +/* This code is used to check that the already-closed analysis handles different control flow + * structures appropriately. + */ +class AlreadyClosedControlFlow01 { + void for01(FileInputStream in) { + for (int i = 0; i < 10; ++i) { + in.close(); + } + // The analyzer does not know how many loop iterations are executed, assumes 0+ + // iterations are possible. + in.skip(0); // Positive finding. + } + + void for02() { + for (int i = 0; i < 10; ++i) { + FileInputStream in = new FileInputStream("somefile"); + in.skip(0); + in.close(); // Negative finding: new instance each iteration. + } + } + + void if01() { + Closeable in = new FileInputStream("somefile"); + if (System.currentTimeMillis() & 1 == 0) { + in.read(); + in.close(); + } else { + in.read(); + in.close(); + } + } + + void if02() { + Closeable in = new FileInputStream("somefile"); + in.close(); + if (System.currentTimeMillis() & 1 == 0) { + in.read(); // Positive finding. + } + } + + void if03() { + Closeable in = new FileInputStream("foo"); + in.close(); + // Check that the if condition is analyzed. + if (in.read() == -1) { // Positive finding. + } + } + + void while01() { + Closeable in = new FileInputStream("bar"); + while (true) { + in.read(); + in.close(); + break; + } + in.read(); // Positive finding. + } + + void while02() { + Closeable in = new FileInputStream("bar"); + while (in.read() != -1) { // Positive finding. + in.close(); + } + } + + void try01(BufferedInputStream in) { + try { + in.close(); + } catch (Throwable t) { + in.read(); // Negative finding: preceding close call interrupted by exception. + } + } + + void try02(BufferedInputStream in) { + try { + in.close(); + } catch (Throwable t) { + in.read(); + } finally { + in.read(); // Positive finding: can be reached after an uninterrupted close call. + } + } + + void switch01(BufferedInputStream in, int i) { + switch (i) { + case 1: + in.close(); + break; + case 2: + in.read(); + break; + case 3: + in.close(); + case 4: + in.read(); // Positive finding. + break; + } + } + + void switch02(BufferedInputStream in, int i) { + switch (i) { + case 1: + in.close(); + break; + case 2: + in.read(); + break; + case 3: + in.close(); + return; + case 4: + in.read(); + default: + in.close(); + } + } +} diff --git a/simplecfg/testdata/AlreadyClosedNegativeFindings01.javax b/simplecfg/testdata/AlreadyClosedNegativeFindings01.javax new file mode 100644 index 0000000000000000000000000000000000000000..f59ecd19af455d4b9ea08b870f183917199e57bf --- /dev/null +++ b/simplecfg/testdata/AlreadyClosedNegativeFindings01.javax @@ -0,0 +1,87 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real code! This file is parsed by the Java API analyzer tests + * to check that no findings are reported for this file. + */ +import java.io.Closeable; +import java.io.FileInputStream; + +/* Negative findings for the already-closed analyzer. */ +class AlreadyClosedNegativeFindings01 { + + static class MyCloseable { + public void close() { + } + public void write() { + } + } + + void f1() { + // MyCloseable does not implement java.io.Closeable, so it should not cause findings. + MyCloseable closeable = new MyCloseable(); + closeable.close(); + closeable.write(); + } + + void f2() { + while (true) { + // This is a new instance each iteration, so close is only called once per instance. + Closeable in = new FileInputStream("x"); + in.read(); + in.close(); + if (System.currentTimeMillis() % 2 == 1) { + break; + } + } + + void f3() { + // This local variable is not effectively final, so it should not be analyzed. + Closeable in = new FileInputStream("x"); + in.read(); + in.close(); + in = new FileInputStream("y"); + in.read(); + } + + void f4() { + // This local variable is not effectively final, so it should not be analyzed. + Closeable in = new FileInputStream("x"); + in.read(); + in.close(); + in = new FileInputStream("y"); + in.read(); + } + + void f5(Closeable in) { + // Repeated calls to close() are allowed and do not produce findings. + in.close(); + in.close(); + in.close(); + in.close(); + in.close(); + } + + String f6(ByteArrayOutputStream out) { + out.close(); + return out.toString(); // Negative finding: calling toString() after close() is okay. + } + + byte[] f7(ByteArrayOutputStream out) { + out.close(); + return out.toByteArray(); // Negative finding: calling toByteArray() after close() is okay. + } +} diff --git a/simplecfg/testdata/AlreadyClosedWriter01.javax b/simplecfg/testdata/AlreadyClosedWriter01.javax new file mode 100644 index 0000000000000000000000000000000000000000..2b7769c0f5333ade6ce2d9aafc9482be9225f68a --- /dev/null +++ b/simplecfg/testdata/AlreadyClosedWriter01.javax @@ -0,0 +1,29 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real code! This file is parsed by the Java API analyzer tests + * to check that the expected findings are reported for this file. + */ + +/* This code is used to check that the already-closed analyzer can identify java.io.Writer + * as a subtype of java.io.Closeable and report a finding for a simple call after close. + */ +class AlreadyClosedWriter01 { + void f(java.io.Writer writer) { + writer.close(); + writer.flush(); // Finding: Calling flush() after close(). + } +} diff --git a/simplecfg/testdata/Close01.javax b/simplecfg/testdata/Close01.javax new file mode 100644 index 0000000000000000000000000000000000000000..025fc45599d3724fbc211d78e48717aa4e191f7c --- /dev/null +++ b/simplecfg/testdata/Close01.javax @@ -0,0 +1,25 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +public class Close01 { + void f(java.io.Writer writer) { + writer.close(); + writer.write(new byte[10]); + } +} diff --git a/simplecfg/testdata/ConditionalExpr01.javax b/simplecfg/testdata/ConditionalExpr01.javax new file mode 100644 index 0000000000000000000000000000000000000000..de8385b6bf9b2144b19711bccb0c8045c0e4cfc5 --- /dev/null +++ b/simplecfg/testdata/ConditionalExpr01.javax @@ -0,0 +1,25 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class ConditionalExpr01 { + static { + // Conditional expressions work like if statements. + int unused = x() ? y() : z(); + } +} diff --git a/simplecfg/testdata/DoStmt01.javax b/simplecfg/testdata/DoStmt01.javax new file mode 100644 index 0000000000000000000000000000000000000000..d2a6d832c3a6db205f19b2c0570aa2cb1bc4c5d2 --- /dev/null +++ b/simplecfg/testdata/DoStmt01.javax @@ -0,0 +1,27 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class DoStmt01 { + { + do { + x(); + } while (y()); + z(); + } +} diff --git a/simplecfg/testdata/DoStmt02.javax b/simplecfg/testdata/DoStmt02.javax new file mode 100644 index 0000000000000000000000000000000000000000..2e1fb9eb949f7797c03fd0e385c8a1e83e024bba --- /dev/null +++ b/simplecfg/testdata/DoStmt02.javax @@ -0,0 +1,28 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class DoStmt02 { + { + // Test do statement with constant false condition. + do { + x(); + } while (false); + y(); + } +} diff --git a/simplecfg/testdata/EnhancedFor01.javax b/simplecfg/testdata/EnhancedFor01.javax new file mode 100644 index 0000000000000000000000000000000000000000..f1532b23592ed6f6f6b22fe9dcf641409ca852c7 --- /dev/null +++ b/simplecfg/testdata/EnhancedFor01.javax @@ -0,0 +1,26 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class EnhancedFor01 { + { + for (int a : aList()) { + x(); + } + } +} diff --git a/simplecfg/testdata/Filtering01.javax b/simplecfg/testdata/Filtering01.javax new file mode 100644 index 0000000000000000000000000000000000000000..8f83cee0544b48b338ef50ba730ebc49e405839f --- /dev/null +++ b/simplecfg/testdata/Filtering01.javax @@ -0,0 +1,31 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class Filtering01 { + int x(int c) { + // Test that uninteresting expressions are not included in the CFG. + int a = 4; + int b = 3; + for (int i = i(1, 4 * 5), j = j(); cond() && c == 3; u1(), j += c / 2, u2(), u3(), i++) { + stmt(); + a += (--b) * c; + } + return a + b; + } +} diff --git a/simplecfg/testdata/ForStmt01.javax b/simplecfg/testdata/ForStmt01.javax new file mode 100644 index 0000000000000000000000000000000000000000..ce58d63becab96d0b5fa5fb498817e97854f8518 --- /dev/null +++ b/simplecfg/testdata/ForStmt01.javax @@ -0,0 +1,25 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class ForStmt01 { + void x() { + for (; call1(); ) { + } + } +} diff --git a/simplecfg/testdata/ForStmt02.javax b/simplecfg/testdata/ForStmt02.javax new file mode 100644 index 0000000000000000000000000000000000000000..f204489cd686042e0bacffe8b5368af12ff207f9 --- /dev/null +++ b/simplecfg/testdata/ForStmt02.javax @@ -0,0 +1,26 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class ForStmt02 { + void x() { + for (; call1(); ) { + call2(); + } + } +} diff --git a/simplecfg/testdata/ForStmt03.javax b/simplecfg/testdata/ForStmt03.javax new file mode 100644 index 0000000000000000000000000000000000000000..720fefd193cc581a23ac7aa8c3f0b794d2c82701 --- /dev/null +++ b/simplecfg/testdata/ForStmt03.javax @@ -0,0 +1,25 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class ForStmt03 { + void x() { + for (int i = init(); cond(); update()) + stmt(); + } +} diff --git a/simplecfg/testdata/ForStmt04.javax b/simplecfg/testdata/ForStmt04.javax new file mode 100644 index 0000000000000000000000000000000000000000..29b036334033967540d8c8a3f71b078b8238d22e --- /dev/null +++ b/simplecfg/testdata/ForStmt04.javax @@ -0,0 +1,27 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class ForStmt04 { + { + // Initializer expressions and update statements are evaluated in the order they are listed. + for (int i = i(), j = j(); cond(); u1(), u2(), u3()) { + stmt(); + } + } +} diff --git a/simplecfg/testdata/ForStmt05.javax b/simplecfg/testdata/ForStmt05.javax new file mode 100644 index 0000000000000000000000000000000000000000..7cea144800964ac35735329cc6f0803023cb30b0 --- /dev/null +++ b/simplecfg/testdata/ForStmt05.javax @@ -0,0 +1,28 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class ForStmt05 { + { + // Test for statement with constant false condition. + for (Object o = i(); false; u()) { + x(); + } + y(); + } +} diff --git a/simplecfg/testdata/GenClassInstance01.javax b/simplecfg/testdata/GenClassInstance01.javax new file mode 100644 index 0000000000000000000000000000000000000000..26e7baeeee6582ca92fddd82f3272e65b823c3dd --- /dev/null +++ b/simplecfg/testdata/GenClassInstance01.javax @@ -0,0 +1,24 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class GenClassInstance01 { + GenClassInstance01() { + new C(p1(), p2(), new D(p3() + p4())); + } +} diff --git a/simplecfg/testdata/GenClassInstance02.javax b/simplecfg/testdata/GenClassInstance02.javax new file mode 100644 index 0000000000000000000000000000000000000000..a73a972e46c5ae098041f3528a701e7d54302d6e --- /dev/null +++ b/simplecfg/testdata/GenClassInstance02.javax @@ -0,0 +1,28 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class GenClassInstance02 { + { + new Object() { + void f() { + notInParentCfg(); + } + }.toString(); + } +} diff --git a/simplecfg/testdata/GenForStmt01.javax b/simplecfg/testdata/GenForStmt01.javax new file mode 100644 index 0000000000000000000000000000000000000000..a1dab9ad9e9689d31297dde0950bfcb22ee48c09 --- /dev/null +++ b/simplecfg/testdata/GenForStmt01.javax @@ -0,0 +1,33 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class GenForStmt01 { + { + // Test a labeled break. + loop1: + for (int i = 3 * x; i < 100; ++i, u()) { + while (c()) { + if (i >= 40) { + break loop1; + } + } + } + fin(); + } +} diff --git a/simplecfg/testdata/GenForStmt02.javax b/simplecfg/testdata/GenForStmt02.javax new file mode 100644 index 0000000000000000000000000000000000000000..23535baf67376b1a0d3ee5e7d69f5a906575dfa8 --- /dev/null +++ b/simplecfg/testdata/GenForStmt02.javax @@ -0,0 +1,33 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class GenForStmt02 { + { + // Test a labeled continue. + loop1: + for (int i = 3 * x; i < 100; ++i, u()) { + while (c()) { + if (i >= 40) { + continue loop1; + } + } + } + fin(); + } +} diff --git a/simplecfg/testdata/GenSwitchStmt01.javax b/simplecfg/testdata/GenSwitchStmt01.javax new file mode 100644 index 0000000000000000000000000000000000000000..1ef81a62a8217e678e0dec43256448b5d44a0492 --- /dev/null +++ b/simplecfg/testdata/GenSwitchStmt01.javax @@ -0,0 +1,46 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class GenSwitchStmt01 { + GenSwitchStmt01() { + while (x + y == 400 - z) { + switch (x) { + case -1: + c1(); + break; + case 1: + break; + case 3: + c3(); + case 2: + c2(); + continue; + case 4: + c4(); + break; + case 5: + c5(); + return; + case 6: + c6(); + break; + } + } + } +} diff --git a/simplecfg/testdata/GenTryStmt01.javax b/simplecfg/testdata/GenTryStmt01.javax new file mode 100644 index 0000000000000000000000000000000000000000..63975d3b5f46a503bbe353af2216a20e3f19de7e --- /dev/null +++ b/simplecfg/testdata/GenTryStmt01.javax @@ -0,0 +1,34 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class GenTryStmt01 { + void f(Exception exception) { + // Test that branches from throw statement go to both all catch clauses. + try { + if (cond()) { + throw exception; + } + x(); + } catch (Exception1 e) { + c1(); + } catch (Exception2 e) { + c2(); + } + } +} diff --git a/simplecfg/testdata/GenTryStmt02.javax b/simplecfg/testdata/GenTryStmt02.javax new file mode 100644 index 0000000000000000000000000000000000000000..3fb2f476c9c7917d1f5108a02bb4bf9c0804b9ee --- /dev/null +++ b/simplecfg/testdata/GenTryStmt02.javax @@ -0,0 +1,30 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class GenTryStmt02 { + { + try { + tryBlock(); + } catch (Exception unused) { + c1(); + } catch (Throwable unused) { + c2(); + } + } +} diff --git a/simplecfg/testdata/GenTryStmt03.javax b/simplecfg/testdata/GenTryStmt03.javax new file mode 100644 index 0000000000000000000000000000000000000000..3d0cf1fb671fb71b91f1c107f1f8446246b4eca5 --- /dev/null +++ b/simplecfg/testdata/GenTryStmt03.javax @@ -0,0 +1,33 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class GenTryStmt03 { + { + try { + x(); + } catch (Throwable unused) { + c1(); + } catch (Exception unused) { + // Never reached. + c2(); + } finally { + f(); + } + } +} diff --git a/simplecfg/testdata/GenTryStmt04.javax b/simplecfg/testdata/GenTryStmt04.javax new file mode 100644 index 0000000000000000000000000000000000000000..66f3e7d2f0b4adbdfff36d17505d6526008a939c --- /dev/null +++ b/simplecfg/testdata/GenTryStmt04.javax @@ -0,0 +1,34 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class GenTryStmt04 { + void m() { + // Test that finally handlers are generated in separate CFG branches. + // Non-identical CFG nodes with the same name are expected in this graph. + try { + if (condition) { + a(); + return; + } + } finally { + x(); + } + y(); + } +} diff --git a/simplecfg/testdata/GenTryStmt05.javax b/simplecfg/testdata/GenTryStmt05.javax new file mode 100644 index 0000000000000000000000000000000000000000..2d4d5540093ede8bcf6134e59af27a6f03337156 --- /dev/null +++ b/simplecfg/testdata/GenTryStmt05.javax @@ -0,0 +1,35 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class GenTryStmt05 { + void z() { + try { + try { + if (condition) throw exception; + } finally { + f1(); + return; + } + a(); + } finally { + f2(); + } + b(); + } +} diff --git a/simplecfg/testdata/GenTryStmt06.javax b/simplecfg/testdata/GenTryStmt06.javax new file mode 100644 index 0000000000000000000000000000000000000000..8af1b8308cc75a258ccbaa322c3caff67503c125 --- /dev/null +++ b/simplecfg/testdata/GenTryStmt06.javax @@ -0,0 +1,34 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class GenTryStmt06 { + { + // Abrupt exit from nested finally block. + try { + try { + s(); + } finally { + f1(); + return; + } + } finally { + f2(); + } + } +} diff --git a/simplecfg/testdata/GenTryStmt07.javax b/simplecfg/testdata/GenTryStmt07.javax new file mode 100644 index 0000000000000000000000000000000000000000..02a27a98cd8c99ea6770bf94a8ba9caf0f028da7 --- /dev/null +++ b/simplecfg/testdata/GenTryStmt07.javax @@ -0,0 +1,35 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class GenTryStmt07 { + { + try { + x(); + } catch (Throwable unused) { + c1(); + return; + } catch (Exception unused) { + // Never reached. + c2(); + } finally { + f(); + } + y(); + } +} diff --git a/simplecfg/testdata/GenTryWithResources01.javax b/simplecfg/testdata/GenTryWithResources01.javax new file mode 100644 index 0000000000000000000000000000000000000000..c0c5c43ce8718e34c2d2ec33db5311797808c2e1 --- /dev/null +++ b/simplecfg/testdata/GenTryWithResources01.javax @@ -0,0 +1,30 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class GenTryWithResources01 { + { + try (AutoCloseable stream = openStream()) { + stmt(); + } catch (IOException e) { + c(); + } finally { + f(); + } + } +} diff --git a/simplecfg/testdata/GenTryWithResources02.javax b/simplecfg/testdata/GenTryWithResources02.javax new file mode 100644 index 0000000000000000000000000000000000000000..d814c2492e69522ae548e8db8876f9369121cea3 --- /dev/null +++ b/simplecfg/testdata/GenTryWithResources02.javax @@ -0,0 +1,31 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class GenTryWithResources02 { + { + try (AutoCloseable s1 = o1(); + AutoCloseable s2 = o2()) { + stmt(); + } catch (IOException e) { + c(); + } finally { + f(); + } + } +} diff --git a/simplecfg/testdata/IfStmt01.javax b/simplecfg/testdata/IfStmt01.javax new file mode 100644 index 0000000000000000000000000000000000000000..ef8dd0bfcd8a7c3c4611ac212626d94b1345c160 --- /dev/null +++ b/simplecfg/testdata/IfStmt01.javax @@ -0,0 +1,26 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class IfStmt01 { + void f() { + if (call1()) { + onTrue(); + } + } +} diff --git a/simplecfg/testdata/IfStmt02.javax b/simplecfg/testdata/IfStmt02.javax new file mode 100644 index 0000000000000000000000000000000000000000..0271e6843c7bb0b95fcf8cc0dfc4597ea014114c --- /dev/null +++ b/simplecfg/testdata/IfStmt02.javax @@ -0,0 +1,28 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class IfStmt02 { + void f() { + if (call1()) { + onTrue(); + } else { + onFalse(); + } + } +} diff --git a/simplecfg/testdata/IfStmt03.javax b/simplecfg/testdata/IfStmt03.javax new file mode 100644 index 0000000000000000000000000000000000000000..e515c9cdc0878e4acf7e9fbf7f846fed49027732 --- /dev/null +++ b/simplecfg/testdata/IfStmt03.javax @@ -0,0 +1,29 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class IfStmt03 { + void f() { + if (call1()) { + onTrue(); + } else { + onFalse(); + call2(); + } + } +} diff --git a/simplecfg/testdata/IfStmt04.javax b/simplecfg/testdata/IfStmt04.javax new file mode 100644 index 0000000000000000000000000000000000000000..19c6c45ce5e5722e4e966e0a7c0c457d583fcb20 --- /dev/null +++ b/simplecfg/testdata/IfStmt04.javax @@ -0,0 +1,29 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class IfStmt04 { + void f() { + if (call1()) { + onTrue(); + call2(); + } else { + onFalse(); + } + } +} diff --git a/simplecfg/testdata/MethodAccess01.javax b/simplecfg/testdata/MethodAccess01.javax new file mode 100644 index 0000000000000000000000000000000000000000..dad7cc795334f280e67c969422fda0e5a13daddd --- /dev/null +++ b/simplecfg/testdata/MethodAccess01.javax @@ -0,0 +1,25 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class MethodAccess01 { + void f() { + // Parameters are evaluated before the outer method call. + x(p1(), p2(), p3()); + } +} diff --git a/simplecfg/testdata/NullableDataflow01.javax b/simplecfg/testdata/NullableDataflow01.javax new file mode 100644 index 0000000000000000000000000000000000000000..41013edcba70b58bdc5b2ad9e0b6d822ef1aa397 --- /dev/null +++ b/simplecfg/testdata/NullableDataflow01.javax @@ -0,0 +1,53 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + */ +import javax.annotation.Nullable; + +public class NullableDataflow01 { + boolean x = false; + + void fp1(@Nullable String p) { + boolean var = p == null; + if (!var) { + p.hashCode(); // False positive: condition variable not effectively final. + } + var = false; + } + + void fp2(@Nullable String p) { + boolean var = p != null; + if (var) { + p.hashCode(); // False positive: condition variable not effectively final. + } + var = false; + } + + void n1(@Nullable String p) { + boolean var = p == null; + if (!var) { + p.hashCode(); // Negative. + } + } + + void n2(@Nullable String p) { + boolean var = p != null; + if (var) { + p.hashCode(); // Negative. + } + } +} diff --git a/simplecfg/testdata/NullableDereference01.javax b/simplecfg/testdata/NullableDereference01.javax new file mode 100644 index 0000000000000000000000000000000000000000..e9b01b97c546e496ea3b971801364c424c5a889d --- /dev/null +++ b/simplecfg/testdata/NullableDereference01.javax @@ -0,0 +1,37 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +import javax.annotation.Nullable; + +/** + * Test some simple positive findings for the Nullable Dereference analyzer. + */ +class NullableDereference01 { + int p1(@Nullable String[] p) { + return p.length; + } + + String p2(@Nullable String[] p) { + return p[0]; + } + + int fn3(@Nullable String[] p) { + return p[0].size(); // False negative. + } +} diff --git a/simplecfg/testdata/NullableDereferenceEqExpr.javax b/simplecfg/testdata/NullableDereferenceEqExpr.javax new file mode 100644 index 0000000000000000000000000000000000000000..95adffa692f4fe7e0cca4ac84e3c4829643659e2 --- /dev/null +++ b/simplecfg/testdata/NullableDereferenceEqExpr.javax @@ -0,0 +1,200 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import javax.annotation.Nullable; + +/** + * This is test data, not real source code! + * This contains tests for EQExpr null guards. Each test method contains + * one false positive dereference of the parameter p. The parameter is + * guarded by a null check in an equality expression. + */ +public class NullableDereferenceEqExpr { + /** + * Non-null when true condition. + * Null test in left hand side, comparing to false. + */ + int nntLhsFalse(@Nullable String p) { + if (false == (p == null)) { + return p.size(); + } + return 0; + } + + /** + * Non-null when true condition. + * Null test in right hand side, comparing to false. + */ + int nntRhsFalse(@Nullable String p) { + if ((p == null) == false) { + return p.size(); + } + return 0; + } + + /** + * Non-null when true condition. + * Null test in left hand side, comparing to true. + */ + int nntLhsTrue(@Nullable String p) { + if (true == (p != null)) { + return p.size(); + } + return 0; + } + + /** + * Non-null when true condition. + * Null test in right hand side, comparing to true. + */ + int nntRhsTrue(@Nullable String p) { + if ((p != null) == true) { + return p.size(); + } + return 0; + } + + /** + * Non-null when false condition. + * Null test in left hand side, comparing to false. + */ + int nnfLhsFalse(@Nullable String p) { + if (false == (p != null)) { + return 0; + } + return p.size(); + } + + /** + * Non-null when false condition. + * Null test in right hand side, comparing to false. + */ + int nnfRhsFalse(@Nullable String p) { + if ((p != null) == false) { + return 0; + } + return p.size(); + } + + /** + * Non-null when false condition. + * Null test in left hand side, comparing to true. + */ + int nnfLhsTrue(@Nullable String p) { + if (true == (p == null)) { + return 0; + } + return p.size(); + } + + /** + * Non-null when false condition. + * Null test in right hand side, comparing to true. + */ + int nnfRhsTrue(@Nullable String p) { + if ((p == null) == true) { + return 0; + } + return p.size(); + } + + /** + * Null when true condition (negated). + * Null test in left hand side, comparing to false. + */ + int ntLhsFalse(@Nullable String p) { + if (!(false == (p != null))) { + return p.size(); + } + return 0; + } + + /** + * Null when true condition (negated). + * Null test in right hand side, comparing to false. + */ + int ntRhsFalse(@Nullable String p) { + if (!((p != null) == false)) { + return p.size(); + } + return 0; + } + + /** + * Null when true condition (negated). + * Null test in left hand side, comparing to true. + */ + int ntLhsTrue(@Nullable String p) { + if (!(true == (p == null))) { + return p.size(); + } + return 0; + } + + /** + * Null when true condition (negated). + * Null test in right hand side, comparing to true. + */ + int ntRhsTrue(@Nullable String p) { + if (!((p == null) == true)) { + return p.size(); + } + return 0; + } + + /** + * Null when false condition (negated). + * Null test in left hand side, comparing to false. + */ + int nfLhsFalse(@Nullable String p) { + if (!(false == (p == null))) { + return 0; + } + return p.size(); + } + + /** + * Null when false condition (negated). + * Null test in right hand side, comparing to false. + */ + int nfRhsFalse(@Nullable String p) { + if (!((p == null) == false)) { + return 0; + } + return p.size(); + } + + /** + * Null when false condition (negated). + * Null test in left hand side, comparing to true. + */ + int nfLhsTrue(@Nullable String p) { + if (!(true == (p != null))) { + return 0; + } + return p.size(); + } + + /** + * Null when false condition (negated). + * Null test in right hand side, comparing to true. + */ + int nfRhsTrue(@Nullable String p) { + if (!((p != null) == true)) { + return 0; + } + return p.size(); + } +} diff --git a/simplecfg/testdata/NullableDereferenceIssue10.javax b/simplecfg/testdata/NullableDereferenceIssue10.javax new file mode 100644 index 0000000000000000000000000000000000000000..cd5e584fe48031543be7eff1b0d730e9a7571472 --- /dev/null +++ b/simplecfg/testdata/NullableDereferenceIssue10.javax @@ -0,0 +1,36 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* This is test data, not real source code! */ +import javax.annotation.Nullable; + +/** + * Test a false positive finding from GitHub issue #10 + * (https://github.com/google/simplecfg/issues/10). + */ +class NullableDereferenceIssue10 { + static class A { + public void m() { + } + } + + void fp1(@Nullable A x, @Nullable A y) { + if (x == null && y == null) { + return; + } + // If x is null then y is not null. + z = (x != null) ? x.m() : y.m(); // False positive for y.m(). + } +} diff --git a/simplecfg/testdata/NullableDereferenceIssue11.javax b/simplecfg/testdata/NullableDereferenceIssue11.javax new file mode 100644 index 0000000000000000000000000000000000000000..fb1fdaa8c6f7e51aa8b1c58efe37cc2df02df959 --- /dev/null +++ b/simplecfg/testdata/NullableDereferenceIssue11.javax @@ -0,0 +1,46 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import javax.annotation.Nullable; + +/** + * This is test data, not real source code! + * Test for GitHub issue #11 (https://github.com/google/simplecfg/issues/11). + */ +public class Issue11 { + /** Test nullable deref in true-part of conditional expression. */ + int test(@Nullable String p, boolean maybe) { + if (p == null || (maybe ? p.size() != 1 : false)) { // Negative finding. + return 1; + } + return 2; + } + + /** Test nullable deref in false-part of conditional expression. */ + int test2(@Nullable String p, boolean maybe) { + if (p == null || (maybe ? false : p.size() != 1)) { // Negative finding. + return 1; + } + return 2; + } + + /** Test that the same null-guard used above works outside of conditional expression. */ + int test3(@Nullable String p) { + if (p == null || p.size() != 1) { // Negative finding. + return 1; + } + return 2; + } +} diff --git a/simplecfg/testdata/NullableDereferenceIssue12.javax b/simplecfg/testdata/NullableDereferenceIssue12.javax new file mode 100644 index 0000000000000000000000000000000000000000..95ca23534a6c6aa81c31d6f822230aff6d21cd20 --- /dev/null +++ b/simplecfg/testdata/NullableDereferenceIssue12.javax @@ -0,0 +1,45 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import javax.annotation.Nullable; + +/** + * This is test data, not real source code! + * Test for GitHub issue #12 (https://github.com/google/simplecfg/issues/12). + */ +public class NullableDereferenceIssue12 { + /** + * The condition (false == x) is equivalent to (!x), however this test + * shows a bug causing a null guard check to fail for (false == x) + * expressions. + */ + int test(@Nullable String p) { + if (false == (p != null)) { + return 0; + } + return p.size(); // Negative finding: not reachable if p == null. + } + + /** + * The condition in this if-statement and the one in the first test are + * equivalent but this version does not give a false positive. + */ + int test2(@Nullable String p) { + if (!(p != null)) { + return 0; + } + return p.size(); // Negative finding. + } +} diff --git a/simplecfg/testdata/NullableDereferenceIssue13.javax b/simplecfg/testdata/NullableDereferenceIssue13.javax new file mode 100644 index 0000000000000000000000000000000000000000..27a28b932568fc5a83788ad1c451866b06e55fa2 --- /dev/null +++ b/simplecfg/testdata/NullableDereferenceIssue13.javax @@ -0,0 +1,40 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import javax.annotation.Nullable; + +/** + * This is test data, not real source code! + * Test for GitHub issue #13 (https://github.com/google/simplecfg/issues/13). + */ +public class NullableDereferenceIssue13 { + protected final Object obj; + + public NullableDereferenceIssue13(@Nullable Object obj) { + this.obj = obj; + + // Calling test() is equivalent to checking obj != null, + // however, the current NullableDereference implementation + // can not handle this case, even though test() is declared + // inside the same class. + if (test()) { + obj.hashCode(); // False positive finding generated here. + } + } + + private boolean test() { + return obj != null; + } +} diff --git a/simplecfg/testdata/NullableDereferenceMethodCall.javax b/simplecfg/testdata/NullableDereferenceMethodCall.javax new file mode 100644 index 0000000000000000000000000000000000000000..c2bcf2093c49be3c57d09751597d66b5a0ba68a2 --- /dev/null +++ b/simplecfg/testdata/NullableDereferenceMethodCall.javax @@ -0,0 +1,55 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import javax.annotation.Nullable; + +/** + * This is test data, not real source code! + * + * <p>This class contains tests checking that a null dereference + * finding is not reported for a parameter p after p has been used + * as an argument in a method call. The NullableDereference + * analyzer is not an interprocedural analysis, so we can not know + * if the method called will perform a null check and ensure + * non-nullness of the argument after the call completes. Instead + * we should just stop analyzing a parameter after it has been + * passed to another method. + */ +public class NullableDereferenceMethodCall { + public interface SomeApi { + public void doSomething(String str); + } + + int interfaceMethodCall(@Nullable String p, SomeApi s) { + s.doSomething(p); + return p.size(); // No finding here, since doSomething might have done a null check. + } + + int interfaceMethodCall2(@Nullable String p, SomeApi s) { + int size = p.size(); // Finding here: no call yet. + s.doSomething(p); + return size; + } + + int methodCall(@Nullable String p, String q) { + q.equals(p); + return p.size(); + } + + int localMethodCall(@Nullable String p) { + localMethod(p); + return p.size(); // No finding here, we don't analyze even local methods. + } +} diff --git a/simplecfg/testdata/NullableDereferenceNeExpr.javax b/simplecfg/testdata/NullableDereferenceNeExpr.javax new file mode 100644 index 0000000000000000000000000000000000000000..f70f138138b976faeb9e2b5ba40c0802712408e0 --- /dev/null +++ b/simplecfg/testdata/NullableDereferenceNeExpr.javax @@ -0,0 +1,200 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import javax.annotation.Nullable; + +/** + * This is test data, not real source code! + * This contains tests for NEExpr null guards. Each test method contains + * one false positive dereference of the parameter p. The parameter is + * guarded by a null check in an inequality expression. + */ +public class NullableDereferenceNeExpr { + /** + * Non-null when true condition. + * Null test in left hand side, comparing to false. + */ + int nntLhsFalse(@Nullable String p) { + if (false != (p != null)) { + return p.size(); + } + return 0; + } + + /** + * Non-null when true condition. + * Null test in right hand side, comparing to false. + */ + int nntRhsFalse(@Nullable String p) { + if ((p != null) != false) { + return p.size(); + } + return 0; + } + + /** + * Non-null when true condition. + * Null test in left hand side, comparing to true. + */ + int nntLhsTrue(@Nullable String p) { + if (true != (p == null)) { + return p.size(); + } + return 0; + } + + /** + * Non-null when true condition. + * Null test in right hand side, comparing to true. + */ + int nntRhsTrue(@Nullable String p) { + if ((p == null) != true) { + return p.size(); + } + return 0; + } + + /** + * Non-null when false condition. + * Null test in left hand side, comparing to false. + */ + int nnfLhsFalse(@Nullable String p) { + if (false != (p == null)) { + return 0; + } + return p.size(); + } + + /** + * Non-null when false condition. + * Null test in right hand side, comparing to false. + */ + int nnfRhsFalse(@Nullable String p) { + if ((p == null) != false) { + return 0; + } + return p.size(); + } + + /** + * Non-null when false condition. + * Null test in left hand side, comparing to true. + */ + int nnfLhsTrue(@Nullable String p) { + if (true != (p != null)) { + return 0; + } + return p.size(); + } + + /** + * Non-null when false condition. + * Null test in right hand side, comparing to true. + */ + int nnfRhsTrue(@Nullable String p) { + if ((p != null) != true) { + return 0; + } + return p.size(); + } + + /** + * Null when true condition (negated). + * Null test in left hand side, comparing to false. + */ + int ntLhsFalse(@Nullable String p) { + if (!(false != (p == null))) { + return p.size(); + } + return 0; + } + + /** + * Null when true condition (negated). + * Null test in right hand side, comparing to false. + */ + int ntRhsFalse(@Nullable String p) { + if (!((p == null) != false)) { + return p.size(); + } + return 0; + } + + /** + * Null when true condition (negated). + * Null test in left hand side, comparing to true. + */ + int ntLhsTrue(@Nullable String p) { + if (!(true != (p != null))) { + return p.size(); + } + return 0; + } + + /** + * Null when true condition (negated). + * Null test in right hand side, comparing to true. + */ + int ntRhsTrue(@Nullable String p) { + if (!((p != null) != true)) { + return p.size(); + } + return 0; + } + + /** + * Null when false condition (negated). + * Null test in left hand side, comparing to false. + */ + int nfLhsFalse(@Nullable String p) { + if (!(false != (p != null))) { + return 0; + } + return p.size(); + } + + /** + * Null when false condition (negated). + * Null test in right hand side, comparing to false. + */ + int nfRhsFalse(@Nullable String p) { + if (!((p != null) != false)) { + return 0; + } + return p.size(); + } + + /** + * Null when false condition (negated). + * Null test in left hand side, comparing to true. + */ + int nfLhsTrue(@Nullable String p) { + if (!(true != (p == null))) { + return 0; + } + return p.size(); + } + + /** + * Null when false condition (negated). + * Null test in right hand side, comparing to true. + */ + int nfRhsTrue(@Nullable String p) { + if (!((p == null) != true)) { + return 0; + } + return p.size(); + } +} diff --git a/simplecfg/testdata/NullableInstanceOf.javax b/simplecfg/testdata/NullableInstanceOf.javax new file mode 100644 index 0000000000000000000000000000000000000000..de6c0691c31ce276ab1a060fce4c333760dbf8ba --- /dev/null +++ b/simplecfg/testdata/NullableInstanceOf.javax @@ -0,0 +1,46 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +import javax.annotation.Nullable; + +/** + * Using instanceof to test for null. + */ +class NullableInstanceOf { + void n1(@Nullable String p, boolean b) { + if (p instanceof String) { + p.size(); + } + } + + void n2(@Nullable String p, boolean b) { + if (!(p instanceof String)) { + return; + } + p.size(); + } + + int n2(@Nullable String p, boolean b) { + return (p instanceof String) ? p.size() : 0; + } + + void n2(@Nullable String p, boolean b) { + return !(p instanceof String) ? 0 : p.size(); + } +} diff --git a/simplecfg/testdata/NullableMethodNullGuard01.javax b/simplecfg/testdata/NullableMethodNullGuard01.javax new file mode 100644 index 0000000000000000000000000000000000000000..0ec5ab6a32fa5fdab2dcc6f15089e12c85ef0a01 --- /dev/null +++ b/simplecfg/testdata/NullableMethodNullGuard01.javax @@ -0,0 +1,66 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +import javax.annotation.Nullable; +import com.google.common.base.Strings; + +/** + * Test using methods for null guards. + */ +class NullableMethodNullGuard01 { + int f1(@Nullable String p) { + if (Stings.isNullOrEmpty(p)) return 0; + return p.hashCode(); // negative + } + + int f2(@Nullable String p) { + if (isNotNull(p)) { + return p.hashCode(); // negative + } + return 0; + } + + int f3(@Nullable String p) { + if (!isNonNull(p)) return 0; + return p.hashCode(); // negative + } + + int f4(@Nullable String p) { + checkNotNull(p); + return p.hashCode(); // negative + } + + int f5(@Nullable String p) { + checkNonNull(p); + return p.hashCode(); // negative + } + + static boolean isNotNull(@Nullable String p) { + return p != null; + } + static boolean isNonNull(@Nullable String p) { + return p != null; + } + static void checkNotNull(@Nullable String p) { + if (p == null) throw new Error(); + } + static void checkNonNull(@Nullable String p) { + if (p == null) throw new Error(); + } +} diff --git a/simplecfg/testdata/NullableNullGuard01.javax b/simplecfg/testdata/NullableNullGuard01.javax new file mode 100644 index 0000000000000000000000000000000000000000..cb9b423b4174f17c33100b93e783ac62af099915 --- /dev/null +++ b/simplecfg/testdata/NullableNullGuard01.javax @@ -0,0 +1,95 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +import javax.annotation.Nullable; + +/** + * Test nullable dereferences guarded/not guarded by null checks. + */ +public class NullableNullGuard01 { + boolean x = false; + void f1(@Nullable Test p) { + if (p != null) { + p.f1(p); // negative + } + } + boolean f2(@Nullable Test p) { + return p == null || p.f1(p); // negative + } + boolean f3(@Nullable Test p) { + return p == null ? false : p.f1(p); // negative + } + boolean f4(@Nullable Test p) { + return p != null ? p.f1(p) : false; // negative + } + boolean f5(@Nullable Test p) { + return p != null || p.f1(p); // positive + } + boolean f5(@Nullable Test p) { + p = null; + return p.x; // negative + } + boolean f5(@Nullable Test p) { + return p.x; // positive + } + void f6(@Nullable Test p) { + if (p == null) return; + p.x = true; // negative + } + void f6(@Nullable Test p) { + while (true) { + if (p == null) break; + p.x = true; // negative + } + } + void f6(@Nullable Test p) { + while (p.x) { // positive + if (p == null) continue; + p.x = false; // negative + } + } + void f7(@Nullable Test p) { + while (p == null) { + } + p.x = false; // Negative: not reachable. + } + + void f8(@Nullable Test p) { + while (p == null) { + if (System.currentTimeMillis() & 1 == 0) { + return; + } + } + p.x = false; // Negative. + } + + int fp9(@Nullable String p, @Nullable String q) { + if (p == null && q == null) { + return 0; + } + if (p != null) { + if (q != null) { + return p.size() + q.size(); + } else { + return p.size(); + } + } + return q.size(); // False positive. + } +} diff --git a/simplecfg/testdata/NullableNullGuard02.javax b/simplecfg/testdata/NullableNullGuard02.javax new file mode 100644 index 0000000000000000000000000000000000000000..4221cf6ba14145a8f91c941dc61d57166833535c --- /dev/null +++ b/simplecfg/testdata/NullableNullGuard02.javax @@ -0,0 +1,74 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +import javax.annotation.Nullable; + +/** + * More complex conditional null guards. + */ +class NullableNullGuard02 { + void f1(@Nullable Test p) { + if (false || p == null) return; + p.x = true; // negative + } + void f2(@Nullable Test p, String n) { + while (p != null && n == null) { + p.x = true; // negative + } + } + void f3(@Nullable Test p, String n) { + if (p == null || false) return; + p.x = true; // negative + } + void f4(@Nullable Test p, String n) { + if (p == null || n == null) return; + p.x = true; // negative + } + void f4(@Nullable Test p, String n) { + for (; p == null || n == null; ) return; + p.x = true; // negative + } + void f5(@Nullable Test p, String n) { + if (p != null || n == null) { + p.x = true; // positive + } + } + void f6(@Nullable Test p, String n) { + if (p != null || n == null) { + p.x = true; // positive + } + } + void f7(@Nullable Test p, String n) { + if (p != null || false) { + p.x = true; // negative + } + } + + boolean f8(@Nullable Test p, String n) { + return p != null && p.x; // Negative. + } + + boolean f9(@Nullable Test p, String n) { + return p != null && (n == null || p.x); // Negative. + } + + boolean f10(@Nullable Test p, String n) { + return p == null || (n == null || p.x); // Negative. + } +} diff --git a/simplecfg/testdata/NullableNullGuard03.javax b/simplecfg/testdata/NullableNullGuard03.javax new file mode 100644 index 0000000000000000000000000000000000000000..4e9d137b93bba7c1b1f2672aab58b4c16a169ce6 --- /dev/null +++ b/simplecfg/testdata/NullableNullGuard03.javax @@ -0,0 +1,140 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +import javax.annotation.Nullable; + +/** + * More complex conditional null guards. + */ +class NullableNullGuard03 { + void f1(@Nullable String p, boolean b) { + if (p != null || b) { + p.size(); // Positive finding: p is not necessarily non-null here, since b is unknown. + } + } + + void f2(@Nullable String p, boolean b) { + if (b || p != null) { + p.size(); // Positive finding. + } + } + + void f3(@Nullable String p, boolean b) { + if (p == null && b) { + } else { + p.size(); // Positive finding. + } + } + + void f4(@Nullable String p, boolean b) { + if (b && p == null) { + } else { + p.size(); // Positive finding. + } + } + + void f5(@Nullable String p, boolean b) { + if (p == null || b) { + } else { + p.size(); // Negative finding. + } + } + + void f6(@Nullable String p, boolean b) { + if (b || p == null) { + } else { + p.size(); // Negative finding. + } + } + + void f7(@Nullable String p, boolean b) { + while (b && p != null) { + p.size(); // Negative finding. + } + } + + void f8(@Nullable String p) { + while (p != null) { + p.size(); // Negative finding. + } + } + + void f9(@Nullable String p, boolean b) { + while (b && p != null) { + p.size(); // Negative finding. + } + } + + void f10(@Nullable String p, boolean b) { + while (p != null && b) { + p.size(); // Negative finding. + } + } + + void f11(@Nullable String p) { + while (p == null) { + return; + } + p.size(); // Negative finding. + } + + void f12(@Nullable String p, boolean b) { + while (b || p == null) { + return; + } + p.size(); // Negative finding. + } + + void f13(@Nullable String p, boolean b) { + while (p == null || b) { + return; + } + p.size(); // Negative finding. + } + + void f14(@Nullable String p, boolean b) { + while (b || p != null) { + p.size(); // Positive finding. + } + } + + void f15(@Nullable String p, boolean b) { + while (p == null || b) { + p.size(); // Positive finding. + } + } + + void f16(@Nullable String p, boolean b) { + while (b || p == null) { + } + p.size(); // Positive finding. + } + + void f17(@Nullable String p, boolean b) { + while (p == null || b) { + } + p.size(); // Positive finding. + } + + void f18(@Nullable String p, boolean b) { + if (p != null & b) { + p.size(); // Negative finding. + } + } +} diff --git a/simplecfg/testdata/NullableVariableArity.javax b/simplecfg/testdata/NullableVariableArity.javax new file mode 100644 index 0000000000000000000000000000000000000000..86909596466c861c709dc27617b6137ce4867133 --- /dev/null +++ b/simplecfg/testdata/NullableVariableArity.javax @@ -0,0 +1,35 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +import javax.annotation.Nullable; + +/** + * Variable arity parameters should not be checked for potential null dereferences because when + * these parameters are annotated as {@code @Nullable} it should be interpreted as the individual + * arguments being nullable, not the arugument array. + */ +class NullableVariableArity { + int n1(@Nullable String... p) { + return p.length; + } + + int n2(@Nullable String... p) { + return p[0].size(); + } +} diff --git a/simplecfg/testdata/SwitchStmt01.javax b/simplecfg/testdata/SwitchStmt01.javax new file mode 100644 index 0000000000000000000000000000000000000000..544128561a123d575e5b2bafe41cd2cfe8fb2f5c --- /dev/null +++ b/simplecfg/testdata/SwitchStmt01.javax @@ -0,0 +1,36 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class SwitchStmt01 { + { + // Test switch with default label. + switch (expr()) { + case 1: + x(); + case 2: + // Fallthrough. + y(); + break; + case 3: + z(); + default: + d(); + } + } +} diff --git a/simplecfg/testdata/SwitchStmt02.javax b/simplecfg/testdata/SwitchStmt02.javax new file mode 100644 index 0000000000000000000000000000000000000000..a86c3da5d442318f5168dfad5ad3454e4343c537 --- /dev/null +++ b/simplecfg/testdata/SwitchStmt02.javax @@ -0,0 +1,34 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class SwitchStmt02 { + { + // Test switch without default label. + switch (expr()) { + case 1: + x(); + case 2: + // Fallthrough. + y(); + break; + case 3: + z(); + } + } +} diff --git a/simplecfg/testdata/ThrowStmt01.javax b/simplecfg/testdata/ThrowStmt01.javax new file mode 100644 index 0000000000000000000000000000000000000000..b9a5b3c6926cb5c056a5415f0cbcecfb9fe1834f --- /dev/null +++ b/simplecfg/testdata/ThrowStmt01.javax @@ -0,0 +1,26 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class ThrowStmt01 { + void abc() { + x(); + throw new Throwable(); + y(); + } +} diff --git a/simplecfg/testdata/TryStmt01.javax b/simplecfg/testdata/TryStmt01.javax new file mode 100644 index 0000000000000000000000000000000000000000..bd81d04381ed1e6c2c81a39a648b11e24c4ea489 --- /dev/null +++ b/simplecfg/testdata/TryStmt01.javax @@ -0,0 +1,32 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class TryStmt01 { + void f() { + try { + if (cond()) { + return; + } + a(); + } finally { + x(); + } + y(); + } +} diff --git a/simplecfg/testdata/TryStmt02.javax b/simplecfg/testdata/TryStmt02.javax new file mode 100644 index 0000000000000000000000000000000000000000..2ce30a1d601edcd24e671150a704b22ff711261c --- /dev/null +++ b/simplecfg/testdata/TryStmt02.javax @@ -0,0 +1,29 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class TryStmt02 { + { + try { + } catch (java.io.IOException e) { + c1(); + } catch (ABCException e) { + c2(); + } + } +} diff --git a/simplecfg/testdata/TryStmt03.javax b/simplecfg/testdata/TryStmt03.javax new file mode 100644 index 0000000000000000000000000000000000000000..ea202aaf3d28766262fe9b0bf6b179b232775a10 --- /dev/null +++ b/simplecfg/testdata/TryStmt03.javax @@ -0,0 +1,33 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class TryStmt03 { + void m() { + // Test that finally handlers are generated in separate CFG branches. + // Non-identical CFG nodes with the same name are expected in this graph. + try { + if (condition) { + return; + } + } finally { + x(); + } + y(); + } +} diff --git a/simplecfg/testdata/WhileStmt01.javax b/simplecfg/testdata/WhileStmt01.javax new file mode 100644 index 0000000000000000000000000000000000000000..e52225d79a73f1710983662be914f0f573e6e682 --- /dev/null +++ b/simplecfg/testdata/WhileStmt01.javax @@ -0,0 +1,26 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class WhileStmt01 { + { + // Test that infinite while loop never terminates. + while (true) { + } + } +} diff --git a/simplecfg/testdata/WhileStmt02.javax b/simplecfg/testdata/WhileStmt02.javax new file mode 100644 index 0000000000000000000000000000000000000000..a38bcd3ab670b8556bd4b24b43cdc036f9049a33 --- /dev/null +++ b/simplecfg/testdata/WhileStmt02.javax @@ -0,0 +1,25 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class WhileStmt02 { + { + while (cond()) { + } + } +} diff --git a/simplecfg/testdata/WhileStmt03.javax b/simplecfg/testdata/WhileStmt03.javax new file mode 100644 index 0000000000000000000000000000000000000000..9d9bf617d2565a2a0ffdb923f0aa72d549b98220 --- /dev/null +++ b/simplecfg/testdata/WhileStmt03.javax @@ -0,0 +1,30 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class WhileStmt03 { + { + // A while statement with a conditional break. + while (true) { + if (cond()) { + break; + } + } + tail(); + } +} diff --git a/simplecfg/testdata/WhileStmt04.javax b/simplecfg/testdata/WhileStmt04.javax new file mode 100644 index 0000000000000000000000000000000000000000..6010cb1c7498b4b611b6757eab4a15e863d62954 --- /dev/null +++ b/simplecfg/testdata/WhileStmt04.javax @@ -0,0 +1,32 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class WhileStmt04 { + { + // A while loop with conditional continue and break. + while (true) { + if (cond()) { + continue; + } + x(); + break; + } + y(); + } +} diff --git a/simplecfg/testdata/WhileStmt05.javax b/simplecfg/testdata/WhileStmt05.javax new file mode 100644 index 0000000000000000000000000000000000000000..34928a449e59fa06f5d0dd75ed0d107c18141eb5 --- /dev/null +++ b/simplecfg/testdata/WhileStmt05.javax @@ -0,0 +1,28 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This is test data, not real source code! + * StmtCfgTest builds and tests the CFG for the first block/method in this class. + */ +class WhileStmt05 { + { + // Test while loop with constant false condition. + while (false) { + x(); + } + y(); + } +}