commit b8982270f2423864c236ff8dcdbeb5cd82aa6002 Author: Open Source Synchronization Date: Tue Jun 9 17:35:48 2015 +0000 initial synchronization diff --git a/.buckconfig b/.buckconfig new file mode 100644 index 000000000..42c021112 --- /dev/null +++ b/.buckconfig @@ -0,0 +1,25 @@ + +[alias] + endtoend_test_objc = //infer/tests/endtoend:objc_endtoend_tests + frontend_test_objc = //infer/tests/frontend:objc_frontend_tests + + endtoend_test_java = //infer/tests/endtoend:java_endtoend_tests + endtoend_test_c = //infer/tests/endtoend:edgc_endtoend_tests + + integration_tests = //infer/tests:integration_tests + objc = //infer/tests:objc_tests + c = //infer/tests:c_tests + cpp = //infer/tests:cpp_tests + objcpp = //infer/tests:objcpp_tests + clang = //infer/tests:clang_tests + java = //infer/tests/endtoend:java_endtoend_tests + + java_libraries = //dependencies/java:java_libraries + + infer = //infer/tests/endtoend/java/infer:infer + eradicate = //infer/tests/endtoend/java/eradicate:eradicate + checkers = //infer/tests/endtoend/java/checkers:checkers + tracing = //infer/tests/endtoend/java/tracing:tracing + +[project] + ignore = .git, .ml, .mli diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..ec9ca3c2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,89 @@ +# Generated files # +################### +*.pyc +*.specs +*.cm* +*.o +*~ +*.swp +*.annot +*.class +*.log +*.orig +*.rej + +# Directories generated by Ocamlbuild +_build + +# IntelliJ files +scripts/.idea/ +infer/tests/.idea/dictionaries +infer/tests/.idea/inspectionProfiles +infer/tests/.idea/tasks.xml +infer/tests/.idea/uiDesigner.xml +infer/tests/.idea/workspace.xml +infer/tests/.idea/misc.xml +infer/tests/.idea/runConfigurations +infer/tests/.idea/copyright/profiles_settings.xml + +# Eclipse settings file +.project +.cproject +.paths +.pydevproject +.settings/ +.classpath + +# Arcanist +/arcanist/.phutil_module_cache + +# MacOS generated files +.DS_Store + +# Directories and files generated by Infer +infer-out/ +*.o.astlog +*.o.sh + +# Directories generated by buck +buck-out/ +.buckd/ + +#other +/infer/bin/InferAnalyze +/infer/bin/InferClang +/infer/bin/InferJava +/infer/bin/InferPrint +/infer/bin/Typeprop +/infer/src/backend/version.ml +infer/models/java/models/ +infer/lib/java/models.jar +infer/models/java/bootclasspath +infer/lib/specs/c_models +infer/lib/specs/cpp_models +infer/lib/specs/objc_models +infer/lib/specs/clean_models +dependencies/clang-plugin/clang-plugin-version.done +include/ +share/ + +#atdgen stubs +infer/src/backend/jsonbug_* + +# intelliJ files +.idea +*.iml + +/infer/src/backend/.projectSettings +infer/models/out/ + +/infer/_build-infer/ + +/infer/src/clang/clang_ast_j.ml +/infer/src/clang/clang_ast_j.mli +/infer/src/clang/clang_ast_proj.ml +/infer/src/clang/clang_ast_proj.mli +/infer/src/clang/clang_ast_t.ml +/infer/src/clang/clang_ast_t.mli + +/infer/annotations/annotations.jar diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..347e7b52b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1 @@ +We require contributors to sign our Contributor License Agreement. In order for us to review and merge your code, please sign up at https://code.facebook.com/cla. If you have any questions, please drop us a line at cla@fb.com. Thanks! diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 000000000..9c356d0b5 --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,134 @@ +#Install Infer + +We provide pre-built Infer binaries for Linux and MacOS. +If you just wish to use Infer, and are not interested in making contributions to it, then these binaries are all you need. +Otherwise, if you wish to compile Infer, here are also instructions to do so, depending on your operating system. + +- [Install the Infer binaries](INSTALL.md#Install-the-Infer-binaries) + - [Mac OS X](INSTALL.md#Mac-OS-X) + - [Linux](INSTALL.md#Linux) +- [Install Infer from source](INSTALL.md#Install-Infer-from-source) + - [Download Infer](INSTALL.md#Download-Infer) + - [Mac OS X](INSTALL.md#Mac-OS-X) + - [Linux](INSTALL.md#Linux) + +##Install the Infer binaries + +###Mac OS X + +Get the latest `infer-osx-vXX.tar.xz` from [infer releases](https://github.com/facebook/infer/releases) and run the commands below in your terminal to install Infer. + + ```bash +tar xf infer-osx-vXX.tar.xz +# this assumes you use bash, adapt to your needs in case you use +# another shell +echo "export PATH=$PATH:`pwd`/infer-osx/infer/infer/bin" \ + >> ~/.bashrc && source ~/.bashrc +``` + +###Linux (64 bit) + +Get the latest `infer-linux64-vXX.tar.xz` from [infer releases](https://github.com/facebook/infer/releases) and run the commands below in your terminal to install Infer. + + ```bash +tar xf infer-linux64-vXX.tar.xz +# this assumes you use bash, adapt to your needs in case you use +# another shell +echo "export PATH=$PATH:`pwd`/infer-0.1-x64-linux/infer/infer/bin" \ + >> ~/.bashrc && source ~/.bashrc +``` + + +##Install Infer from source + +The following instructions describe how to compile Infer on different platforms. + +### Download the Infer repository + +git clone https://github.com/facebook/infer.git + +To analyse C and ObjC, Infer requires clang and the [facebook-clang-plugin](https://github.com/facebook/facebook-clang-plugins). If you wish to analyse only Java/Android code, then you could skip these dependencies. Details below. + +###MacOS X + +####Requirements + +- `opam` (Instructions [here](https://opam.ocaml.org/doc/Install.html#OSX)) + +##### Requirements for Java analysis +- `Java <= 1.7` +- Android dev setup for analysis of Android apps. + + +##### Requirements for C/ObjC analysis +- `XCode <= 6.3, >= 6.1` +- `clang` (XCode command line tools. You can install them with the command `xcode-select --install`) + + +###Installation instructions + +Install OCaml dependencies: +```bash +opam init --comp=4.01.0 # (answer 'y' to the question) +opam install sawja.1.5 atdgen.1.5.0 javalib.2.3 extlib.1.5.4 +``` + +If you do not require support for the C/Objective C analysis in Infer, and only wish to analyse Java files, continue with these instructions. By the way, Java 1.8 is not supported. + +```bash +cd infer +make -C infer java +export PATH=`pwd`/infer/bin:$PATH +``` +To compile support for both Java and C/Objective C, do this instead. + +```bash +cd infer +./update-fcp.sh && ../facebook-clang-plugin/clang/setup.sh && ./compile-fcp.sh # go have a coffee :) +make -C infer +export PATH=`pwd`/infer/bin:$PATH +``` + +###Linux + +These instructions were tested on Linux (64 Bit), on the following distributions: Debian 7, Ubuntu 14.04 and Ubuntu 12.04.4 LTS. + +Install OCaml dependencies: +```bash +sudo apt-get update +sudo apt-get upgrade +sudo apt-get install git openjdk-7-jdk m4 zlib1g-dev python-software-properties build-essential libgmp-dev libmpfr-dev libmpc-dev unzip +wget https://github.com/ocaml/opam/releases/download/1.2.2/opam-1.2.2-x86_64-Linux -O opam +chmod +x opam +./opam init --comp=4.01.0 #(then say 'y' to the final question) +eval `./opam config env` +./opam install sawja.1.5 atdgen.1.5.0 javalib.2.3 extlib.1.5.4 #(then say 'y' to the question) +``` +​If you do not require support for the C/Objective C analysis in Infer, and only wish to analyse Java files, continue with these instructions. By the way, Java 1.8 is not supported. + +```bash +cd infer +make -C infer java +export PATH=`pwd`/infer/bin:$PATH +``` + +To compile support for both Java and C/Objective C, do this instead. If your distribution is Ubuntu 12.04.4 LTS, you need to install `gcc 4.8` and `g++ 4.8` as well. Follow the following instructions to do that. You may skip this step in other distributions. + +```bash +sudo apt-get install python-software-properties +sudo add-apt-repository ppa:ubuntu-toolchain-r/test +sudo apt-get update +sudo apt-get install gcc-4.8 g++-4.8 +sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 60 --slave /usr/bin/g++ g++ /usr/bin/g++-4.8 +``` + +Then continue with: + +```bash +cd infer +./update-fcp.sh +../facebook-clang-plugin/clang/setup.sh # go have a coffee :) +./compile-fcp.sh +make -C infer +export PATH=`pwd`/infer/bin:$PATH +``` \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..8be7b96b8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,30 @@ +BSD License + +For Infer software + +Copyright (c) 2013-, Facebook, Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +* Neither the name Facebook nor the names of its contributors may be used to +endorse or promote products derived from this software without specific +prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/PATENTS b/PATENTS new file mode 100644 index 000000000..ec1e1ad34 --- /dev/null +++ b/PATENTS @@ -0,0 +1,33 @@ +Additional Grant of Patent Rights Version 2 + +"Software" means the Infer software distributed by Facebook, Inc. + +Facebook, Inc. (“Facebook”) hereby grants to each recipient of the Software +(“you”) a perpetual, worldwide, royalty-free, non-exclusive, irrevocable +(subject to the termination provision below) license under any Necessary +Claims, to make, have made, use, sell, offer to sell, import, and otherwise +transfer the Software. For avoidance of doubt, no license is granted under +Facebook's rights in any patent claims that are infringed by (i) modifications +to the Software made by you or any third party or (ii) the Software in +combination with any software or other technology. + +The license granted hereunder will terminate, automatically and without notice, +if you (or any of your subsidiaries, corporate affiliates or agents) initiate +directly or indirectly, or take a direct financial interest in, any Patent +Assertion: (i) against Facebook or any of its subsidiaries or corporate +affiliates, (ii) against any party if such Patent Assertion arises in whole or +in part from any software, technology, product or service of Facebook or any of +its subsidiaries or corporate affiliates, or (iii) against any party relating +to the Software. Notwithstanding the foregoing, if Facebook or any of its +subsidiaries or corporate affiliates files a lawsuit alleging patent +infringement against you in the first instance, and you respond by filing a +patent infringement counterclaim in that lawsuit against that party that is +unrelated to the Software, the license granted hereunder will not terminate +under section (i) of this paragraph due to such counterclaim. + +A "Necessary Claim" is a claim of a patent owned by Facebook that is +necessarily infringed by the Software standing alone. + +A "Patent Assertion" is any lawsuit or other action alleging direct, indirect, +or contributory infringement or inducement to infringe any patent, including a +cross-claim or counterclaim. diff --git a/README.md b/README.md new file mode 100644 index 000000000..55f61e4e9 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +Infer +===== + +Infer is a static analysis tool for Java and C / Objective C. +To see what it can do for you, check out the documentation at . + +Installation +------------ + +Read the [INSTALL.md](INSTALL.md) file for details on installing Infer. + +License +------- +Infer is BSD-licensed. We also provide an additional patent grant. + diff --git a/compile-fcp.sh b/compile-fcp.sh new file mode 100755 index 000000000..180c99183 --- /dev/null +++ b/compile-fcp.sh @@ -0,0 +1,48 @@ +#!/bin/bash +set -e +set -x + +# This script installs the facebook-clang-plugins +# +# TODO (t5939566): ADD INSTRUCTIONS ON HOW TO CUSTOMIZE THE ENVVARS FOR +# THE INSTALLATION OF THE PLUGINS. + +INFER_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +PLUGIN_DIR="$INFER_ROOT/../facebook-clang-plugin" +CLANG_EXEC="$PLUGIN_DIR/clang/bin/clang" + +# check if clang is available +if ! $CLANG_EXEC --version 2>&1 | grep -q '3\.6'; then + echo "The required version of clang has not been found in $CLANG_EXEC" && exit 1 +fi + +# install facebook-clang-plugins +pushd "$PLUGIN_DIR" +# prepare flags for the compilation on the Linux platform +platform="$(uname)" +if [ "$platform" == 'Linux' ]; then + export SDKPATH="" + export PATH="$PLUGIN_DIR/clang/bin:$PATH" + [ -z "$CC" ] && export CC="$PLUGIN_DIR/clang/bin/clang" + [ -z "$CXX" ] && export CXX="$PLUGIN_DIR/clang/bin/clang++" + [ -z "$CFLAGS" ] && export CFLAGS="-std=c++11 -fPIC" + [ -z "$LDFLAGS" ] && export LDFLAGS="-shared" + [ -z "$CLANG_PREFIX" ] && export CLANG_PREFIX="$PLUGIN_DIR/clang" + [ -z "$LLVM_INCLUDES" ] && export LLVM_INCLUDES="$PLUGIN_DIR/clang/include" + [ -z "$CLANG_INCLUDES" ] && export CLANG_INCLUDES="$LLVM_INCLUDES $CLANG_PREFIX/include" +fi + +# compile +make clean +make -C clang-ocaml clean +make +make -C clang-ocaml all build/clang_ast_proj.ml build/clang_ast_proj.mli +popd + +# check YojsonASTExporter works with clang +echo "int main() { return 0; }" | \ + $CLANG_EXEC -o /dev/null -x c \ + -Xclang -load -Xclang $PLUGIN_DIR/libtooling/build/FacebookClangPlugin.dylib \ + -Xclang -plugin -Xclang YojsonASTExporter -c - > /dev/null \ +|| { echo "$CLANG_EXEC and the facebook-clang-plugins are not working."; + echo "Check you're using the right revision of clang, then retry"; exit 1; } diff --git a/dependencies/clang-plugin/clang-plugin-version.config b/dependencies/clang-plugin/clang-plugin-version.config new file mode 100644 index 000000000..9cc08bb46 --- /dev/null +++ b/dependencies/clang-plugin/clang-plugin-version.config @@ -0,0 +1 @@ +ab60f2578bf51a8b58afa63de7cee65d7ad891c9 diff --git a/dependencies/java/BUCK b/dependencies/java/BUCK new file mode 100644 index 000000000..1c121abc8 --- /dev/null +++ b/dependencies/java/BUCK @@ -0,0 +1,17 @@ +java_library( + name = 'java_libraries', + deps=[ + '//dependencies/java/guava:guava', + '//dependencies/java/junit:hamcrest', + '//dependencies/java/jackson:jackson', + '//dependencies/java/jsr-305:jsr-305', + '//dependencies/java/junit:junit', + '//dependencies/java/opencsv:opencsv' + ], + visibility = [ + 'PUBLIC' + ] +) +project_config( + src_target = ':java_libraries', +) diff --git a/dependencies/java/guava/BUCK b/dependencies/java/guava/BUCK new file mode 100644 index 000000000..db88f56a3 --- /dev/null +++ b/dependencies/java/guava/BUCK @@ -0,0 +1,7 @@ +prebuilt_jar( + name = 'guava', + binary_jar = 'guava-10.0.1-fork.jar', + visibility = [ + 'PUBLIC' + ] +) diff --git a/dependencies/java/guava/guava-10.0.1-fork.jar b/dependencies/java/guava/guava-10.0.1-fork.jar new file mode 100644 index 000000000..dc94d63e8 Binary files /dev/null and b/dependencies/java/guava/guava-10.0.1-fork.jar differ diff --git a/dependencies/java/jackson/BUCK b/dependencies/java/jackson/BUCK new file mode 100644 index 000000000..2a27c2035 --- /dev/null +++ b/dependencies/java/jackson/BUCK @@ -0,0 +1,10 @@ +prebuilt_jar( + name = 'jackson', + binary_jar = 'jackson-2.2.3.jar', + deps = [ + '//dependencies/java/guava:guava', + ], + visibility = [ + 'PUBLIC', + ] +) diff --git a/dependencies/java/jackson/jackson-2.2.3.jar b/dependencies/java/jackson/jackson-2.2.3.jar new file mode 100644 index 000000000..365db3075 Binary files /dev/null and b/dependencies/java/jackson/jackson-2.2.3.jar differ diff --git a/dependencies/java/jsr-305/BUCK b/dependencies/java/jsr-305/BUCK new file mode 100644 index 000000000..eb83f9091 --- /dev/null +++ b/dependencies/java/jsr-305/BUCK @@ -0,0 +1,8 @@ +prebuilt_jar( + name = 'jsr-305', + binary_jar = 'jsr305.jar', + source_jar = 'jsr305-src.jar', + visibility = [ + 'PUBLIC', + ], +) diff --git a/dependencies/java/jsr-305/jsr305-src.jar b/dependencies/java/jsr-305/jsr305-src.jar new file mode 100644 index 000000000..7a61b2430 Binary files /dev/null and b/dependencies/java/jsr-305/jsr305-src.jar differ diff --git a/dependencies/java/jsr-305/jsr305.jar b/dependencies/java/jsr-305/jsr305.jar new file mode 100644 index 000000000..633f8450d Binary files /dev/null and b/dependencies/java/jsr-305/jsr305.jar differ diff --git a/dependencies/java/junit/BUCK b/dependencies/java/junit/BUCK new file mode 100644 index 000000000..1e20f558d --- /dev/null +++ b/dependencies/java/junit/BUCK @@ -0,0 +1,20 @@ + +prebuilt_jar( + name = 'hamcrest', + binary_jar = 'hamcrest-core-1.3.jar', + visibility = [ + 'PUBLIC' + ] +) + +prebuilt_jar( + name = 'junit', + binary_jar = 'junit-4.11.jar', + deps = [ + ':hamcrest' + ], + visibility = [ + 'PUBLIC' + ] +) + diff --git a/dependencies/java/junit/hamcrest-core-1.3.jar b/dependencies/java/junit/hamcrest-core-1.3.jar new file mode 100755 index 000000000..9d5fe16e3 Binary files /dev/null and b/dependencies/java/junit/hamcrest-core-1.3.jar differ diff --git a/dependencies/java/junit/junit-4.11.jar b/dependencies/java/junit/junit-4.11.jar new file mode 100755 index 000000000..aaf744484 Binary files /dev/null and b/dependencies/java/junit/junit-4.11.jar differ diff --git a/dependencies/java/opencsv/BUCK b/dependencies/java/opencsv/BUCK new file mode 100644 index 000000000..4ad693de6 --- /dev/null +++ b/dependencies/java/opencsv/BUCK @@ -0,0 +1,7 @@ +prebuilt_jar( + name = 'opencsv', + binary_jar = 'opencsv-2.3.jar', + visibility = [ + 'PUBLIC' + ] +) diff --git a/dependencies/java/opencsv/license.txt b/dependencies/java/opencsv/license.txt new file mode 100644 index 000000000..dedecdcf6 --- /dev/null +++ b/dependencies/java/opencsv/license.txt @@ -0,0 +1 @@ +http://www.apache.org/licenses/LICENSE-2.0 diff --git a/dependencies/java/opencsv/opencsv-2.3.jar b/dependencies/java/opencsv/opencsv-2.3.jar new file mode 100644 index 000000000..32b00f927 Binary files /dev/null and b/dependencies/java/opencsv/opencsv-2.3.jar differ diff --git a/examples/Hello.java b/examples/Hello.java new file mode 100644 index 000000000..baf9fea69 --- /dev/null +++ b/examples/Hello.java @@ -0,0 +1,6 @@ +class Hello { + int test() { + String s = null; + return s.length(); + } +} diff --git a/examples/Hello.m b/examples/Hello.m new file mode 100644 index 000000000..d443e8d49 --- /dev/null +++ b/examples/Hello.m @@ -0,0 +1,12 @@ +#import + +@interface Hello: NSObject +@property NSString* s; +@end + +@implementation Hello +NSString* m() { + Hello* hello = nil; + return hello->_s; +} +@end diff --git a/examples/README b/examples/README new file mode 100644 index 000000000..36714506b --- /dev/null +++ b/examples/README @@ -0,0 +1,39 @@ +This directory contains small examples to play with Infer. They each exhibit +one simple programming error that is caught by Infer. + +Contents +-------- + +- Hello.java: try this example by running + infer -- javac Hello.java + +- Hello.m: try this example by running + infer -- clang -c Hello.m + +- hello.c: try this example by running + infer -- gcc -c hello.c + + In this case, note that Infer captures the gcc command and runs + clang instead to parse C files. Thus you may get compiler errors and + warnings that differ from gcc's. + +- android_hello/: a sample Android app. Try this example by running + infer -- ./gradlew build + + Make sure that you have the Android SDK 22 installed and up to date, and in + particular the "Android SDK Build-tools" and "Android Support Repository". + +- ios_hello/: a sample iOS app. Try this example by running + infer -- xcodebuild -target HelloWorldApp -configuration Debug -sdk iphonesimulator + +- c_hello/: a sample make-based C project. Try this example by running + infer -- make + + +Note +---- + +The infer toplevel command must be in your PATH for the commands above to +succeed. Otherwise, modify the commands to use the correct path to infer, eg + ../infer/bin/infer -- javac Hello.java + diff --git a/examples/android_hello/.gitignore b/examples/android_hello/.gitignore new file mode 100644 index 000000000..afbdab33e --- /dev/null +++ b/examples/android_hello/.gitignore @@ -0,0 +1,6 @@ +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build diff --git a/examples/android_hello/app/.gitignore b/examples/android_hello/app/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/examples/android_hello/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/examples/android_hello/app/build.gradle b/examples/android_hello/app/build.gradle new file mode 100644 index 000000000..9aa67a78c --- /dev/null +++ b/examples/android_hello/app/build.gradle @@ -0,0 +1,25 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 22 + buildToolsVersion "22.0.1" + + defaultConfig { + applicationId "infer.inferandroidexample" + minSdkVersion 8 + targetSdkVersion 22 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + compile 'com.android.support:appcompat-v7:22.0.0' +} diff --git a/examples/android_hello/app/proguard-rules.pro b/examples/android_hello/app/proguard-rules.pro new file mode 100644 index 000000000..5dd337939 --- /dev/null +++ b/examples/android_hello/app/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/irp/android-sdk-macosx/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/examples/android_hello/app/src/androidTest/java/infer/inferandroidexample/ApplicationTest.java b/examples/android_hello/app/src/androidTest/java/infer/inferandroidexample/ApplicationTest.java new file mode 100644 index 000000000..498179e34 --- /dev/null +++ b/examples/android_hello/app/src/androidTest/java/infer/inferandroidexample/ApplicationTest.java @@ -0,0 +1,13 @@ +package infer.inferandroidexample; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} diff --git a/examples/android_hello/app/src/main/AndroidManifest.xml b/examples/android_hello/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..14a7fc432 --- /dev/null +++ b/examples/android_hello/app/src/main/AndroidManifest.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + diff --git a/examples/android_hello/app/src/main/java/infer/inferandroidexample/MainActivity.java b/examples/android_hello/app/src/main/java/infer/inferandroidexample/MainActivity.java new file mode 100644 index 000000000..4e1e87073 --- /dev/null +++ b/examples/android_hello/app/src/main/java/infer/inferandroidexample/MainActivity.java @@ -0,0 +1,63 @@ +package infer.inferandroidexample; + +import android.support.v7.app.ActionBarActivity; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Calendar; + + +public class MainActivity extends ActionBarActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + String s = getDay(); + int length = s.length(); + writeToFile(); + } + + private String getDay() { + if (Calendar.getInstance().get(Calendar.DAY_OF_WEEK) == Calendar.WEDNESDAY) { + return "Wednesday"; + } else return null; + } + + private void writeToFile() { + byte[] arr = {1, 2, 3}; + FileOutputStream fis; + try { + fis = new FileOutputStream("file.txt"); + fis.write(arr); + fis.close(); + } catch (IOException e) { + //Deal with exception + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.menu_main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + + //noinspection SimplifiableIfStatement + if (id == R.id.action_settings) { + return true; + } + + return super.onOptionsItemSelected(item); + } +} diff --git a/examples/android_hello/app/src/main/res/drawable-hdpi/ic_launcher.png b/examples/android_hello/app/src/main/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 000000000..96a442e5b Binary files /dev/null and b/examples/android_hello/app/src/main/res/drawable-hdpi/ic_launcher.png differ diff --git a/examples/android_hello/app/src/main/res/drawable-mdpi/ic_launcher.png b/examples/android_hello/app/src/main/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 000000000..359047dfa Binary files /dev/null and b/examples/android_hello/app/src/main/res/drawable-mdpi/ic_launcher.png differ diff --git a/examples/android_hello/app/src/main/res/drawable-xhdpi/ic_launcher.png b/examples/android_hello/app/src/main/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 000000000..71c6d760f Binary files /dev/null and b/examples/android_hello/app/src/main/res/drawable-xhdpi/ic_launcher.png differ diff --git a/examples/android_hello/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/examples/android_hello/app/src/main/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..4df189464 Binary files /dev/null and b/examples/android_hello/app/src/main/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/examples/android_hello/app/src/main/res/layout/activity_main.xml b/examples/android_hello/app/src/main/res/layout/activity_main.xml new file mode 100644 index 000000000..f7158b81a --- /dev/null +++ b/examples/android_hello/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/examples/android_hello/app/src/main/res/menu/menu_main.xml b/examples/android_hello/app/src/main/res/menu/menu_main.xml new file mode 100644 index 000000000..b1cb90811 --- /dev/null +++ b/examples/android_hello/app/src/main/res/menu/menu_main.xml @@ -0,0 +1,6 @@ + + + diff --git a/examples/android_hello/app/src/main/res/values-w820dp/dimens.xml b/examples/android_hello/app/src/main/res/values-w820dp/dimens.xml new file mode 100644 index 000000000..63fc81644 --- /dev/null +++ b/examples/android_hello/app/src/main/res/values-w820dp/dimens.xml @@ -0,0 +1,6 @@ + + + 64dp + diff --git a/examples/android_hello/app/src/main/res/values/dimens.xml b/examples/android_hello/app/src/main/res/values/dimens.xml new file mode 100644 index 000000000..47c822467 --- /dev/null +++ b/examples/android_hello/app/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ + + + 16dp + 16dp + diff --git a/examples/android_hello/app/src/main/res/values/strings.xml b/examples/android_hello/app/src/main/res/values/strings.xml new file mode 100644 index 000000000..c9947ae5b --- /dev/null +++ b/examples/android_hello/app/src/main/res/values/strings.xml @@ -0,0 +1,8 @@ + + + + InferAndroidExample + Hello world! + Settings + + diff --git a/examples/android_hello/app/src/main/res/values/styles.xml b/examples/android_hello/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..766ab9930 --- /dev/null +++ b/examples/android_hello/app/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/examples/android_hello/build.gradle b/examples/android_hello/build.gradle new file mode 100644 index 000000000..6356aabdc --- /dev/null +++ b/examples/android_hello/build.gradle @@ -0,0 +1,19 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.0.0' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + jcenter() + } +} diff --git a/examples/android_hello/gradle.properties b/examples/android_hello/gradle.properties new file mode 100644 index 000000000..1d3591c8a --- /dev/null +++ b/examples/android_hello/gradle.properties @@ -0,0 +1,18 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true \ No newline at end of file diff --git a/examples/android_hello/gradle/wrapper/gradle-wrapper.jar b/examples/android_hello/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..8c0fb64a8 Binary files /dev/null and b/examples/android_hello/gradle/wrapper/gradle-wrapper.jar differ diff --git a/examples/android_hello/gradle/wrapper/gradle-wrapper.properties b/examples/android_hello/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..0c71e760d --- /dev/null +++ b/examples/android_hello/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Apr 10 15:27:10 PDT 2013 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip diff --git a/examples/android_hello/gradlew b/examples/android_hello/gradlew new file mode 100755 index 000000000..91a7e269e --- /dev/null +++ b/examples/android_hello/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/examples/android_hello/settings.gradle b/examples/android_hello/settings.gradle new file mode 100644 index 000000000..e7b4def49 --- /dev/null +++ b/examples/android_hello/settings.gradle @@ -0,0 +1 @@ +include ':app' diff --git a/examples/c_hello/Makefile b/examples/c_hello/Makefile new file mode 100644 index 000000000..3a156d281 --- /dev/null +++ b/examples/c_hello/Makefile @@ -0,0 +1,11 @@ + +SOURCES = $(shell ls *.c) +OBJECTS = $(SOURCES:.c=.o) + +all: $(OBJECTS) + +.c.o: + ${CC} -c $< + +clean: + rm -rf $(OBJECTS) diff --git a/examples/c_hello/example.c b/examples/c_hello/example.c new file mode 100644 index 000000000..016ceb7ab --- /dev/null +++ b/examples/c_hello/example.c @@ -0,0 +1,60 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct Person { + int age; + int height; + int weight; +}; + +int simple_null_pointer() { + struct Person *max = 0; + return max->age; +} + +struct Person *Person_create(int age, int height, int weight) { + struct Person *who = 0; + return who; +} + +int get_age(struct Person *who) { + return who->age; +} + +int null_pointer_interproc() { + struct Person *joe = Person_create(32, 64, 140); + return get_age(joe); +} + +void fileNotClosed() +{ + int fd = open("hi.txt", O_WRONLY | O_CREAT | O_TRUNC, 0600); + if (fd != -1) { + char buffer[256]; + // We can easily batch that by separating with space + write(fd, buffer, strlen(buffer)); + } +} + +void simple_leak() { + int *p; + p = (int*) malloc(sizeof(int)); +} + +void common_realloc_leak() { + int *p, *q; + p = (int*) malloc(sizeof(int)); + q = (int*) realloc(p, sizeof(int) * 42); + // if realloc fails, then p becomes unreachable + if (q != NULL) free(q); +} diff --git a/examples/hello.c b/examples/hello.c new file mode 100644 index 000000000..c681ea287 --- /dev/null +++ b/examples/hello.c @@ -0,0 +1,6 @@ +#include + +void test() { + int *s = NULL; + *s = 42; +} diff --git a/examples/ios_hello/HelloWorldApp.xcodeproj/project.pbxproj b/examples/ios_hello/HelloWorldApp.xcodeproj/project.pbxproj new file mode 100644 index 000000000..888752dfc --- /dev/null +++ b/examples/ios_hello/HelloWorldApp.xcodeproj/project.pbxproj @@ -0,0 +1,433 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 124F6C531B0CDAE400C16385 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 124F6C521B0CDAE400C16385 /* main.m */; }; + 124F6C561B0CDAE400C16385 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 124F6C551B0CDAE400C16385 /* AppDelegate.m */; }; + 124F6C591B0CDAE400C16385 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 124F6C581B0CDAE400C16385 /* ViewController.m */; }; + 124F6C5C1B0CDAE400C16385 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 124F6C5A1B0CDAE400C16385 /* Main.storyboard */; }; + 124F6C5E1B0CDAE400C16385 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 124F6C5D1B0CDAE400C16385 /* Images.xcassets */; }; + 124F6C611B0CDAE400C16385 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 124F6C5F1B0CDAE400C16385 /* LaunchScreen.xib */; }; + 124F6C6D1B0CDAE400C16385 /* HelloWorldAppTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 124F6C6C1B0CDAE400C16385 /* HelloWorldAppTests.m */; }; + 124F6C771B0CED9B00C16385 /* Hello.m in Sources */ = {isa = PBXBuildFile; fileRef = 124F6C761B0CED9B00C16385 /* Hello.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 124F6C671B0CDAE400C16385 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 124F6C451B0CDAE400C16385 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 124F6C4C1B0CDAE400C16385; + remoteInfo = HelloWorldApp; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 124F6C4D1B0CDAE400C16385 /* HelloWorldApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HelloWorldApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 124F6C511B0CDAE400C16385 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 124F6C521B0CDAE400C16385 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 124F6C541B0CDAE400C16385 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 124F6C551B0CDAE400C16385 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 124F6C571B0CDAE400C16385 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + 124F6C581B0CDAE400C16385 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + 124F6C5B1B0CDAE400C16385 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 124F6C5D1B0CDAE400C16385 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + 124F6C601B0CDAE400C16385 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; + 124F6C661B0CDAE400C16385 /* HelloWorldAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = HelloWorldAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 124F6C6B1B0CDAE400C16385 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 124F6C6C1B0CDAE400C16385 /* HelloWorldAppTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HelloWorldAppTests.m; sourceTree = ""; }; + 124F6C761B0CED9B00C16385 /* Hello.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Hello.m; sourceTree = ""; }; + 124F6C781B0CEDAF00C16385 /* Hello.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Hello.h; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 124F6C4A1B0CDAE400C16385 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 124F6C631B0CDAE400C16385 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 124F6C441B0CDAE400C16385 = { + isa = PBXGroup; + children = ( + 124F6C4F1B0CDAE400C16385 /* HelloWorldApp */, + 124F6C691B0CDAE400C16385 /* HelloWorldAppTests */, + 124F6C4E1B0CDAE400C16385 /* Products */, + ); + sourceTree = ""; + }; + 124F6C4E1B0CDAE400C16385 /* Products */ = { + isa = PBXGroup; + children = ( + 124F6C4D1B0CDAE400C16385 /* HelloWorldApp.app */, + 124F6C661B0CDAE400C16385 /* HelloWorldAppTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 124F6C4F1B0CDAE400C16385 /* HelloWorldApp */ = { + isa = PBXGroup; + children = ( + 124F6C541B0CDAE400C16385 /* AppDelegate.h */, + 124F6C551B0CDAE400C16385 /* AppDelegate.m */, + 124F6C571B0CDAE400C16385 /* ViewController.h */, + 124F6C581B0CDAE400C16385 /* ViewController.m */, + 124F6C5A1B0CDAE400C16385 /* Main.storyboard */, + 124F6C5D1B0CDAE400C16385 /* Images.xcassets */, + 124F6C5F1B0CDAE400C16385 /* LaunchScreen.xib */, + 124F6C501B0CDAE400C16385 /* Supporting Files */, + 124F6C761B0CED9B00C16385 /* Hello.m */, + 124F6C781B0CEDAF00C16385 /* Hello.h */, + ); + path = HelloWorldApp; + sourceTree = ""; + }; + 124F6C501B0CDAE400C16385 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 124F6C511B0CDAE400C16385 /* Info.plist */, + 124F6C521B0CDAE400C16385 /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 124F6C691B0CDAE400C16385 /* HelloWorldAppTests */ = { + isa = PBXGroup; + children = ( + 124F6C6C1B0CDAE400C16385 /* HelloWorldAppTests.m */, + 124F6C6A1B0CDAE400C16385 /* Supporting Files */, + ); + path = HelloWorldAppTests; + sourceTree = ""; + }; + 124F6C6A1B0CDAE400C16385 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 124F6C6B1B0CDAE400C16385 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 124F6C4C1B0CDAE400C16385 /* HelloWorldApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 124F6C701B0CDAE400C16385 /* Build configuration list for PBXNativeTarget "HelloWorldApp" */; + buildPhases = ( + 124F6C491B0CDAE400C16385 /* Sources */, + 124F6C4A1B0CDAE400C16385 /* Frameworks */, + 124F6C4B1B0CDAE400C16385 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = HelloWorldApp; + productName = HelloWorldApp; + productReference = 124F6C4D1B0CDAE400C16385 /* HelloWorldApp.app */; + productType = "com.apple.product-type.application"; + }; + 124F6C651B0CDAE400C16385 /* HelloWorldAppTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 124F6C731B0CDAE400C16385 /* Build configuration list for PBXNativeTarget "HelloWorldAppTests" */; + buildPhases = ( + 124F6C621B0CDAE400C16385 /* Sources */, + 124F6C631B0CDAE400C16385 /* Frameworks */, + 124F6C641B0CDAE400C16385 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 124F6C681B0CDAE400C16385 /* PBXTargetDependency */, + ); + name = HelloWorldAppTests; + productName = HelloWorldAppTests; + productReference = 124F6C661B0CDAE400C16385 /* HelloWorldAppTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 124F6C451B0CDAE400C16385 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0620; + ORGANIZATIONNAME = "Dulma Rodriguez"; + TargetAttributes = { + 124F6C4C1B0CDAE400C16385 = { + CreatedOnToolsVersion = 6.2; + }; + 124F6C651B0CDAE400C16385 = { + CreatedOnToolsVersion = 6.2; + TestTargetID = 124F6C4C1B0CDAE400C16385; + }; + }; + }; + buildConfigurationList = 124F6C481B0CDAE400C16385 /* Build configuration list for PBXProject "HelloWorldApp" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 124F6C441B0CDAE400C16385; + productRefGroup = 124F6C4E1B0CDAE400C16385 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 124F6C4C1B0CDAE400C16385 /* HelloWorldApp */, + 124F6C651B0CDAE400C16385 /* HelloWorldAppTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 124F6C4B1B0CDAE400C16385 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 124F6C5C1B0CDAE400C16385 /* Main.storyboard in Resources */, + 124F6C611B0CDAE400C16385 /* LaunchScreen.xib in Resources */, + 124F6C5E1B0CDAE400C16385 /* Images.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 124F6C641B0CDAE400C16385 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 124F6C491B0CDAE400C16385 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 124F6C591B0CDAE400C16385 /* ViewController.m in Sources */, + 124F6C561B0CDAE400C16385 /* AppDelegate.m in Sources */, + 124F6C771B0CED9B00C16385 /* Hello.m in Sources */, + 124F6C531B0CDAE400C16385 /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 124F6C621B0CDAE400C16385 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 124F6C6D1B0CDAE400C16385 /* HelloWorldAppTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 124F6C681B0CDAE400C16385 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 124F6C4C1B0CDAE400C16385 /* HelloWorldApp */; + targetProxy = 124F6C671B0CDAE400C16385 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 124F6C5A1B0CDAE400C16385 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 124F6C5B1B0CDAE400C16385 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 124F6C5F1B0CDAE400C16385 /* LaunchScreen.xib */ = { + isa = PBXVariantGroup; + children = ( + 124F6C601B0CDAE400C16385 /* Base */, + ); + name = LaunchScreen.xib; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 124F6C6E1B0CDAE400C16385 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.2; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 124F6C6F1B0CDAE400C16385 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.2; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 124F6C711B0CDAE400C16385 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = HelloWorldApp/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 124F6C721B0CDAE400C16385 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = HelloWorldApp/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; + 124F6C741B0CDAE400C16385 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = HelloWorldAppTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/HelloWorldApp.app/HelloWorldApp"; + }; + name = Debug; + }; + 124F6C751B0CDAE400C16385 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = HelloWorldAppTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/HelloWorldApp.app/HelloWorldApp"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 124F6C481B0CDAE400C16385 /* Build configuration list for PBXProject "HelloWorldApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 124F6C6E1B0CDAE400C16385 /* Debug */, + 124F6C6F1B0CDAE400C16385 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 124F6C701B0CDAE400C16385 /* Build configuration list for PBXNativeTarget "HelloWorldApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 124F6C711B0CDAE400C16385 /* Debug */, + 124F6C721B0CDAE400C16385 /* Release */, + ); + defaultConfigurationIsVisible = 0; + }; + 124F6C731B0CDAE400C16385 /* Build configuration list for PBXNativeTarget "HelloWorldAppTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 124F6C741B0CDAE400C16385 /* Debug */, + 124F6C751B0CDAE400C16385 /* Release */, + ); + defaultConfigurationIsVisible = 0; + }; +/* End XCConfigurationList section */ + }; + rootObject = 124F6C451B0CDAE400C16385 /* Project object */; +} diff --git a/examples/ios_hello/HelloWorldApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/ios_hello/HelloWorldApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..cddd18633 --- /dev/null +++ b/examples/ios_hello/HelloWorldApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/ios_hello/HelloWorldApp.xcodeproj/project.xcworkspace/xcuserdata/dulmarod.xcuserdatad/UserInterfaceState.xcuserstate b/examples/ios_hello/HelloWorldApp.xcodeproj/project.xcworkspace/xcuserdata/dulmarod.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 000000000..6ab3f901f Binary files /dev/null and b/examples/ios_hello/HelloWorldApp.xcodeproj/project.xcworkspace/xcuserdata/dulmarod.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/examples/ios_hello/HelloWorldApp.xcodeproj/xcuserdata/dulmarod.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/examples/ios_hello/HelloWorldApp.xcodeproj/xcuserdata/dulmarod.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 000000000..e85ab1605 --- /dev/null +++ b/examples/ios_hello/HelloWorldApp.xcodeproj/xcuserdata/dulmarod.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/examples/ios_hello/HelloWorldApp.xcodeproj/xcuserdata/dulmarod.xcuserdatad/xcschemes/HelloWorldApp.xcscheme b/examples/ios_hello/HelloWorldApp.xcodeproj/xcuserdata/dulmarod.xcuserdatad/xcschemes/HelloWorldApp.xcscheme new file mode 100644 index 000000000..d865d0282 --- /dev/null +++ b/examples/ios_hello/HelloWorldApp.xcodeproj/xcuserdata/dulmarod.xcuserdatad/xcschemes/HelloWorldApp.xcscheme @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/ios_hello/HelloWorldApp.xcodeproj/xcuserdata/dulmarod.xcuserdatad/xcschemes/xcschememanagement.plist b/examples/ios_hello/HelloWorldApp.xcodeproj/xcuserdata/dulmarod.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 000000000..da2b6927a --- /dev/null +++ b/examples/ios_hello/HelloWorldApp.xcodeproj/xcuserdata/dulmarod.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,27 @@ + + + + + SchemeUserState + + HelloWorldApp.xcscheme + + orderHint + 0 + + + SuppressBuildableAutocreation + + 124F6C4C1B0CDAE400C16385 + + primary + + + 124F6C651B0CDAE400C16385 + + primary + + + + + diff --git a/examples/ios_hello/HelloWorldApp/AppDelegate.h b/examples/ios_hello/HelloWorldApp/AppDelegate.h new file mode 100644 index 000000000..a49e6e973 --- /dev/null +++ b/examples/ios_hello/HelloWorldApp/AppDelegate.h @@ -0,0 +1,17 @@ +// +// AppDelegate.h +// HelloWorldApp +// +// Created by Dulma Rodriguez on 20/05/2015. +// Copyright (c) 2015 Dulma Rodriguez. All rights reserved. +// + +#import + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + + +@end + diff --git a/examples/ios_hello/HelloWorldApp/AppDelegate.m b/examples/ios_hello/HelloWorldApp/AppDelegate.m new file mode 100644 index 000000000..c6444c28e --- /dev/null +++ b/examples/ios_hello/HelloWorldApp/AppDelegate.m @@ -0,0 +1,78 @@ +// +// AppDelegate.m +// HelloWorldApp +// +// Created by Dulma Rodriguez on 20/05/2015. +// Copyright (c) 2015 Dulma Rodriguez. All rights reserved. +// + +#import "AppDelegate.h" +#import "Hello.h" +#import + +@interface AppDelegate () + +@end + +@implementation AppDelegate + +-(void) memory_leak_bug { + CGPathRef shadowPath = CGPathCreateWithRect(self.inputView.bounds, NULL); +} + +-(void) resource_leak_bug { + FILE *fp; + fp=fopen("c:\\test.txt", "r"); +} + +-(void) parameter_not_null_checked_block_bug:(void (^)())callback { + callback(); +} + +-(NSArray*) npe_in_array_literal_bug { + NSString *str = nil; + return @[@"horse", str, @"dolphin"]; +} + +-(NSArray*) premature_nil_termination_argument_bug { + NSString *str = nil; + return [NSArray arrayWithObjects: @"horse", str, @"dolphin", nil]; +} + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + // Override point for customization after application launch. + Hello *hello = [Hello new]; + [hello null_dereference_bug]; + [self memory_leak_bug]; + [self resource_leak_bug]; + [hello parameter_not_null_checked_bug:nil]; + [self parameter_not_null_checked_block_bug:nil]; + [hello ivar_not_nullable_bug:nil]; + [self npe_in_array_literal_bug]; + [self premature_nil_termination_argument_bug]; + return YES; +} + +- (void)applicationWillResignActive:(UIApplication *)application { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. +} + +- (void)applicationDidEnterBackground:(UIApplication *)application { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. +} + +- (void)applicationWillEnterForeground:(UIApplication *)application { + // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. +} + +- (void)applicationDidBecomeActive:(UIApplication *)application { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. +} + +- (void)applicationWillTerminate:(UIApplication *)application { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. +} + +@end diff --git a/examples/ios_hello/HelloWorldApp/Base.lproj/LaunchScreen.xib b/examples/ios_hello/HelloWorldApp/Base.lproj/LaunchScreen.xib new file mode 100644 index 000000000..a2f15cf94 --- /dev/null +++ b/examples/ios_hello/HelloWorldApp/Base.lproj/LaunchScreen.xib @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/ios_hello/HelloWorldApp/Base.lproj/Main.storyboard b/examples/ios_hello/HelloWorldApp/Base.lproj/Main.storyboard new file mode 100644 index 000000000..d912f9d76 --- /dev/null +++ b/examples/ios_hello/HelloWorldApp/Base.lproj/Main.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/ios_hello/HelloWorldApp/Hello.h b/examples/ios_hello/HelloWorldApp/Hello.h new file mode 100644 index 000000000..64c98528a --- /dev/null +++ b/examples/ios_hello/HelloWorldApp/Hello.h @@ -0,0 +1,24 @@ +// +// Hello.h +// HelloWorldApp +// +// Created by Dulma Rodriguez on 20/05/2015. +// Copyright (c) 2015 Dulma Rodriguez. All rights reserved. +// + +#import + +@interface Hello : NSObject + +@property (strong) NSString* s; +@property (strong) Hello* hello; + +-(Hello*) return_hello; + +-(NSString*) null_dereference_bug; + +-(NSString*) ivar_not_nullable_bug:(Hello*) hello; + +-(NSString*) parameter_not_null_checked_bug:(Hello*) hello; + +@end \ No newline at end of file diff --git a/examples/ios_hello/HelloWorldApp/Hello.m b/examples/ios_hello/HelloWorldApp/Hello.m new file mode 100644 index 000000000..4dcd9842c --- /dev/null +++ b/examples/ios_hello/HelloWorldApp/Hello.m @@ -0,0 +1,33 @@ +// +// Hello.m +// HelloWorldApp +// +// Created by Dulma Rodriguez on 20/05/2015. +// Copyright (c) 2015 Dulma Rodriguez. All rights reserved. +// + +#import +#import "Hello.h" + +@implementation Hello + +-(Hello*) return_hello { + return [Hello new]; +} + +-(NSString*) null_dereference_bug { + Hello *hello = nil; + return hello->_s; +} + +-(NSString*) ivar_not_nullable_bug:(Hello*) hello { + Hello* ret_hello = [hello->_hello return_hello]; + return ret_hello->_s; +} + +-(NSString*) parameter_not_null_checked_bug:(Hello*) hello { + Hello *ret_hello = [hello return_hello]; + return ret_hello->_s; +} + +@end diff --git a/examples/ios_hello/HelloWorldApp/Images.xcassets/AppIcon.appiconset/Contents.json b/examples/ios_hello/HelloWorldApp/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..118c98f74 --- /dev/null +++ b/examples/ios_hello/HelloWorldApp/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,38 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/ios_hello/HelloWorldApp/Info.plist b/examples/ios_hello/HelloWorldApp/Info.plist new file mode 100644 index 000000000..fda2b9565 --- /dev/null +++ b/examples/ios_hello/HelloWorldApp/Info.plist @@ -0,0 +1,40 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.facebook.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/examples/ios_hello/HelloWorldApp/ViewController.h b/examples/ios_hello/HelloWorldApp/ViewController.h new file mode 100644 index 000000000..4ca61a835 --- /dev/null +++ b/examples/ios_hello/HelloWorldApp/ViewController.h @@ -0,0 +1,15 @@ +// +// ViewController.h +// HelloWorldApp +// +// Created by Dulma Rodriguez on 20/05/2015. +// Copyright (c) 2015 Dulma Rodriguez. All rights reserved. +// + +#import + +@interface ViewController : UIViewController + + +@end + diff --git a/examples/ios_hello/HelloWorldApp/ViewController.m b/examples/ios_hello/HelloWorldApp/ViewController.m new file mode 100644 index 000000000..184a77155 --- /dev/null +++ b/examples/ios_hello/HelloWorldApp/ViewController.m @@ -0,0 +1,27 @@ +// +// ViewController.m +// HelloWorldApp +// +// Created by Dulma Rodriguez on 20/05/2015. +// Copyright (c) 2015 Dulma Rodriguez. All rights reserved. +// + +#import "ViewController.h" + +@interface ViewController () + +@end + +@implementation ViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do any additional setup after loading the view, typically from a nib. +} + +- (void)didReceiveMemoryWarning { + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +@end diff --git a/examples/ios_hello/HelloWorldApp/main.m b/examples/ios_hello/HelloWorldApp/main.m new file mode 100644 index 000000000..299addb83 --- /dev/null +++ b/examples/ios_hello/HelloWorldApp/main.m @@ -0,0 +1,16 @@ +// +// main.m +// HelloWorldApp +// +// Created by Dulma Rodriguez on 20/05/2015. +// Copyright (c) 2015 Dulma Rodriguez. All rights reserved. +// + +#import +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/examples/ios_hello/HelloWorldAppTests/HelloWorldAppTests.m b/examples/ios_hello/HelloWorldAppTests/HelloWorldAppTests.m new file mode 100644 index 000000000..f40845acd --- /dev/null +++ b/examples/ios_hello/HelloWorldAppTests/HelloWorldAppTests.m @@ -0,0 +1,40 @@ +// +// HelloWorldAppTests.m +// HelloWorldAppTests +// +// Created by Dulma Rodriguez on 20/05/2015. +// Copyright (c) 2015 Dulma Rodriguez. All rights reserved. +// + +#import +#import + +@interface HelloWorldAppTests : XCTestCase + +@end + +@implementation HelloWorldAppTests + +- (void)setUp { + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testExample { + // This is an example of a functional test case. + XCTAssert(YES, @"Pass"); +} + +- (void)testPerformanceExample { + // This is an example of a performance test case. + [self measureBlock:^{ + // Put the code you want to measure the time of here. + }]; +} + +@end diff --git a/examples/ios_hello/HelloWorldAppTests/Info.plist b/examples/ios_hello/HelloWorldAppTests/Info.plist new file mode 100644 index 000000000..87e3a6175 --- /dev/null +++ b/examples/ios_hello/HelloWorldAppTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.facebook.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/infer/.merlin b/infer/.merlin new file mode 100644 index 000000000..8f4f6951e --- /dev/null +++ b/infer/.merlin @@ -0,0 +1,4 @@ +S src/** +B _build-infer/** +PKG sawja +PKG atdgen diff --git a/infer/.project b/infer/.project new file mode 100644 index 000000000..513728ddf --- /dev/null +++ b/infer/.project @@ -0,0 +1,17 @@ + + + EdgNative + + + + + + Ocaml.ocamlMakefileBuilder + + + + + + ocaml.ocamlnatureMakefile + + diff --git a/infer/Makefile b/infer/Makefile new file mode 100644 index 000000000..8588bd28c --- /dev/null +++ b/infer/Makefile @@ -0,0 +1,25 @@ +SHELL := /bin/bash +CWD = $(shell pwd) + +ANNOTATIONS = annotations +MODELS = models +SRCDIR = src + + +.PHONY: clean clang java models + +all: clang java + +java: + make -C $(SRCDIR) java + make -C $(MODELS) java + make -C $(ANNOTATIONS) + +clang: + make -C $(SRCDIR) clang + make -C $(MODELS) clang + +clean: + make -C $(SRCDIR) clean + make -C $(MODELS) clean + make -C $(ANNOTATIONS) clean diff --git a/infer/annotations/BUCK b/infer/annotations/BUCK new file mode 100644 index 000000000..1eff76b4e --- /dev/null +++ b/infer/annotations/BUCK @@ -0,0 +1,7 @@ +prebuilt_jar( + name = 'annotations', + binary_jar = 'annotations.jar', + visibility = [ + 'PUBLIC', + ] +) diff --git a/infer/annotations/Makefile b/infer/annotations/Makefile new file mode 100644 index 000000000..a17ae39f0 --- /dev/null +++ b/infer/annotations/Makefile @@ -0,0 +1,13 @@ +DEPENDENCIES = ../../dependencies +JSR_JAR = $(DEPENDENCIES)/java/jsr-305/jsr305.jar +CLASSES_OUT = classes +SOURCES = `find . -name "*.java"` + +all: + mkdir -p classes + javac -cp $(JSR_JAR) $(SOURCES) + jar cvf annotations.jar $(SOURCES) `find . -name "*.class"` + +clean: + rm -rvf `find . -name "*.class"` + rm -rvf annotations.jar diff --git a/infer/annotations/com/facebook/infer/annotation/Assertions.java b/infer/annotations/com/facebook/infer/annotation/Assertions.java new file mode 100644 index 000000000..e6b8d16ed --- /dev/null +++ b/infer/annotations/com/facebook/infer/annotation/Assertions.java @@ -0,0 +1,80 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.infer.annotation; + +import javax.annotation.Nullable; +import java.util.List; +import java.util.Map; + +public class Assertions { + + public static T assumeNotNull(@Nullable T object) { + return object; + } + + public static T assumeNotNull(@Nullable T object, String explanation) { + return object; + } + + public static T assertNotNull(@Nullable T object) { + if (object == null) { + throw new AssertionError(); + } + return object; + } + + public static T assertNotNull(@Nullable T object, String explanation) { + if (object == null) { + throw new AssertionError(explanation); + } + return object; + } + + public static T getAssumingNotNull(List list, int index) { + return list.get(index); + } + + public static T getAssertingNotNull(List list, int index) { + assertCondition(0 <= index && index < list.size()); + return assertNotNull(list.get(index)); + } + + public static V getAssumingNotNull(Map map, K key) { + return map.get(key); + } + + public static V getAssertingNotNull(Map map, K key) { + assertCondition(map.containsKey(key)); + return assertNotNull(map.get(key)); + } + + public static void assumeCondition(boolean condition) { + } + + public static void assumeCondition(boolean condition, String explanation) { + } + + public static void assertCondition(boolean condition) { + if (!condition) { + throw new AssertionError(); + } + } + + public static void assertCondition(boolean condition, String explanation) { + if (!condition) { + throw new AssertionError(explanation); + } + } + + public static AssertionError assertUnreachable() { + throw new AssertionError(); + } + + public static AssertionError assertUnreachable(String explanation) { + throw new AssertionError(explanation); + } + + public static AssertionError assertUnreachable(Exception exception) { + throw new AssertionError(exception); + } +} diff --git a/infer/annotations/com/facebook/infer/annotation/Initializer.java b/infer/annotations/com/facebook/infer/annotation/Initializer.java new file mode 100644 index 000000000..1cf0f1298 --- /dev/null +++ b/infer/annotations/com/facebook/infer/annotation/Initializer.java @@ -0,0 +1,19 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.infer.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A method annotated with @Initializer should always be be called before the object is used. + * Users of the class and static checkers must enforce, and can rely on, this invariant. + * Examples include methods called indirectly by the constructor, protocols of init-then-use + * where some values are initialized after construction but before the first use, + * and builder classes where an object initialization must complete before build() is called. + */ +@Retention(RetentionPolicy.CLASS) +@Target({ElementType.TYPE, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.METHOD}) +public @interface Initializer {} diff --git a/infer/annotations/com/facebook/infer/annotation/Mutable.java b/infer/annotations/com/facebook/infer/annotation/Mutable.java new file mode 100644 index 000000000..0be0f9d27 --- /dev/null +++ b/infer/annotations/com/facebook/infer/annotation/Mutable.java @@ -0,0 +1,12 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.infer.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.CLASS) +@Target({ElementType.TYPE, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.METHOD}) +public @interface Mutable {} diff --git a/infer/annotations/com/facebook/infer/annotation/Present.java b/infer/annotations/com/facebook/infer/annotation/Present.java new file mode 100644 index 000000000..1033d1981 --- /dev/null +++ b/infer/annotations/com/facebook/infer/annotation/Present.java @@ -0,0 +1,18 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.infer.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A class field, or method return/parameter type, of Optional type is annotated @Present + * to indicate that its value cannot be absent. + * Users of the method/field and static checkers must enforce, and can rely on, this invariant. + */ +@Retention(RetentionPolicy.CLASS) +@Target({ElementType.TYPE, ElementType.FIELD, ElementType.CONSTRUCTOR, + ElementType.METHOD, ElementType.PARAMETER}) +public @interface Present {} diff --git a/infer/annotations/com/facebook/infer/annotation/Strict.java b/infer/annotations/com/facebook/infer/annotation/Strict.java new file mode 100644 index 000000000..76477fe85 --- /dev/null +++ b/infer/annotations/com/facebook/infer/annotation/Strict.java @@ -0,0 +1,21 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.infer.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.CLASS) +@Target({ + ElementType.CONSTRUCTOR, + ElementType.FIELD, + ElementType.METHOD, + ElementType.PACKAGE, + ElementType.TYPE, +}) + +public @interface Strict { + String value() default ""; +} diff --git a/infer/annotations/com/facebook/infer/annotation/SuppressFieldNotInitialized.java b/infer/annotations/com/facebook/infer/annotation/SuppressFieldNotInitialized.java new file mode 100644 index 000000000..18175e94c --- /dev/null +++ b/infer/annotations/com/facebook/infer/annotation/SuppressFieldNotInitialized.java @@ -0,0 +1,12 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.infer.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.CLASS) +@Target({ElementType.TYPE, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.METHOD}) +public @interface SuppressFieldNotInitialized {} diff --git a/infer/annotations/com/facebook/infer/annotation/SuppressFieldNotNullable.java b/infer/annotations/com/facebook/infer/annotation/SuppressFieldNotNullable.java new file mode 100644 index 000000000..a65787113 --- /dev/null +++ b/infer/annotations/com/facebook/infer/annotation/SuppressFieldNotNullable.java @@ -0,0 +1,12 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.infer.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.CLASS) +@Target({ElementType.TYPE, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.METHOD}) +public @interface SuppressFieldNotNullable {} diff --git a/infer/annotations/com/facebook/infer/annotation/SuppressNullFieldAccess.java b/infer/annotations/com/facebook/infer/annotation/SuppressNullFieldAccess.java new file mode 100644 index 000000000..adb80100f --- /dev/null +++ b/infer/annotations/com/facebook/infer/annotation/SuppressNullFieldAccess.java @@ -0,0 +1,12 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.infer.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.CLASS) +@Target({ElementType.TYPE, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.METHOD}) +public @interface SuppressNullFieldAccess {} diff --git a/infer/annotations/com/facebook/infer/annotation/SuppressNullMethodCall.java b/infer/annotations/com/facebook/infer/annotation/SuppressNullMethodCall.java new file mode 100644 index 000000000..0a8f7c93d --- /dev/null +++ b/infer/annotations/com/facebook/infer/annotation/SuppressNullMethodCall.java @@ -0,0 +1,12 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.infer.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.CLASS) +@Target({ElementType.TYPE, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.METHOD}) +public @interface SuppressNullMethodCall {} diff --git a/infer/annotations/com/facebook/infer/annotation/SuppressParameterNotNullable.java b/infer/annotations/com/facebook/infer/annotation/SuppressParameterNotNullable.java new file mode 100644 index 000000000..5684f6dcc --- /dev/null +++ b/infer/annotations/com/facebook/infer/annotation/SuppressParameterNotNullable.java @@ -0,0 +1,12 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.infer.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.CLASS) +@Target({ElementType.TYPE, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.METHOD}) +public @interface SuppressParameterNotNullable {} diff --git a/infer/annotations/com/facebook/infer/annotation/SuppressReturnOverAnnotated.java b/infer/annotations/com/facebook/infer/annotation/SuppressReturnOverAnnotated.java new file mode 100644 index 000000000..56d01b0dc --- /dev/null +++ b/infer/annotations/com/facebook/infer/annotation/SuppressReturnOverAnnotated.java @@ -0,0 +1,12 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.infer.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.CLASS) +@Target({ElementType.TYPE, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.METHOD}) +public @interface SuppressReturnOverAnnotated {} diff --git a/infer/annotations/com/facebook/infer/annotation/Verify.java b/infer/annotations/com/facebook/infer/annotation/Verify.java new file mode 100644 index 000000000..1f1a13059 --- /dev/null +++ b/infer/annotations/com/facebook/infer/annotation/Verify.java @@ -0,0 +1,19 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.infer.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.CLASS) +@Target({ + ElementType.CONSTRUCTOR, + ElementType.METHOD, + ElementType.PACKAGE, + ElementType.TYPE, +}) + +public @interface Verify { +} diff --git a/infer/bin/BuckAnalyze b/infer/bin/BuckAnalyze new file mode 100755 index 000000000..b2d47ea97 --- /dev/null +++ b/infer/bin/BuckAnalyze @@ -0,0 +1,531 @@ +#!/usr/bin/env python +# +# Copyright (c) 2013-present Facebook. All rights reserved. +# + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import argparse +import csv +import io +import json +import logging +import multiprocessing +import os +import platform +import re +import shutil +import stat +import subprocess +import sys +import tempfile +import time +import traceback +import zipfile + +# Infer imports +import inferlib +import utils + +ANALYSIS_SUMMARY_OUTPUT = 'analysis_summary.txt' + +BUCK_CONFIG = '.buckconfig.local' +BUCK_CONFIG_BACKUP = '.buckconfig.local.backup_generated_by_BuckAnalyze' +DEFAULT_BUCK_OUT = os.path.join(os.getcwd(), 'buck-out') +DEFAULT_BUCK_OUT_GEN = os.path.join(DEFAULT_BUCK_OUT, 'gen') + +INFER_REPORT = os.path.join(utils.BUCK_INFER_OUT, utils.CSV_REPORT_FILENAME) +INFER_STATS = os.path.join(utils.BUCK_INFER_OUT, utils.STATS_FILENAME) + +INFERJ_SCRIPT = """\ +#!/bin/sh +{0} {1} javac $@ +""" + +LOCAL_CONFIG = """\ + [tools] + javac = %s +""" + + +def prepare_build(args): + """Creates script that redirects javac calls to inferJ and a local buck + configuration that tells buck to use that script. + """ + inferJ_options = [ + '--buck', + '--analyzer', + args.analyzer, + ] + + if args.debug: + inferJ_options.append('--debug') + + if args.analyzer_mode: + inferJ_options.append('--analyzer_mode') + inferJ_options.append(args.analyzer_mode) + + # Create a temporary directory as a cache for jar files. + infer_cache_dir = os.path.join(args.infer_out, 'cache') + if not os.path.isdir(infer_cache_dir): + os.mkdir(infer_cache_dir) + inferJ_options.append('--infer_cache') + inferJ_options.append(infer_cache_dir) + temp_files = [infer_cache_dir] + + try: + inferJ = utils.get_cmd_in_bin_dir('inferJ') + ' ' +\ + ' '.join(inferJ_options) + except subprocess.CalledProcessError as e: + logging.error('Could not find inferJ') + raise e + + # Disable the use of buckd as this scripts modifies .buckconfig.local + logging.info('Disabling buckd: export NO_BUCKD=1') + os.environ['NO_BUCKD'] = '1' + + # make sure INFER_ANALYSIS is set when buck is called + logging.info('Setup Infer analysis mode for Buck: export INFER_ANALYSIS=1') + os.environ['INFER_ANALYSIS'] = '1' + + # Create a script to be called by buck + inferJ_script = None + with tempfile.NamedTemporaryFile(delete=False, + prefix='inferJ_', + suffix='.sh', + dir='.') as inferJ_script: + logging.info('Creating %s' % inferJ_script.name) + inferJ_script.file.write( + (INFERJ_SCRIPT.format(sys.executable, inferJ)).encode()) + + st = os.stat(inferJ_script.name) + os.chmod(inferJ_script.name, st.st_mode | stat.S_IEXEC) + + # Backup and patch local buck config + patched_config = '' + if os.path.isfile(BUCK_CONFIG): + logging.info('Backing up %s to %s', BUCK_CONFIG, BUCK_CONFIG_BACKUP) + shutil.move(BUCK_CONFIG, BUCK_CONFIG_BACKUP) + with open(BUCK_CONFIG_BACKUP) as buckconfig: + patched_config = '\n'.join(buckconfig) + + javac_section = '[tools]\n{0}javac = {1}'.format( + ' ' * 4, + inferJ_script.name) + patched_config += javac_section + with open(BUCK_CONFIG, 'w') as buckconfig: + buckconfig.write(patched_config) + + temp_files += [inferJ_script.name] + return temp_files + + +def java_targets(): + target_types = [ + 'android_library', + 'java_library', + ] + try: + targets = subprocess.check_output([ + 'buck', + 'targets', + '--type', + ] + target_types).decode().strip().split('\n') + except subprocess.CalledProcessError as e: + logging.error('Could not compute java library targets') + raise e + return set(targets) + + +def is_alias(target): + return ':' not in target + + +def expand_target(target, java_targets): + if not is_alias(target): + return [target] + else: + try: + buck_audit_cmd = ['buck', 'audit', 'classpath', '--dot', target] + output = subprocess.check_output(buck_audit_cmd) + dotty = output.decode().split('\n') + except subprocess.CalledProcessError as e: + logging.error('Could not expand target {0}'.format(target)) + raise e + targets = set() + edge_re = re.compile('.*"(.*)".*"(.*)".*') + for line in dotty: + match = re.match(edge_re, line) + if match: + for t in match.groups(): + if t in java_targets: + targets.add(t) + return targets + + +def normalize_target(target): + if is_alias(target) or target.startswith('//'): + return target + else: + return '//' + target + + +def determine_library_targets(args): + """ Uses git and buck audit to expand aliases into the list of java or + android library targets that are parts of these aliases. + Buck targets directly passed as argument are not expanded """ + + args.targets = [normalize_target(t) for t in args.targets] + + if any(map(is_alias, args.targets)): + all_java_targets = java_targets() + targets = set() + for t in args.targets: + targets.update(expand_target(t, all_java_targets)) + args.targets = list(targets) + + if args.verbose: + logging.debug('Targets to analyze:') + for target in args.targets: + logging.debug(target) + + +def init_stats(args, start_time): + """Returns dictionary with target independent statistics. + """ + return { + 'float': {}, + 'int': { + 'cores': multiprocessing.cpu_count(), + 'time': int(time.time()), + 'start_time': int(round(start_time)), + }, + 'normal': { + 'debug': str(args.debug), + 'analyzer': args.analyzer, + 'machine': platform.machine(), + 'node': platform.node(), + 'project': os.path.basename(os.getcwd()), + 'revision': utils.vcs_revision(), + 'branch': utils.vcs_branch(), + 'system': platform.system(), + 'infer_version': utils.infer_version(), + 'infer_branch': utils.infer_branch(), + } + } + + +def store_performances_csv(infer_out, stats): + """Stores the statistics about perfromances into a CSV file to be exported + to a database""" + perf_filename = os.path.join(infer_out, utils.CSV_PERF_FILENAME) + with open(perf_filename, 'w') as csv_file_out: + csv_writer = csv.writer(csv_file_out) + keys = ['infer_version', 'project', 'revision', 'files', 'lines', + 'cores', 'system', 'machine', 'node', 'total_time', + 'capture_time', 'analysis_time', 'reporting_time', 'time'] + int_stats = list(stats['int'].items()) + normal_stats = list(stats['normal'].items()) + flat_stats = dict(int_stats + normal_stats) + values = [] + for key in keys: + values.append(flat_stats[key]) + csv_writer.writerow(keys) + csv_writer.writerow(values) + csv_file_out.flush() + + +def get_harness_code(): + all_harness_code = '\nGenerated harness code:\n' + for filename in os.listdir(DEFAULT_BUCK_OUT_GEN): + if 'InferGeneratedHarness' in filename: + all_harness_code += '\n' + filename + ':\n' + with open(os.path.join(DEFAULT_BUCK_OUT_GEN, + filename), 'r') as file_in: + all_harness_code += file_in.read() + return all_harness_code + '\n' + + +def get_basic_stats(stats): + files_analyzed = '{0} files ({1} lines) analyzed in {2}s\n\n'.format( + stats['int']['files'], + stats['int']['lines'], + stats['int']['total_time'], + ) + phase_times = 'Capture time: {0}s\nAnalysis time: {1}s\n\n'.format( + stats['int']['capture_time'], + stats['int']['analysis_time'], + ) + + to_skip = { + 'files', + 'lines', + 'cores', + 'time', + 'start_time', + 'capture_time', + 'analysis_time', + 'reporting_time', + 'total_time', + } + bugs_found = 'Errors found:\n\n' + for key, value in sorted(stats['int'].items()): + if key not in to_skip: + bugs_found += ' {0:>8} {1}\n'.format(value, key) + + basic_stats_message = files_analyzed + phase_times + bugs_found + '\n' + return basic_stats_message + + +def get_buck_stats(): + trace_filename = os.path.join( + DEFAULT_BUCK_OUT, + 'log', + 'traces', + 'build.trace' + ) + ARGS = 'args' + SUCCESS_STATUS = 'success_type' + buck_stats = {} + try: + with open(trace_filename, 'r') as file_in: + trace = json.load(file_in) + for t in trace: + if SUCCESS_STATUS in t[ARGS]: + status = t[ARGS][SUCCESS_STATUS] + count = buck_stats.get(status, 0) + buck_stats[status] = count + 1 + + buck_stats_message = 'Buck build statistics:\n\n' + for key, value in sorted(buck_stats.items()): + buck_stats_message += ' {0:>8} {1}\n'.format(value, key) + + return buck_stats_message + except IOError as e: + logging.error('Caught %s: %s' % (e.__class__.__name__, str(e))) + logging.error(traceback.format_exc()) + return '' + + +class NotFoundInJar(Exception): + pass + + +def load_stats(opened_jar): + try: + return json.loads(opened_jar.read(INFER_STATS).decode()) + except KeyError as e: + raise NotFoundInJar + + +def load_report(opened_jar): + try: + sio = io.StringIO(opened_jar.read(INFER_REPORT).decode()) + return list(csv.reader(sio)) + except KeyError as e: + raise NotFoundInJar + + +def rows_remove_duplicates(rows): + seen = {} + result = [] + for row in rows: + t = tuple(row) + if t in seen: + continue + seen[t] = 1 + result.append(row) + return result + + +def collect_results(args, start_time): + """Walks through buck-gen, collects results for the different buck targets + and stores them in in args.infer_out/results.csv. + """ + + buck_stats = get_buck_stats() + logging.info(buck_stats) + with open(os.path.join(args.infer_out, ANALYSIS_SUMMARY_OUTPUT), 'w') as f: + f.write(buck_stats) + + all_rows = [] + headers = [] + stats = init_stats(args, start_time) + + accumulation_whitelist = list(map(re.compile, [ + '^cores$', + '^time$', + '^start_time$', + '.*_pc', + ])) + + expected_analyzer = stats['normal']['analyzer'] + expected_version = stats['normal']['infer_version'] + + for root, _, files in os.walk(DEFAULT_BUCK_OUT_GEN): + for f in [f for f in files if f.endswith('.jar')]: + path = os.path.join(root, f) + try: + with zipfile.ZipFile(path) as jar: + # Accumulate integers and float values + target_stats = load_stats(jar) + + found_analyzer = target_stats['normal']['analyzer'] + found_version = target_stats['normal']['infer_version'] + + if (found_analyzer != expected_analyzer + or found_version != expected_version): + continue + else: + for type_k in ['int', 'float']: + items = target_stats.get(type_k, {}).items() + for key, value in items: + if not any(map(lambda r: r.match(key), + accumulation_whitelist)): + old_value = stats[type_k].get(key, 0) + stats[type_k][key] = old_value + value + + rows = load_report(jar) + if len(rows) > 0: + headers.append(rows[0]) + all_rows.extend(rows[1:]) + + # Override normals + stats['normal'].update(target_stats.get('normal', {})) + except NotFoundInJar: + pass + except zipfile.BadZipfile: + logging.warn('Bad zip file %s', path) + + csv_report = os.path.join(args.infer_out, utils.CSV_REPORT_FILENAME) + bugs_out = os.path.join(args.infer_out, utils.BUGS_FILENAME) + + if len(headers) == 0: + with open(csv_report, 'w'): + pass + logging.info('No reports found') + return + elif len(headers) > 1: + if any(map(lambda x: x != headers[0], headers)): + raise Exception('Inconsistent reports found') + + # Convert all float values to integer values + for key, value in stats.get('float', {}).items(): + stats['int'][key] = int(round(value)) + + # Delete the float entries before exporting the results + del(stats['float']) + + with open(csv_report, 'w') as report: + writer = csv.writer(report) + writer.writerows([headers[0]] + rows_remove_duplicates(all_rows)) + report.flush() + + # export the CSV rows to JSON + utils.create_json_report(args.infer_out) + + print('\n') + inferlib.print_errors(csv_report, bugs_out, args.analyzer) + + stats['int']['total_time'] = int(round(utils.elapsed_time(start_time))) + + store_performances_csv(args.infer_out, stats) + + stats_filename = os.path.join(args.infer_out, utils.STATS_FILENAME) + with open(stats_filename, 'w') as stats_out: + json.dump(stats, stats_out) + + basic_stats = get_basic_stats(stats) + + if args.print_harness: + harness_code = get_harness_code() + basic_stats += harness_code + + logging.info(basic_stats) + + with open(os.path.join(args.infer_out, ANALYSIS_SUMMARY_OUTPUT), 'a') as f: + f.write(basic_stats) + + +def cleanup(temp_files): + """Removes the generated .buckconfig.local and the temporary inferJ script. + """ + for file in [BUCK_CONFIG] + temp_files: + try: + logging.info('Removing %s' % file) + if os.path.isdir(file): + shutil.rmtree(file) + else: + os.unlink(file) + except IOError: + logging.error('Could not remove %s' % file) + + if os.path.isfile(BUCK_CONFIG_BACKUP): + logging.info('Restoring %s', BUCK_CONFIG) + shutil.move(BUCK_CONFIG_BACKUP, BUCK_CONFIG) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(parents=[inferlib.base_parser]) + parser.add_argument('--verbose', action='store_true', + help='Print buck compilation steps') + parser.add_argument('--no-cache', action='store_true', + help='Do not use buck distributed cache') + parser.add_argument('--print-harness', action='store_true', + help='Print generated harness code (Android only)') + parser.add_argument('targets', nargs='*', metavar='target', + help='Build targets to analyze') + args = parser.parse_args() + + utils.configure_logging(args.verbose) + timer = utils.Timer(logging.info) + temp_files = [] + + try: + start_time = time.time() + logging.info('Starting the analysis') + subprocess.check_call( + [utils.get_cmd_in_bin_dir('InferAnalyze'), '-version']) + + if not os.path.isdir(args.infer_out): + os.mkdir(args.infer_out) + + timer.start('Preparing build...') + temp_files += prepare_build(args) + timer.stop('Build prepared') + + # TODO(t3786463) Start buckd. + + timer.start('Computing library targets') + determine_library_targets(args) + timer.stop('%d targets computed', len(args.targets)) + + timer.start('Running buck...') + buck_cmd = ['buck', 'build'] + if args.no_cache: + buck_cmd += ['--no-cache'] + if args.verbose: + buck_cmd += ['-v', '2'] + subprocess.check_call(buck_cmd + args.targets) + timer.stop('Buck finished') + + timer.start('Collecting results...') + collect_results(args, start_time) + timer.stop('Done') + + except KeyboardInterrupt as e: + timer.stop('Exiting') + sys.exit(0) + except Exception as e: + timer.stop('Failed') + logging.error('Caught %s: %s' % (e.__class__.__name__, str(e))) + logging.error(traceback.format_exc()) + sys.exit(1) + finally: + cleanup(temp_files) + + +# vim: set sw=4 ts=4 et: diff --git a/infer/bin/infer b/infer/bin/infer new file mode 100755 index 000000000..bfcea7464 --- /dev/null +++ b/infer/bin/infer @@ -0,0 +1,137 @@ +#!/usr/bin/env python + +import argparse +import imp +import inferlib +import os +import sys + +SCRIPT_FOLDER = os.path.dirname(__file__) +CAPTURE_PACKAGE = 'capture' +LIB_FOLDER = os.path.join( + os.path.dirname(__file__), os.path.pardir, 'lib') + +# token that identifies the end of the options for infer and the beginning +# of the compilation command +CMD_MARKER = '--' + +# insert here the correspondence between module name and the list of +# compiler/build-systems it handles. +# All supported commands should be listed here +MODULE_TO_COMMAND = { + 'ant': ['ant'], + 'analyze': ['analyze'], + 'buck': ['buck'], + 'gradle': ['gradle', 'gradlew'], + 'javac': ['javac'], + 'make': ['make', 'clang', 'clang++', 'cc', 'gcc', 'g++'], + 'xcodebuild': ['xcodebuild'], + 'mvn': ['mvn'] +} + + +def get_commands(): + """Return all commands that are supported.""" + #flatten and dedup the list of commands + return set(sum(MODULE_TO_COMMAND.values(), [])) + + +def get_module_name(command): + """ Return module that is able to handle the command. None if + there is no such module.""" + for module, commands in MODULE_TO_COMMAND.iteritems(): + if command in commands: + return module + return None + + +def load_module(mod_name): + # load the 'capture' package in lib + pkg_info = imp.find_module(CAPTURE_PACKAGE, [LIB_FOLDER]) + imported_pkg = imp.load_module(CAPTURE_PACKAGE, *pkg_info) + # load the requested module (e.g. make) + mod_file, mod_path, mod_descr = \ + imp.find_module(mod_name, imported_pkg.__path__) + try: + return imp.load_module( + '{pkg}.{mod}'.format(pkg=imported_pkg.__name__, mod=mod_name), + mod_file, mod_path, mod_descr) + finally: + if mod_file: + mod_file.close() + + +def split_args_to_parse(): + dd_index = \ + sys.argv.index(CMD_MARKER) if CMD_MARKER in sys.argv else len(sys.argv) + return sys.argv[1:dd_index], sys.argv[dd_index + 1:] + + +def create_argparser(parents=[]): + parser = argparse.ArgumentParser( + parents=[inferlib.inferJ_parser] + parents, + add_help=False, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + group = parser.add_argument_group( + 'supported compiler/build-system commands') + + supported_commands = ', '.join(get_commands()) + group.add_argument( + CMD_MARKER, + metavar='', + dest='nullarg', + default=None, + help=('Command to run the compiler/build-system. ' + 'Supported build commands (run `infer --help -- ` for ' + 'extra help, e.g. `infer --help -- javac`): ' + supported_commands), + ) + return parser + + +def main(): + to_parse, cmd = split_args_to_parse() + # get the module name (if any), then load it + capture_module_name = os.path.basename(cmd[0]) if len(cmd) > 0 else None + mod_name = get_module_name(capture_module_name) + imported_module = None + if mod_name: + # There is module that supports the command + imported_module = load_module(mod_name) + + # get the module's argparser and merge it with the global argparser + module_argparser = [] + if imported_module: + module_argparser.append( + imported_module.create_argparser(capture_module_name) + ) + global_argparser = create_argparser(module_argparser) + + args = global_argparser.parse_args(to_parse) + + if imported_module: + if capture_module_name != 'analyze' and not args.incremental: + inferlib.remove_infer_out(args.infer_out) + capture_exitcode = imported_module.gen_instance(args, cmd).capture() + if capture_exitcode != os.EX_OK: + exit(capture_exitcode) + elif capture_module_name is not None: + # There was a command, but it's not supported + print('Command "{cmd}" not recognised'.format( + cmd='' if capture_module_name is None else capture_module_name)) + global_argparser.print_help() + sys.exit(1) + else: + global_argparser.print_help() + sys.exit(os.EX_OK) + + if not (mod_name == 'buck' or mod_name == 'javac'): + # Something should be already captured, otherwise analysis would fail + if not os.path.exists(os.path.join(args.infer_out, 'captured')): + print('There was nothing to analyze, exiting') + exit(os.EX_USAGE) + analysis = inferlib.Infer(args, []) + analysis.analyze_and_report() + analysis.save_stats() + +main() diff --git a/infer/bin/inferJ b/infer/bin/inferJ new file mode 100755 index 000000000..0dabe2c38 --- /dev/null +++ b/infer/bin/inferJ @@ -0,0 +1,28 @@ +#!/usr/bin/env python +# +# Copyright (c) 2013- Facebook. All rights reserved. +# + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import logging +import sys + +import inferlib + +if __name__ == '__main__': + cmd_args = sys.argv[1:] + analysis = inferlib.Infer(inferlib.get_inferJ_args(cmd_args), + inferlib.get_javac_args(cmd_args)) + stats = analysis.start() + + if stats: + logging.info('Capture time: {0:.2f}s'.format( + stats['float']['capture_time'] + )) + logging.info('Analysis time: {0:.2f}s'.format( + stats['float']['analysis_time'] + )) diff --git a/infer/bin/inferTest b/infer/bin/inferTest new file mode 100755 index 000000000..229545d9a --- /dev/null +++ b/infer/bin/inferTest @@ -0,0 +1,24 @@ +#!/bin/bash -e + +# Copyright (c) 2013 - Facebook. +# All rights reserved. + +# This script takes a buck target and runs the respective tests. + +# Arguments: +# $1 : buck target +# [$2] : "keep" or "replace". Keep will keep the temporary folders used in the tests. +# Replace will replace the saved dot files with the new created ones. + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +cd $SCRIPT_DIR/../../ + +case $2 in + keep) + INFER_KEEP_FOLDER=Y buck test --no-results-cache $1 ;; + replace) + INFER_DOT_REPLACE=Y buck test --no-results-cache $1 ;; + *) + buck test --no-results-cache $1 ;; +esac diff --git a/infer/bin/inferlib.py b/infer/bin/inferlib.py new file mode 100644 index 000000000..fb04c5195 --- /dev/null +++ b/infer/bin/inferlib.py @@ -0,0 +1,648 @@ +# +# Copyright (c) 2013- Facebook. All rights reserved. +# + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import argparse +import csv +import glob +import json +import logging +import multiprocessing +import os +import re +import shutil +import subprocess +import sys +import tempfile +import time +import xml.etree.ElementTree as ET + +# Increase the limit of the CSV parser to sys.maxlimit +csv.field_size_limit(sys.maxsize) + +# Infer imports +import jwlib +import utils + +# list of analysis options +INFER = 'infer' +ERADICATE = 'eradicate' +CHECKERS = 'checkers' +CAPTURE = 'capture' +COMPILE = 'compile' +TRACING = 'tracing' + +MODES = [COMPILE, CAPTURE, INFER, ERADICATE, CHECKERS, TRACING] + +INFER_ANALYZE_BINARY = "InferAnalyze" + +class AbsolutePathAction(argparse.Action): + """Convert a path from relative to absolute in the arg parser""" + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, os.path.abspath(values)) + + +# https://github.com/python/cpython/blob/aa8ea3a6be22c92e774df90c6a6ee697915ca8ec/Lib/argparse.py +class VersionAction(argparse._VersionAction): + def __call__(self, parser, namespace, values, option_string=None): + # set self.version so that argparse version action knows it + self.version = self.get_infer_version() + super(VersionAction, self).__call__(parser, + namespace, + values, + option_string) + + def get_infer_version(self): + try: + return subprocess.check_output([ + utils.get_cmd_in_bin_dir(INFER_ANALYZE_BINARY), '-version']) + except: + print("Failed to run {0} binary, exiting". + format(INFER_ANALYZE_BINARY)) + exit(2) + + +base_parser = argparse.ArgumentParser(add_help=False) +base_group = base_parser.add_argument_group('global arguments') +base_group.add_argument('-o', '--out', metavar='', + default=utils.DEFAULT_INFER_OUT, dest='infer_out', + action=AbsolutePathAction, + help='Set the INFER results directory') +base_group.add_argument('-i', '--incremental', action='store_true', + help='''Do not delete the results directory across + Infer runs''') +base_group.add_argument('-g', '--debug', action='store_true', + help='Generate extra debugging information') +base_group.add_argument('-a', '--analyzer', + help='Select the analyzer within: {0}'.format( + ', '.join(MODES)), + default=INFER) +base_group.add_argument('-m', '--analyzer_mode', metavar='', + help='''Select a special analyzer mode such as + graphql1 or graphql2''') +base_parser.add_argument('-v', '--version', help='Get version of the analyzer', + action=VersionAction) + + +inferJ_parser = argparse.ArgumentParser(parents=[base_parser]) +inferJ_group = inferJ_parser.add_argument_group('backend arguments') +inferJ_group.add_argument('-j', '--multicore', metavar='n', type=int, + default=multiprocessing.cpu_count(), + dest='multicore', help='Set the number of cores to ' + 'be used for the analysis (default uses all cores)') +inferJ_group.add_argument('-x', '--project', metavar='', + help='Project name, for recording purposes only') + +inferJ_group.add_argument('-r', '--revision', metavar='', + help='The githash, for recording purposes only') + +inferJ_group.add_argument('--buck', action='store_true', dest='buck', + help='To use when run with buck') + +inferJ_group.add_argument('--infer_cache', metavar='', + help='Select a directory to contain the infer cache') + +inferJ_group.add_argument('-pr', '--project_root', + dest='project_root', + default=os.getcwd(), + help='Location of the project root ' + '(default is current directory)') + +inferJ_group.add_argument('--objc_ml_buckets', + dest='objc_ml_buckets', + help='memory leak buckets to be checked, ' + 'separated by commas. The possible ' + 'buckets are cf (Core Foundation), ' + 'arc, narc (No arc)') + +inferJ_group.add_argument('-nt', '--notest', action='store_true', + dest='notest', + help='Prints output of symbolic execution') + +def detect_javac(args): + for index, arg in enumerate(args): + if arg == 'javac': + return index + + +def get_inferJ_args(args): + index = detect_javac(args) + if index is None: + cmd_args = args + else: + cmd_args = args[:index] + return inferJ_parser.parse_args(cmd_args) + + +def get_javac_args(args): + javac_args = args[detect_javac(args) + 1:] + if len(javac_args) == 0: + return None + else: + # replace any -g:.* flag with -g to preserve debugging symbols + return map(lambda arg: '-g' if '-g:' in arg else arg, javac_args) + + +def remove_infer_out(infer_out): + # it is safe to ignore errors here because recreating the infer_out + # directory will fail later + shutil.rmtree(infer_out, True) + + +def clean_infer_out(infer_out): + + directories = ['multicore', 'classnames', 'sources'] + extensions = ['.cfg', '.cg'] + + for root, dirs, files in os.walk(infer_out): + for d in dirs: + if d in directories: + path = os.path.join(root, d) + shutil.rmtree(path) + for f in files: + for ext in extensions: + if f.endswith(ext): + path = os.path.join(root, f) + os.remove(path) + + +def help_exit(message): + print(message) + inferJ_parser.print_usage() + exit(1) + + +def compare_rows(row_1, row_2): + filename_1 = row_1[utils.CSV_INDEX_FILENAME] + filename_2 = row_2[utils.CSV_INDEX_FILENAME] + if filename_1 < filename_2: + return -1 + elif filename_1 > filename_2: + return 1 + else: + line_1 = int(row_1[utils.CSV_INDEX_LINE]) + line_2 = int(row_2[utils.CSV_INDEX_LINE]) + return line_1 - line_2 + + +def sort_csv(csv_report, infer_out): + collected_rows = [] + with open(csv_report, 'r') as file_in: + reader = csv.reader(file_in) + rows = [row for row in reader] + if len(rows) <= 1: + return rows + else: + for row in rows[1:]: + filename = row[utils.CSV_INDEX_FILENAME] + if os.path.isfile(filename): + collected_rows.append(row) + collected_rows = sorted( + collected_rows, + cmp=compare_rows) + collected_rows = [rows[0]] + collected_rows + temporary_file = tempfile.mktemp() + with open(temporary_file, 'w') as file_out: + writer = csv.writer(file_out) + writer.writerows(collected_rows) + file_out.flush() + shutil.move(temporary_file, csv_report) + + +def should_print(analyzer, row): + error_kind = row[utils.CSV_INDEX_KIND] + error_type = row[utils.CSV_INDEX_TYPE] + error_bucket = '' # can be updated later once we extract it from qualifier + + try: + qualifier_xml = ET.fromstring(row[utils.CSV_INDEX_QUALIFIER_TAGS]) + if qualifier_xml.tag == utils.QUALIFIER_TAGS: + bucket = qualifier_xml.find(utils.BUCKET_TAGS) + if bucket is not None: + error_bucket = bucket.text + except ET.ParseError: + pass # this will skip any invalid xmls + + # config what to print is listed below + error_kinds = ['ERROR', 'WARNING'] + + null_style_bugs = [ + 'NULL_DEREFERENCE', + 'PARAMETER_NOT_NULL_CHECKED', + 'IVAR_NOT_NULL_CHECKED', + 'PREMATURE_NIL_TERMINATION_ARGUMENT', + ] + null_style_buckets = ['B1', 'B2'] + + other_bugs = ['RESOURCE_LEAK', 'MEMORY_LEAK', 'RETAIN_CYCLE'] + + filter_by_type = True + if analyzer in [ERADICATE, CHECKERS]: + # report all issues for eredicate and checkers + filter_by_type = False + + if error_kind not in error_kinds: + return False + + if not filter_by_type: + return True + + if not error_type: + return False + + if error_type in null_style_bugs: + if error_bucket and error_bucket in null_style_buckets: + return True + else: + return False + elif error_type in other_bugs: + return True + else: + return False + + +def remove_bucket(bug_message): + """ Remove anything from the beginning if the message that + looks like a bucket """ + return re.sub(r'(^\[[a-zA-Z0-9]*\])', '', bug_message, 1) + + +def print_and_write(file_out, message): + print(message) + file_out.write(message + '\n') + + +def print_errors(csv_report, bugs_out, analyzer): + with open(csv_report, 'r') as file_in: + reader = csv.reader(file_in) + reader.next() # first line is header, skip it + + errors = filter( + lambda row: should_print(analyzer, row), + reader + ) + with open(bugs_out, 'w') as file_out: + if not errors: + print_and_write(file_out, 'No issues found') + for row in errors: + filename = row[utils.CSV_INDEX_FILENAME] + if os.path.isfile(filename): + kind = row[utils.CSV_INDEX_KIND] + line = row[utils.CSV_INDEX_LINE] + error_type = row[utils.CSV_INDEX_TYPE] + msg = remove_bucket(row[utils.CSV_INDEX_QUALIFIER]) + print_and_write( + file_out, + '{0}:{1}: {2}: {3}\n {4}\n'.format( + filename, + line, + kind.lower(), + error_type, + msg, + ) + ) + + +def run_command(cmd, debug_mode, javac_arguments, step, analyzer): + if debug_mode: + print('\n{0}\n'.format(' '.join(cmd))) + try: + return subprocess.check_call(cmd) + except subprocess.CalledProcessError as e: + error_msg = 'Failure during {0}, original command was\n\n{1}\n\n' + inferJ_cmd = ['inferJ', '-g', '-a', analyzer] + failing_cmd = inferJ_cmd + ['javac'] + javac_arguments + logging.error(error_msg.format( + step, + failing_cmd + )) + raise e + + +class Infer: + + def __init__(self, args, javac_args): + + self.args = args + if self.args.analyzer not in MODES: + help_exit( + 'Unknown analysis mode \"{0}\"'.format(self.args.analyzer) + ) + + utils.configure_logging(self.args.debug) + + self.javac = jwlib.CompilerCall(javac_args) + + if not self.javac.args.version: + if javac_args is None: + help_exit('No javac command detected') + + if self.args.infer_out is None: + help_exit('Expect INFER results directory') + + if self.args.buck: + self.args.infer_out = os.path.join( + self.javac.args.classes_out, + utils.BUCK_INFER_OUT) + self.args.infer_out = os.path.abspath(self.args.infer_out) + + try: + os.mkdir(self.args.infer_out) + except OSError as e: + if not os.path.isdir(self.args.infer_out): + raise e + + self.stats = {'int': {}, 'float': {}} + self.timing = {} + + def clean_exit(self): + if os.path.isdir(self.args.infer_out): + print('removing', self.args.infer_out) + shutil.rmtree(self.args.infer_out) + exit(os.EX_OK) + + def run_infer_frontend(self): + + infer_cmd = [utils.get_cmd_in_bin_dir('InferJava')] + + if self.args.buck: + infer_cmd += ['-project_root', os.getcwd()] + + infer_cmd += [ + '-results_dir', self.args.infer_out, + '-verbose_out', self.javac.verbose_out, + ] + + if os.path.isfile(utils.MODELS_JAR): + infer_cmd += ['-models', utils.MODELS_JAR] + + infer_cmd.append('-no-static_final') + + if self.args.debug: + infer_cmd.append('-debug') + if self.args.analyzer == TRACING: + infer_cmd.append('-tracing') + + return run_command( + infer_cmd, + self.args.debug, + self.javac.original_arguments, + 'frontend', + self.args.analyzer + ) + + def compile(self): + return self.javac.run() + + def capture(self): + javac_status = self.compile() + if javac_status == os.EX_OK: + return self.run_infer_frontend() + else: + return javac_status + + def analyze(self): + infer_analyze = [ + utils.get_cmd_in_bin_dir(INFER_ANALYZE_BINARY), + '-results_dir', + self.args.infer_out + ] + infer_options = [] + + # remove specs if possible so that old issues are less likely + # to be reported + infer_options += ['-allow_specs_cleanup'] + + if self.args.analyzer == ERADICATE: + infer_options += ['-checkers', '-eradicate'] + elif self.args.analyzer == CHECKERS: + infer_options += ['-checkers'] + else: + if self.args.analyzer == TRACING: + infer_options.append('-tracing') + if os.path.isfile(utils.MODELS_JAR): + infer_options += ['-models', utils.MODELS_JAR] + + if self.args.analyzer_mode: + infer_options += ['-analyzer_mode', self.args.analyzer_mode] + + if self.args.infer_cache: + infer_options += ['-infer_cache', self.args.infer_cache] + + if self.args.objc_ml_buckets: + infer_options += ['-objc_ml_buckets', self.args.objc_ml_buckets] + + if self.args.notest: + infer_options += ['-notest'] + + if self.args.debug: + infer_options += [ + '-developer_mode', + '-html', + '-dotty', + '-print_types', + '-trace_error', + # '-notest', + ] + + exit_status = os.EX_OK + + if self.args.buck: + infer_options += ['-project_root', os.getcwd(), '-java'] + if self.javac.args.classpath is not None: + for path in self.javac.args.classpath.split(os.pathsep): + if os.path.isfile(path): + infer_options += ['-ziplib', os.path.abspath(path)] + elif self.args.project_root: + infer_options += ['-project_root', self.args.project_root] + + if self.args.multicore == 1: + analyze_cmd = infer_analyze + infer_options + exit_status = run_command( + analyze_cmd, + self.args.debug, + self.javac.original_arguments, + 'analysis', + self.args.analyzer + ) + + else: + if self.args.analyzer in [ERADICATE, CHECKERS]: + infer_analyze.append('-intraprocedural') + + os.environ['INFER_OPTIONS'] = ' '.join(infer_options) + + multicore_dir = os.path.join(self.args.infer_out, 'multicore') + pwd = os.getcwd() + if os.path.isdir(multicore_dir): + shutil.rmtree(multicore_dir) + os.mkdir(multicore_dir) + os.chdir(multicore_dir) + os.environ['INFER_DEVELOPER'] = 'Y' + analyze_cmd = infer_analyze + ['-makefile', 'Makefile'] + analyze_cmd += infer_options + makefile_status = run_command( + analyze_cmd, + self.args.debug, + self.javac.original_arguments, + 'create_makefile', + self.args.analyzer + ) + exit_status += makefile_status + if makefile_status == os.EX_OK: + make_cmd = ['make', '-k', '-j', str(self.args.multicore)] + if not self.args.debug: + make_cmd += ['-s'] + make_status = run_command( + make_cmd, + self.args.debug, + self.javac.original_arguments, + 'run_makefile', + self.args.analyzer + ) + os.chdir(pwd) + exit_status += make_status + + if self.args.buck and exit_status == os.EX_OK: + clean_infer_out(self.args.infer_out) + + cfgs = os.path.join(self.args.infer_out, 'captured', '*', '*.cfg') + captured_total = len(glob.glob(cfgs)) + captured_plural = '' if captured_total <= 1 else 's' + print('\n%d file%s analyzed' % (captured_total, captured_plural)) + + return exit_status + + def file_stats(self, file, stats): + if file is not None: + stats['files'] += 1 + try: + with open(file, 'r') as f: + stats['lines'] += len(list(f)) + except IOError: + logging.warning('File {} not found'.format(file)) + + + def javac_stats(self): + stats = {'files': 0, 'lines': 0} + + for arg in self.javac.original_arguments: + file = None + if arg.endswith('.java'): + file = arg + self.file_stats(file, stats) + if arg.startswith('@'): + with open(arg[1:], 'r') as f: + for line in f: + file = line.strip() + self.file_stats(file, stats) + + return stats + + def update_stats(self, csv_report): + with open(csv_report, 'r') as file_in: + reader = csv.reader(file_in) + rows = [row for row in reader][1:] + for row in rows: + key = row[utils.CSV_INDEX_TYPE] + previous_value = self.stats['int'].get(key, 0) + self.stats['int'][key] = previous_value + 1 + + def report_errors(self): + """Report statistics about the computation and create a CSV file + containing the list or errors found during the analysis""" + + csv_report = os.path.join(self.args.infer_out, + utils.CSV_REPORT_FILENAME) + bugs_out = os.path.join(self.args.infer_out, + utils.BUGS_FILENAME) + procs_report = os.path.join(self.args.infer_out, 'procs.csv') + + infer_print_cmd = [utils.get_cmd_in_bin_dir('InferPrint')] + infer_print_options = [ + '-q', + '-results_dir', self.args.infer_out, + '-bugs', csv_report, + '-procs', procs_report, + '-analyzer', self.args.analyzer + ] + exit_status = subprocess.check_call( + infer_print_cmd + infer_print_options + ) + if exit_status != os.EX_OK: + logging.error('Error with InferPrint with the command: ' + + infer_print_cmd) + else: + sort_csv(csv_report, self.args.infer_out) + self.update_stats(csv_report) + utils.create_json_report(self.args.infer_out) + + print('\n') + if not self.args.buck: + print_errors(csv_report, bugs_out, self.args.analyzer) + + return exit_status + + def save_stats(self): + """Print timing information to infer_out/stats.json""" + stats_path = os.path.join(self.args.infer_out, utils.STATS_FILENAME) + + self.stats['int'].update(self.javac_stats()) + + self.stats['float'].update({ + 'capture_time': self.timing.get('capture', 0.0), + 'analysis_time': self.timing.get('analysis', 0.0), + 'reporting_time': self.timing.get('reporting', 0.0), + }) + + # Adding the analyzer and the version of Infer + self.stats['normal'] = {} + self.stats['normal']['analyzer'] = self.args.analyzer + self.stats['normal']['infer_version'] = utils.infer_version() + + with open(stats_path, 'w') as stats_file: + json.dump(self.stats, stats_file) + + def close(self): + if self.args.analyzer != COMPILE: + os.remove(self.javac.verbose_out) + + def analyze_and_report(self): + if self.args.analyzer not in [COMPILE, CAPTURE]: + analysis_start_time = time.time() + if self.analyze() == os.EX_OK: + elapsed = utils.elapsed_time(analysis_start_time) + self.timing['analysis'] = elapsed + reporting_start_time = time.time() + self.report_errors() + elapsed = utils.elapsed_time(reporting_start_time) + self.timing['reporting'] = elapsed + + def start(self): + if self.javac.args.version: + if self.args.buck: + key = self.args.analyzer + if self.args.analyzer_mode: + key += '_' + self.args.analyzer_mode + print(utils.infer_key(key), file=sys.stderr) + else: + return self.javac.run() + else: + start_time = time.time() + if self.capture() == os.EX_OK: + self.timing['capture'] = utils.elapsed_time(start_time) + self.analyze_and_report() + self.close() + elapsed = utils.elapsed_time(start_time) + self.timing['total'] = elapsed + self.save_stats() + return self.stats + else: + return dict({}) + +# vim: set sw=4 ts=4 et: diff --git a/infer/bin/jwlib.py b/infer/bin/jwlib.py new file mode 100644 index 000000000..94727b866 --- /dev/null +++ b/infer/bin/jwlib.py @@ -0,0 +1,52 @@ +# Copyright (c) 2009-2013 Monoidics ltd. +# Copyright (c) 2013- Facebook. +# All rights reserved. + +import argparse +import logging +import tempfile +import os +import subprocess + +# javac options +parser = argparse.ArgumentParser() + +current_directory = os.getcwd() + +parser.add_argument('-version', action='store_true') +parser.add_argument('-cp', '-classpath', type=str, dest='classpath') +parser.add_argument('-bootclasspath', type=str) +parser.add_argument('-d', dest='classes_out') + + +class CompilerCall: + + def __init__(self, arguments): + + self.original_arguments = arguments + self.args, _ = parser.parse_known_args(arguments) + self.verbose_out = None + + def run(self): + if self.args.version: + return subprocess.call(['javac'] + self.original_arguments) + else: + javac_cmd = ['javac', '-verbose', '-g'] + self.original_arguments + + with tempfile.NamedTemporaryFile( + mode='w', + suffix='.out', + prefix='javac_', + delete=False) as file_out: + self.verbose_out = file_out.name + + try: + subprocess.check_call(javac_cmd, stderr=file_out) + return os.EX_OK + except subprocess.CalledProcessError as exc: + error_msg = 'Javac compilation error with: \n\n{}\n' + failing_cmd = [arg for arg in javac_cmd + if arg != '-verbose'] + logging.error(error_msg.format(failing_cmd)) + os.system(' '.join(failing_cmd)) + return exc.returncode diff --git a/infer/bin/utils.py b/infer/bin/utils.py new file mode 100644 index 000000000..11ea5cb45 --- /dev/null +++ b/infer/bin/utils.py @@ -0,0 +1,345 @@ +# +# Copyright (c) 2013- Facebook. All rights reserved. +# + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import csv +import fnmatch +import gzip +import json +import logging +import os +import subprocess +import sys +import tempfile +import time + + +BIN_DIRECTORY = os.path.dirname(os.path.realpath(__file__)) +LIB_DIRECTORY = os.path.join(BIN_DIRECTORY, '..', 'lib', 'java') +TMP_DIRECTORY = tempfile.gettempdir() +MODELS_JAR = os.path.join(LIB_DIRECTORY, 'models.jar') + + +DEFAULT_INFER_OUT = os.path.join(os.getcwd(), 'infer-out') +CSV_PERF_FILENAME = 'performances.csv' +STATS_FILENAME = 'stats.json' + +CSV_REPORT_FILENAME = 'report.csv' +JSON_REPORT_FILENAME = 'report.json' +BUGS_FILENAME = 'bugs.txt' + +CSV_INDEX_KIND = 1 +CSV_INDEX_TYPE = 2 +CSV_INDEX_QUALIFIER = 3 +CSV_INDEX_LINE = 5 +CSV_INDEX_FILENAME = 8 +CSV_INDEX_QUALIFIER_TAGS = 11 + +QUALIFIER_TAGS = 'qualifier_tags' +BUCKET_TAGS = 'bucket' + +IOS_CAPTURE_ERRORS = 'errors' +IOS_BUILD_OUTPUT = 'build_output' + +BUCK_INFER_OUT = 'infer' + +FORMAT = '[%(levelname)s] %(message)s' +DEBUG_FORMAT = '[%(levelname)s:%(filename)s:%(lineno)03d] %(message)s' + + +# Monkey patching subprocess (I'm so sorry!). +if "check_output" not in dir(subprocess): + def f(*popenargs, **kwargs): + if 'stdout' in kwargs: + raise ValueError('stdout not supported') + process = subprocess.Popen( + stdout=subprocess.PIPE, + *popenargs, + **kwargs) + output, unused_err = process.communicate() + retcode = process.poll() + if retcode: + cmd = kwargs.get("args") + if cmd is None: + cmd = popenargs[0] + raise subprocess.CalledProcessError(retcode, cmd) + return output + subprocess.check_output = f + + +def configure_logging(debug, quiet=False): + """Configures the default logger. This can be called only once and has to + be called before any logging is done. + """ + logging.TIMING = logging.ERROR + 5 + logging.addLevelName(logging.TIMING, "TIMING") + + def timing(msg, *args, **kwargs): + logging.log(logging.TIMING, msg, *args, **kwargs) + + logging.timing = timing + if quiet: + logging.basicConfig(level=logging.TIMING, format=FORMAT) + elif not debug: + logging.basicConfig(level=logging.INFO, format=FORMAT) + else: + logging.basicConfig(level=logging.DEBUG, format=DEBUG_FORMAT) + + +def elapsed_time(start_time): + return time.time() - start_time + + +def error(msg): + print(msg, file=sys.stderr) + + +def get_cmd_in_bin_dir(binary_name): + # this relies on the fact that utils.py is located in infer/bin + return os.path.join( + os.path.dirname(os.path.realpath(__file__)), + binary_name) + + +def write_cmd_streams_to_file(logfile, cmd=None, out=None, err=None): + with open(logfile, 'w') as log_filedesc: + if cmd: + log_filedesc.write(' '.join(cmd) + '\n') + if err is not None: + errors = str(err) + log_filedesc.write('\nSTDERR:\n') + log_filedesc.write(errors) + if out is not None: + output = str(out) + log_filedesc.write('\n\nSTDOUT:\n') + log_filedesc.write(output) + + +def save_failed_command( + infer_out, + cmd, + message, + prefix='failed_', + out=None, + err=None): + cmd_filename = tempfile.mktemp( + '_' + message + ".txt", + prefix, infer_out + ) + write_cmd_streams_to_file(cmd_filename, cmd=cmd, out=out, err=err) + logging.error('\n' + message + ' error saved in ' + cmd_filename) + + +def run_command(cmd, debug_mode, infer_out, message, env=os.environ): + if debug_mode: + print('\n{0}\n'.format(' '.join(cmd))) + try: + return subprocess.check_call(cmd, env=env) + except subprocess.CalledProcessError as e: + save_failed_command(infer_out, cmd, message) + raise e + + +def print_exit(s): + print(s) + exit(os.EX_OK) + + +def infer_version(): + version = json.loads(subprocess.check_output([ + get_cmd_in_bin_dir('InferAnalyze'), + '-version_json', + ]).decode()) + return version['commit'] + + +def infer_branch(): + version = json.loads(subprocess.check_output([ + get_cmd_in_bin_dir('InferAnalyze'), + '-version_json', + ]).decode()) + return version['branch'] + + +def infer_key(analyzer): + return os.pathsep.join([analyzer, infer_version()]) + + +def vcs_branch(dir='.'): + cwd = os.getcwd() + try: + os.chdir(dir) + + branch = subprocess.check_output([ + 'git', + 'rev-parse', + '--abbrev-ref', + 'HEAD', + ]).decode().strip() + except subprocess.CalledProcessError: + try: + branch = subprocess.check_output([ + 'hg', + 'id', + '-B', + ]).decode().strip() + except subprocess.CalledProcessError: + branch = 'not-versioned' + finally: + os.chdir(cwd) + return branch + + +def vcs_revision(dir='.'): + cwd = os.getcwd() + try: + os.chdir(dir) + + revision = subprocess.check_output([ + 'git', + 'rev-parse', + 'HEAD', + ]).decode().strip() + except subprocess.CalledProcessError: + try: + revision = subprocess.check_output([ + 'hg', + 'id', + '-i', + ]).decode().strip() + except subprocess.CalledProcessError: + revision = 'not-versioned' + finally: + os.chdir(cwd) + return revision + + +class Timer: + """Simple logging timer. Initialize with a printf like logging function.""" + def __init__(self, logger=lambda x: None): + self._logger = logger + self._start = 0 + + def start(self, message=None, *args): + self._start = time.time() + if message: + self._logger(message, *args) + + def stop(self, message=None, *args): + self._stop = time.time() + self._dt = self._stop - self._start + if message: + self._logger(message + ' (%.2fs)', *(args + (self._dt,))) + return self._dt + + + +def interact(): + """Start interactive mode. Useful for debugging. + """ + import code + code.interact(local=locals()) + + +def search_files(root_dir, extension): + # Input: + # - root directory where to start a recursive search of yjson files + # - file extension to search from the root + # Output: + # - list of absolute filepaths + files = [] + if not os.path.isabs(root_dir): + root_dir = os.path.abspath(root_dir) + for dirpath, _, filenames in os.walk(root_dir): + for filename in fnmatch.filter(filenames, "*" + extension): + files.append(os.path.join(dirpath, filename)) + return files + + +def uncompress_gzip_file(gzip_file, out_dir): + # This is python2.6 compliant, gzip.open doesn't support 'with' statement + # Input: + # - gzip file path + # - output directory where uncompress the file + # Output: + # - path of the uncompressed file + # NOTE: the file is permanently created, is responsibility of the + # caller to delete it + uncompressed_path = None + uncompressed_fd = None + compressed_fd = None + try: + # the uncompressed filename loses its final extension + # (for example abc.gz -> abc) + uncompressed_path = os.path.join( + out_dir, + os.path.splitext(gzip_file)[0], + ) + uncompressed_fd = open(uncompressed_path, 'wb') + compressed_fd = gzip.open(gzip_file, 'rb') + uncompressed_fd.write(compressed_fd.read()) + return uncompressed_path + except IOError as exc: + # delete the uncompressed file (if exists) + if uncompressed_path is not None and os.path.exists(uncompressed_path): + os.remove(uncompressed_path) + raise exc + finally: + if compressed_fd is not None: + compressed_fd.close() + if uncompressed_fd is not None: + uncompressed_fd.close() + + +def run_process(cmd, cwd=None, logfile=None): + # Input: + # - command to execute + # - current working directory to cd before running the cmd + # - logfile where to dump stdout/stderr + # Output: + # - exitcode of the executed process + p = subprocess.Popen( + cmd, + cwd=cwd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + (out, err) = p.communicate() + if logfile: + write_cmd_streams_to_file(logfile, cmd=cmd, out=out, err=err) + return p.returncode + + +def invoke_function_with_callbacks( + func, + args, + on_terminate=None, + on_exception=None): + try: + res = func(*args) + if on_terminate: + on_terminate(res) + return res + except Exception as exc: + if on_exception: + return on_exception(exc) + raise + + +def create_json_report(out_dir): + csv_report_filename = os.path.join(out_dir, CSV_REPORT_FILENAME) + json_report_filename = os.path.join(out_dir, JSON_REPORT_FILENAME) + rows = [] + with open(csv_report_filename, 'r') as file_in: + reader = csv.reader(file_in) + rows = [row for row in reader] + with open(json_report_filename, 'w') as file_out: + headers = rows[0] + issues = rows[1:] + json.dump([dict(zip(headers, row)) for row in issues], file_out) + +# vim: set sw=4 ts=4 et: diff --git a/infer/lib/capture/__init__.py b/infer/lib/capture/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/infer/lib/capture/analyze.py b/infer/lib/capture/analyze.py new file mode 100644 index 000000000..c4698c8f6 --- /dev/null +++ b/infer/lib/capture/analyze.py @@ -0,0 +1,24 @@ +import os +import util + +MODULE_NAME = __name__ +MODULE_DESCRIPTION = '''Run analysis of what has already been captured: +Usage: +infer -- analyze +infer --out -- analyze''' + + +def gen_instance(*args): + return NoCapture(*args) + +# This creates an empty argparser for the module, which provides only +# description/usage information and no arguments. +create_argparser = util.base_argparser(MODULE_DESCRIPTION, MODULE_NAME) + + +class NoCapture: + def __init__(self, args, cmd): + self.args = args + + def capture(self): + return os.EX_OK diff --git a/infer/lib/capture/ant.py b/infer/lib/capture/ant.py new file mode 100644 index 000000000..543e71cfb --- /dev/null +++ b/infer/lib/capture/ant.py @@ -0,0 +1,69 @@ +import os +import util + +MODULE_NAME = __name__ +MODULE_DESCRIPTION = '''Run analysis of code built with a command like: +ant [options] [target] + +Analysis examples: +infer -- ant compile''' + +def gen_instance(*args): + return AntCapture(*args) + +# This creates an empty argparser for the module, which provides only +# description/usage information and no arguments. +create_argparser = util.base_argparser(MODULE_DESCRIPTION, MODULE_NAME) + + +class AntCapture: + + def __init__(self, args, cmd): + self.args = args + # TODO: make the extraction of targets smarter + self.build_cmd = ['ant', '-verbose'] + cmd[1:] + + def is_interesting(self, content): + return self.is_quoted(content) or content.endswith('.java') + + def is_quoted(self, argument): + quote = '\'' + return len(argument) > 2 and argument[0] == quote\ + and argument[-1] == quote + + def remove_quotes(self, argument): + if self.is_quoted(argument): + return argument[1:-1] + else: + return argument + + def get_inferJ_commands(self, verbose_output): + javac_pattern = '[javac]' + argument_start_pattern = 'Compilation arguments' + calls = [] + javac_arguments = [] + collect = False + for line in verbose_output: + if javac_pattern in line: + if argument_start_pattern in line: + collect = True + if javac_arguments != []: + capture = util.create_inferJ_command(self.args, + javac_arguments) + calls.append(capture) + javac_arguments = [] + if collect: + pos = line.index(javac_pattern) + len(javac_pattern) + content = line[pos:].strip() + if self.is_interesting(content): + arg = self.remove_quotes(content) + javac_arguments.append(arg) + if javac_arguments != []: + capture = util.create_inferJ_command(self.args, javac_arguments) + calls.append(capture) + javac_arguments = [] + return calls + + def capture(self): + cmds = self.get_inferJ_commands(util.get_build_output(self.build_cmd)) + return util.run_commands(cmds) diff --git a/infer/lib/capture/buck.py b/infer/lib/capture/buck.py new file mode 100644 index 000000000..657a8e1c1 --- /dev/null +++ b/infer/lib/capture/buck.py @@ -0,0 +1,42 @@ +import os +import subprocess +import traceback +import util + +import utils # this is module located in ../utils.py + +MODULE_NAME = __name__ +MODULE_DESCRIPTION = '''Run analysis of code built with a command like: +buck [options] [target] + +Analysis examples: +infer -- buck build HelloWorld''' + + +def gen_instance(*args): + return BuckAnalyzer(*args) + +# This creates an empty argparser for the module, which provides only +# description/usage information and no arguments. +create_argparser = util.base_argparser(MODULE_DESCRIPTION, MODULE_NAME) + + +class BuckAnalyzer: + def __init__(self, args, cmd): + self.args = args + self.cmd = cmd[2:] # TODO: make the extraction of targets smarter + + def capture(self): + # BuckAnalyze is a special case, and we run the analysis from here + capture_cmd = [utils.get_cmd_in_bin_dir('BuckAnalyze')] + if self.args.debug: + capture_cmd.append('-g') + capture_cmd += self.cmd + capture_cmd += ['--analyzer', self.args.analyzer] + try: + subprocess.check_call(capture_cmd) + return os.EX_OK + except subprocess.CalledProcessError as exc: + if self.args.debug: + traceback.print_exc() + return exc.returncode diff --git a/infer/lib/capture/clang b/infer/lib/capture/clang new file mode 100755 index 000000000..7f8a0ba7b --- /dev/null +++ b/infer/lib/capture/clang @@ -0,0 +1,19 @@ +#!/bin/bash + +SCRIPT_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +if [ "${0%++}" != "$0" ]; then XX="++"; else XX=""; fi + +export FCP_CLANG_COMPILER="${SCRIPT_PATH%/}/../clang/clang_wrapper$XX"; +export FCP_RESULTS_DIR="${INFER_RESULTS_DIR}"; +export FCP_USE_STD_CLANG_CMD="1"; + +if [ -z $INFER_RESULTS_DIR ]; then + # this redirects to the compiler without adding any FCP flag + # this is because xcode requires message category info from the compiler + # and invokes it without any env var set. + "$FCP_CLANG_COMPILER" "$@" + exit $? +fi + +"${SCRIPT_PATH%/}/../clang/clang_general_wrapper$XX" "$@" diff --git a/infer/lib/capture/clang++ b/infer/lib/capture/clang++ new file mode 120000 index 000000000..060d289be --- /dev/null +++ b/infer/lib/capture/clang++ @@ -0,0 +1 @@ +clang \ No newline at end of file diff --git a/infer/lib/capture/gradle.py b/infer/lib/capture/gradle.py new file mode 100644 index 000000000..941562f69 --- /dev/null +++ b/infer/lib/capture/gradle.py @@ -0,0 +1,42 @@ +import os +import util + +MODULE_NAME = __name__ +MODULE_DESCRIPTION = '''Run analysis of code built with a command like: +gradle [options] [task] + +Analysis examples: +infer -- gradle build +infer -- ./gradlew build''' + + +def gen_instance(*args): + return GradleCapture(*args) + +# This creates an empty argparser for the module, which provides only +# description/usage information and no arguments. +create_argparser = util.base_argparser(MODULE_DESCRIPTION, MODULE_NAME) + + +class GradleCapture: + + def __init__(self, args, cmd): + self.args = args + # TODO: make the extraction of targets smarter + self.build_cmd = [cmd[0], '--debug'] + cmd[1:] + + def get_inferJ_commands(self, verbose_output): + argument_start_pattern = ' Compiler arguments: ' + calls = [] + for line in verbose_output: + if argument_start_pattern in line: + content = line.partition(argument_start_pattern)[2].strip() + javac_arguments = content.split(' ') + capture = util.create_inferJ_command(self.args, + javac_arguments) + calls.append(capture) + return calls + + def capture(self): + cmds = self.get_inferJ_commands(util.get_build_output(self.build_cmd)) + return util.run_commands(cmds) diff --git a/infer/lib/capture/javac.py b/infer/lib/capture/javac.py new file mode 100644 index 000000000..fedad8cc4 --- /dev/null +++ b/infer/lib/capture/javac.py @@ -0,0 +1,44 @@ +import os +import subprocess +import traceback +import util + +import utils # this is module located in ../utils.py +MODULE_NAME = __name__ +MODULE_DESCRIPTION = '''Run analysis of code built with a command like: +javac + +Analysis examples: +infer -- javac srcfile.java''' + + +def gen_instance(*args): + return JavacCapture(*args) + +# This creates an empty argparser for the module, which provides only +# description/usage information and no arguments. +create_argparser = util.base_argparser(MODULE_DESCRIPTION, MODULE_NAME) + + +class JavacCapture: + def __init__(self, args, cmd): + self.args = args + self.cmd = cmd + + def capture(self): + # run inferJ only in capture mode + # pass all the frontend args (if any) + capture_cmd = [utils.get_cmd_in_bin_dir('inferJ')] + capture_cmd += ['--out', self.args.infer_out] + capture_cmd += ['--analyzer', self.args.analyzer] + if self.args.debug: + capture_cmd.append('-g') + capture_cmd += self.cmd + + try: + subprocess.check_call(capture_cmd) + return os.EX_OK + except subprocess.CalledProcessError as exc: + if self.args.debug: + traceback.print_exc() + return exc.returncode diff --git a/infer/lib/capture/make.py b/infer/lib/capture/make.py new file mode 100644 index 000000000..7116b439a --- /dev/null +++ b/infer/lib/capture/make.py @@ -0,0 +1,131 @@ +import argparse +import os +import subprocess +import traceback + +MODULE_NAME = 'make/cc/clang/gcc' +MODULE_DESCRIPTION = '''Run analysis of code built with commands like: +make [target] +clang [compiler_options] +gcc [compiler_options] +cc [compiler_options] + +Analysis examples: +infer -- make all +infer -- clang -c srcfile.m +infer -- gcc -c srcfile.c''' + + +def gen_instance(*args): + return MakeCapture(*args) + + +def mkdir_if_not_exists(path): + if not os.path.exists(path): + os.mkdir(path) + + +def create_argparser(group_name=MODULE_NAME): + """This defines the set of arguments that get added by this module to the + set of global args defined in the infer top-level module + Do not use this function directly, it should be invoked by the infer + top-level module""" + parser = argparse.ArgumentParser(add_help=False) + group = parser.add_argument_group( + "{grp} module".format(grp=MODULE_NAME), + description=MODULE_DESCRIPTION, + ) + group.add_argument( + '-hd', '--headers', + action='store_true', + help='Analyze code in header files', + ) + group.add_argument( + '--models_mode', + action='store_true', + dest='models_mode', + help='Mode for computing the models', + ) + group.add_argument( + '--no_failures_allowed', + action='store_true', + dest='no_failures_allowed', + help='Fail if at least one of the translations fails', + ) + group.add_argument( + '-tm', '--testing_mode', + dest='testing_mode', + action='store_true', + help='Testing mode for the translation: Do not translate libraries' + ' (including enums)') + group.add_argument( + '-fs', '--frontend-stats', + dest='frontend_stats', + action='store_true', + help='Output statistics about the capture phase to *.o.astlog') + group.add_argument( + '-fd', '--frontend-debug', + dest='frontend_debug', + action='store_true', + help='Output debugging information to *.o.astlog during capture') + return parser + + +class MakeCapture: + def __init__(self, args, cmd): + self.args = args + self.cmd = [os.path.basename(cmd[0])] + cmd[1:] + + def create_results_dir(self): + results_dir = self.args.infer_out + mkdir_if_not_exists(results_dir) + mkdir_if_not_exists(os.path.join(results_dir, 'specs')) + mkdir_if_not_exists(os.path.join(results_dir, 'captured')) + mkdir_if_not_exists(os.path.join(results_dir, 'sources')) + + def get_envvars(self): + env_vars = dict(os.environ) + env_vars['INFER_RESULTS_DIR'] = self.args.infer_out + wrappers_path = os.path.join( + os.path.dirname(os.path.realpath(__file__)), '..', 'wrappers') + env_vars['INFER_OLD_PATH'] = env_vars['PATH'] + env_vars['PATH'] = '{wrappers}{sep}{path}'.format( + wrappers=wrappers_path, + sep=os.pathsep, + path=env_vars['PATH'], + ) + return env_vars + + def capture(self): + self.create_results_dir() + + env_vars = self.get_envvars() + frontend_args = [] + + if self.args.headers: + frontend_args.append('-headers') + if self.args.models_mode: + frontend_args.append('-models_mode') + if self.args.project_root: + frontend_args += ['-project_root', self.args.project_root] + if self.args.testing_mode: + frontend_args.append('-testing_mode') + if self.args.frontend_debug: + frontend_args += ['-debug'] + env_vars['FCP_DEBUG_MODE'] = '1' + if self.args.frontend_stats: + frontend_args += ['-stats'] + env_vars['FCP_DEBUG_MODE'] = '1' + if self.args.no_failures_allowed: + env_vars['FCP_REPORT_FRONTEND_FAILURE'] = '1' + + # export an env variable with all the arguments to pass to InferClang + env_vars['FCP_INFER_FRONTEND_ARGS'] = ' '.join(frontend_args) + + try: + subprocess.check_call(self.cmd, env=env_vars) + return os.EX_OK + except subprocess.CalledProcessError as exc: + if self.args.debug: + traceback.print_exc() + return exc.returncode diff --git a/infer/lib/capture/mvn.py b/infer/lib/capture/mvn.py new file mode 100644 index 000000000..04c154e63 --- /dev/null +++ b/infer/lib/capture/mvn.py @@ -0,0 +1,59 @@ +# +# Copyright (c) 2013- Facebook. All rights reserved. +# + +import os +import re +import util + +MODULE_NAME = __name__ +MODULE_DESCRIPTION = '''Run analysis of code built with a command like: +mvn [options] [task] + +Analysis examples: +infer -- mvn build''' + +def gen_instance(*args): + return MavenCapture(*args) + +# This creates an empty argparser for the module, which provides only +# description/usage information and no arguments. +create_argparser = util.base_argparser(MODULE_DESCRIPTION, MODULE_NAME) + + +class MavenCapture: + def __init__(self, args, cmd): + self.args = args + # TODO: make the extraction of targets smarter + self.build_cmd = ['mvn', '-X'] + cmd[1:] + + def get_inferJ_commands(self, verbose_output): + file_pattern = r'\[DEBUG\] Stale source detected: ([^ ]*\.java)' + options_pattern = '[DEBUG] Command line options:' + + files_to_compile = [] + calls = [] + options_next = False + for line in verbose_output: + if options_next: + # line has format [Debug] + javac_args = line.split(' ')[1:] + files_to_compile + capture = util.create_inferJ_command(self.args, javac_args) + calls.append(capture) + options_next = False + files_to_compile = [] + + elif options_pattern in line: + # Next line will have javac options to run + options_next = True + + else: + found = re.match(file_pattern, line) + if found: + files_to_compile.append(found.group(1)) + + return calls + + def capture(self): + cmds = self.get_inferJ_commands(util.get_build_output(self.build_cmd)) + return util.run_commands(cmds) diff --git a/infer/lib/capture/util.py b/infer/lib/capture/util.py new file mode 100644 index 000000000..c0812f97b --- /dev/null +++ b/infer/lib/capture/util.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python + +# Copyright (c) 2013- Facebook. +# All rights reserved. + +import argparse +import os +import subprocess +import inferlib + + +def create_inferJ_command(args, javac_arguments): + infer_args = ['-o', args.infer_out] + if args.debug: + infer_args.append('--debug') + infer_args += ['--analyzer', 'capture'] + + return inferlib.Infer(inferlib.inferJ_parser.parse_args(infer_args), + inferlib.get_javac_args(['javac'] + javac_arguments)) + + +def get_build_output(build_cmd): + # TODO make it return generator to be able to handle large builds + proc = subprocess.Popen(build_cmd, stdout=subprocess.PIPE) + (verbose_out_chars, _) = proc.communicate() + return verbose_out_chars.split('\n') + + +def run_commands(cmds): + # TODO call it in parallel + if len(cmds) == 0: + return os.EX_NOINPUT + for cmd in cmds: + if not cmd.start(): + return os.EX_SOFTWARE + return os.EX_OK + + +def base_argparser(description, module_name): + def _func(group_name=module_name): + """This creates an empty argparser for the module, which provides only + description/usage information and no arguments.""" + parser = argparse.ArgumentParser(add_help=False) + group = parser.add_argument_group( + "{grp} module".format(grp=group_name), + description=description, + ) + return parser + return _func diff --git a/infer/lib/capture/xcodebuild.py b/infer/lib/capture/xcodebuild.py new file mode 100644 index 000000000..4701f2b19 --- /dev/null +++ b/infer/lib/capture/xcodebuild.py @@ -0,0 +1,64 @@ +import os +import subprocess +import traceback +import util + +MODULE_NAME = __name__ +MODULE_DESCRIPTION = '''Run analysis of code built with a command like: +xcodebuild [options] + +Analysis examples: +infer -- xcodebuild -target HelloWorldApp -sdk iphonesimulator +infer -- xcodebuild -workspace HelloWorld.xcworkspace -scheme HelloWorld''' + +SCRIPT_DIR = os.path.dirname(__file__) +INFER_ROOT = os.path.join(SCRIPT_DIR, '..', '..', '..') +FCP_ROOT = os.path.join(INFER_ROOT, '..', 'facebook-clang-plugin') +CLANG_WRAPPER = os.path.join( + SCRIPT_DIR, 'clang', +) +CLANGPLUSPLUS_WRAPPER = os.path.join( + SCRIPT_DIR, 'clang++', +) + + +def gen_instance(*args): + return XcodebuildCapture(*args) + +# This creates an empty argparser for the module, which provides only +# description/usage information and no arguments. +create_argparser = util.base_argparser(MODULE_DESCRIPTION, MODULE_NAME) + + +class XcodebuildCapture: + def __init__(self, args, cmd): + self.args = args + self.cmd = cmd + + def capture(self): + env_vars = dict(os.environ) + + # get the path to 'true' using xcrun + true_path = subprocess.check_output(['xcrun', '--find', 'true']).strip() + + # these settings will instruct xcodebuild on which clang to use + # and to not run any linking + self.cmd += ['LD={true_path}'.format(true_path=true_path)] + self.cmd += ['LDPLUSPLUS={true_path}'.format(true_path=true_path)] + self.cmd += ['CC={wrapper}'.format(wrapper=CLANG_WRAPPER)] + self.cmd += ['CPLUSPLUS={wrapper}'.format(wrapper=CLANGPLUSPLUS_WRAPPER)] + self.cmd += ['LIPO={true_path}'.format(true_path=true_path)] + + env_vars['INFER_RESULTS_DIR'] = self.args.infer_out + + # fix the GenerateDSYMFile error + self.cmd += ["DEBUG_INFORMATION_FORMAT='dwarf'"] + + try: + subprocess.check_call(self.cmd, env=env_vars) + return os.EX_OK + except subprocess.CalledProcessError as exc: + if self.args.debug: + traceback.print_exc() + print(exc.output) + return exc.returncode diff --git a/infer/lib/clang/clang_general_wrapper b/infer/lib/clang/clang_general_wrapper new file mode 100755 index 000000000..906cadd24 --- /dev/null +++ b/infer/lib/clang/clang_general_wrapper @@ -0,0 +1,166 @@ +#!/bin/bash +# Clang wrapper to inject the execution of a plugin and execute the infer frontend + +# Initialization +PARENT=$(dirname "$0") +SCRIPT_DIR=$(cd "$PARENT" && pwd) +SCRIPT_DIR="${SCRIPT_DIR%/}" +BIN_DIR="${SCRIPT_DIR}/../../bin" + +#### Configuration #### +# path to the wrapped clang compiler to invoke +CLANG_COMPILER="${SCRIPT_DIR}/clang_wrapper" +# extension of the file containing the clang cmd intercepted +CMD_FILE_EXT=".sh" +# extenion of the file containing the output of the Infer Clang frontend +INFERCLANG_LOG_FILE_EXT=".astlog" +# path of the plugin to load in clang +CLANG_PLUGIN_REL_PATH="facebook-clang-plugin/libtooling/build/FacebookClangPlugin.dylib" +PLUGIN_PATH="${SCRIPT_DIR}/../../../../${CLANG_PLUGIN_REL_PATH}" +# name of the plugin to use +PLUGIN_NAME="YojsonASTExporter" +# output directory of the plugin +RESULTS_DIR="${FCP_RESULTS_DIR}" +# space-separated list of source file extensions to compile, where the plugin is needed +# e.g. EXTENSIONS="c cpp m mm" (*note* no dots needed) +EXTENSIONS="${FCP_EXTENSIONS}" +# this forces the wrapper to invoke get_standard_commandline_args to get +# a more precise clang command with all the arguments in the right place (slow) +USE_STD_CLANG_CMD="${FCP_USE_STD_CLANG_CMD}" +# this skips the creation of .o files +SYNTAX_ONLY="${FCP_RUN_SYNTAX_ONLY}" +# extra arguments to pass during the execution of the infer frontend +INFER_FRONTEND_ARGS=($FCP_INFER_FRONTEND_ARGS) +# this fails the execution of clang if the frontend fails +REPORT_FRONTEND_FAILURE="${FCP_REPORT_FRONTEND_FAILURE}" +# enable debug mode (to get more data saved to disk for future inspections) +DEBUG_MODE="${FCP_DEBUG_MODE}" + +if [ -z "$RESULTS_DIR" ]; then + echo '$FCP_RESULTS_DIR with the name of the output directory not provided.' > /dev/stderr + exit 1 +fi + +if [ "${0%++}" != "$0" ]; then XX="++"; else XX=""; fi +CLANG_CMD=("${CLANG_COMPILER}${XX}" "$@") +CWD=$(pwd) +CWD="${CWD%/}" +[ ! -d "$RESULTS_DIR" ] && mkdir -p "$RESULTS_DIR" + +# If no extensions provided, will use default ones (c, h, cc, cpp, m, mm) +if [ -z "$EXTENSIONS" ]; then + EXTENSIONS="c h cc cpp m mm" +fi + +# regular expression for grep to look for specific extensions +# (for example c,h,m files) +EXTENSIONS_REGEX="\.($(echo $EXTENSIONS | tr ' ' '|'))$" + +# Functions +function get_option_argument { + # retrieves the value passed to an argument of a clang command + OPT="$1" + shift + while [ -n "$1" ] && [ "$1" != "$OPT" ]; do shift; done + echo "$2" +} + +function has_flag { + # return if the given flag is part of the given command or not + local FLAG="$1" + shift + while [ -n "$1" ] && [ "$1" != "$FLAG" ]; do shift; done + [ -n "$1" ]; echo "$?" +} + +# Main +INPUT_ARGUMENTS=("$@") +if [ -n "$USE_STD_CLANG_CMD" ]; then + # this will run clang with the -### argument to get the command in a more + # standard format. + # Slow since it spawns clang as a separate process + STD_CMD="$($CLANG_COMPILER$XX -### "$@" 2>&1 | grep '^[[:space:]]\"' -m 1)" + # use sed to split all the arguments, and remove their surrounding double quotes + SED_CMD=$(echo "$STD_CMD" | sed -e 's/" "/\'$'\n/g' -e 's/^[[:space:]]*"//' -e 's/"[[:space:]]*$//') + IFS=$'\n' + # create an array of arguments using newline as separator + INPUT_ARGUMENTS=($SED_CMD) + unset IFS +fi + +OBJECT_FILENAME="$(get_option_argument "-o" "${INPUT_ARGUMENTS[@]}")" + +if echo "$OBJECT_FILENAME" | grep -q "\.o$" +then + # get the source file name + if [ -n "$USE_STD_CLANG_CMD" ]; then + # the source file is at the end of the command, match it with the wanted extensions + SOURCE_FILE=$(echo ${INPUT_ARGUMENTS[${#INPUT_ARGUMENTS[@]} - 1]} \ + | grep -i -E "$EXTENSIONS_REGEX") + else + # in this case we search for the argument after -c, match it with the wanted extensions + SOURCE_FILE=$(get_option_argument "-c" "${INPUT_ARGUMENTS[@]}" \ + | grep -i -E "$EXTENSIONS_REGEX") + fi + + if [ -n "$SOURCE_FILE" ] + then + ATTACH_PLUGIN="1" + IFS=$'\n' + EXTRA_ARGS=("-Xclang" "-load" + "-Xclang" "${PLUGIN_PATH}" + "-Xclang" "-add-plugin" + "-Xclang" "${PLUGIN_NAME}") + if [ -n "$SYNTAX_ONLY" ]; then + EXTRA_ARGS+=("-fsyntax-only") + fi + unset IFS + + # using always the original clang command for several reasons: + # - to avoid handling the presence/absence of -Xclang if the standard command is used + # - to emit the same command that was captured by this wrapper + # - to invoke the linker, whenever is needed + CLANG_CMD+=("${EXTRA_ARGS[@]}") + fi +fi + +if [ -n "$ATTACH_PLUGIN" ]; then + FOBJC_ARC_FLAG=$(has_flag "-fobjc-arc" "${INPUT_ARGUMENTS[@]}") + LANGUAGE=$(get_option_argument "-x" "${INPUT_ARGUMENTS[@]}") + + if [ -n "$LANGUAGE" ]; then INFER_FRONTEND_ARGS+=("-x" "$LANGUAGE"); fi + if [ "$FOBJC_ARC_FLAG" == 0 ]; then INFER_FRONTEND_ARGS+=("-fobjc-arc"); fi + + INFERCLANG_CMD=( + "${BIN_DIR}/InferClang" + "-c" "$SOURCE_FILE" + "-results_dir" "$RESULTS_DIR" + "${INFER_FRONTEND_ARGS[@]}") + + INFERCLANG_LOG_FILE="/dev/null" + + if [ -n "$DEBUG_MODE" ]; then + # Emit the clang command with the extra args + echo "${CLANG_CMD[@]}" > "${OBJECT_FILENAME}${CMD_FILE_EXT}" + # Emit the InferClang cmd used to run the frontend + INFERCLANG_LOG_FILE="${OBJECT_FILENAME}${INFERCLANG_LOG_FILE_EXT}" + echo "${INFERCLANG_CMD[@]}" > "$INFERCLANG_LOG_FILE" + fi + + export CLANG_FRONTEND_PLUGIN__PREPEND_CURRENT_DIR="1" + # run clang and pipe its output to InferClang, or flush it in case the latter crashes + "${CLANG_CMD[@]}" | ("${INFERCLANG_CMD[@]}" || { EC=$?; cat > /dev/null; exit $EC; }) >> "$INFERCLANG_LOG_FILE" 2>&1 + STATUSES=("${PIPESTATUS[@]}") + STATUS="${STATUSES[0]}" + INFERCLANG_STATUS="${STATUSES[1]}" + + # if clang fails, then fail, otherwise, fail with the frontend's exitcode if required + if [ "$STATUS" == 0 ] && [ -n "$REPORT_FRONTEND_FAILURE" ]; then + STATUS="$INFERCLANG_STATUS" + fi +else + "${CLANG_CMD[@]}" + STATUS=$? +fi + +exit $STATUS diff --git a/infer/lib/clang/clang_general_wrapper++ b/infer/lib/clang/clang_general_wrapper++ new file mode 120000 index 000000000..67b0ba784 --- /dev/null +++ b/infer/lib/clang/clang_general_wrapper++ @@ -0,0 +1 @@ +clang_general_wrapper \ No newline at end of file diff --git a/infer/lib/clang/clang_wrapper b/infer/lib/clang/clang_wrapper new file mode 100755 index 000000000..a57bc22aa --- /dev/null +++ b/infer/lib/clang/clang_wrapper @@ -0,0 +1,28 @@ +#!/bin/bash +# Wrapper around the opensource clang meant to work around various path or library +# issues occurring when one tries to substitute Apple's version of clang with +# a different version. +# The wrapper tries to mitigate version discrepancies in clang's fatal warnings. + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +CLANG_COMPILER="${SCRIPT_DIR}/../../../../facebook-clang-plugin/clang/bin/clang" + +if [ "${0%++}" != "$0" ]; then XX="++"; else XX=""; fi + +COMMAND=("${CLANG_COMPILER}${XX}") + +# Remove command line options not supported by the opensource compiler or the plugins. +for X in "$@" +do + if [ "$X" != "-fapplication-extension" ] && [ "$X" != "-fmodules" ] + then + COMMAND+=("$X") + fi +done + +# Never error on warnings. Clang is often more strict than Apple's version. +# These arguments are appended to override previous opposite settings. +COMMAND+=(-Wno-error -Qunused-arguments) + +"${COMMAND[@]}" diff --git a/infer/lib/clang/clang_wrapper++ b/infer/lib/clang/clang_wrapper++ new file mode 120000 index 000000000..4fc0dde1b --- /dev/null +++ b/infer/lib/clang/clang_wrapper++ @@ -0,0 +1 @@ +clang_wrapper \ No newline at end of file diff --git a/infer/lib/java/android/BUCK b/infer/lib/java/android/BUCK new file mode 100644 index 000000000..9a16e5377 --- /dev/null +++ b/infer/lib/java/android/BUCK @@ -0,0 +1,7 @@ +prebuilt_jar( + name = 'android', + binary_jar = 'android-19.jar', + visibility = [ + 'PUBLIC' + ] +) diff --git a/infer/lib/java/android/android-19.jar b/infer/lib/java/android/android-19.jar new file mode 100644 index 000000000..b9aada2db Binary files /dev/null and b/infer/lib/java/android/android-19.jar differ diff --git a/infer/lib/specs/models b/infer/lib/specs/models new file mode 100644 index 000000000..e69de29bb diff --git a/infer/lib/wrappers/c++ b/infer/lib/wrappers/c++ new file mode 120000 index 000000000..060d289be --- /dev/null +++ b/infer/lib/wrappers/c++ @@ -0,0 +1 @@ +clang \ No newline at end of file diff --git a/infer/lib/wrappers/cc b/infer/lib/wrappers/cc new file mode 120000 index 000000000..060d289be --- /dev/null +++ b/infer/lib/wrappers/cc @@ -0,0 +1 @@ +clang \ No newline at end of file diff --git a/infer/lib/wrappers/clang b/infer/lib/wrappers/clang new file mode 100755 index 000000000..7e6758ed4 --- /dev/null +++ b/infer/lib/wrappers/clang @@ -0,0 +1,29 @@ +#!/bin/bash + +# This is a wrapper for clang/clang++ gcc/g++ + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +if [ -z "$INFER_RESULTS_DIR" ]; then + echo '$INFER_RESULTS_DIR with a path to the results dir not provided.' > /dev/stderr + exit 1 +fi + +# invoke the right compiler looking at the final plusplus (e.g. gcc/g++ clang/clang++) +if [ "${0%++}" != "$0" ]; then XX="++"; else XX=""; fi +FRONTEND_COMMAND=("$SCRIPT_DIR/../clang/clang_general_wrapper$XX" "$@") +HOST_COMPILER_COMMAND=("$SCRIPT_DIR/../clang/clang_wrapper$XX" "$@") + +if [ -n "$INFER_COMPILER_WRAPPER_IN_RECURSION" ]; then + if [ -z "$INFER_LISTENER" ]; then + "${HOST_COMPILER_COMMAND[@]}" + fi +else + export INFER_COMPILER_WRAPPER_IN_RECURSION="Y" + export FCP_RESULTS_DIR="$INFER_RESULTS_DIR"; + export FCP_USE_STD_CLANG_CMD="1"; + + "${FRONTEND_COMMAND[@]}" +fi + +exit $? diff --git a/infer/lib/wrappers/clang++ b/infer/lib/wrappers/clang++ new file mode 120000 index 000000000..060d289be --- /dev/null +++ b/infer/lib/wrappers/clang++ @@ -0,0 +1 @@ +clang \ No newline at end of file diff --git a/infer/lib/wrappers/g++ b/infer/lib/wrappers/g++ new file mode 120000 index 000000000..060d289be --- /dev/null +++ b/infer/lib/wrappers/g++ @@ -0,0 +1 @@ +clang \ No newline at end of file diff --git a/infer/lib/wrappers/gcc b/infer/lib/wrappers/gcc new file mode 120000 index 000000000..060d289be --- /dev/null +++ b/infer/lib/wrappers/gcc @@ -0,0 +1 @@ +clang \ No newline at end of file diff --git a/infer/lib/wrappers/javac b/infer/lib/wrappers/javac new file mode 100755 index 000000000..4ea1f1ff4 --- /dev/null +++ b/infer/lib/wrappers/javac @@ -0,0 +1,27 @@ +#!/bin/bash + +# This is a wrapper for javac + +if [ -z "$INFER_RESULTS_DIR" ]; then + echo '$INFER_RESULTS_DIR with a path to the results dir not provided.' > /dev/stderr + exit 1 +elif [ -z "$INFER_OLD_PATH" ]; then + echo '$INFER_OLD_PATH with a copy of $PATH not provided.' > /dev/stderr + exit 1 +fi + +HOST_COMPILER=(`PATH=$INFER_OLD_PATH which javac`) +COMPILER_ARGS="$@" +HOST_COMPILER_COMMAND=("$HOST_COMPILER" $COMPILER_ARGS) +FRONTEND_COMMAND=("inferJ" "-a" "capture" "-o" "$INFER_RESULTS_DIR" "javac" $COMPILER_ARGS) + +if [ -n "$INFER_COMPILER_WRAPPER_IN_RECURSION" ]; then + if [ -z "$INFER_LISTENER" ]; then + "${HOST_COMPILER_COMMAND[@]}" + fi +else + export INFER_COMPILER_WRAPPER_IN_RECURSION="Y" + "${FRONTEND_COMMAND[@]}" +fi + +exit $? diff --git a/infer/models/Makefile b/infer/models/Makefile new file mode 100644 index 000000000..23c67e207 --- /dev/null +++ b/infer/models/Makefile @@ -0,0 +1,57 @@ +MAKE = make +JAVA_MODELS = java +C_MODELS = c +CPP_MODELS = cpp +OBJC_MODELS = objc +CWD = $(shell pwd) +BINDIR = $(CWD)/../bin +LIBDIR = $(CWD)/../lib +LIB_SPECS = $(LIBDIR)/specs + +INFERANALYZE = $(BINDIR)/InferAnalyze +INFERCLANG = $(BINDIR)/InferClang +INFERJAVA = $(BINDIR)/InferJava +JAVA_SCRIPTS = $(addprefix $(BINDIR)/, jwlib.py inferlib.py inferJ) +CLANG_SCRIPTS = $(addprefix $(BINDIR)/, inferiOS) # Add more once this part is stable + +PLATFORM = $(shell uname) + +JAVA_MODELS_JAR = ../lib/java/models.jar + +JAVA_MODELS_SOURCES = $(shell find $(JAVA_MODELS)/src -name "*.java") +C_MODELS_SOURCES = $(shell find $(C_MODELS)/src -name "*.c") +CPP_MODELS_SOURCES = $(shell find $(CPP_MODELS)/src -name "*.cpp") +OBJC_MODELS_SOURCES = $(shell find $(OBJC_MODELS)/src -name "*.m" -or -name "*.c") + +C_MODELS_FILE = $(LIB_SPECS)/c_models +CPP_MODELS_FILE = $(LIB_SPECS)/cpp_models +OBJC_MODELS_FILE = $(LIB_SPECS)/objc_models + +.PHONY: java clang clean + +java: $(JAVA_MODELS_JAR) + +clang: $(C_MODELS_FILE) $(CPP_MODELS_FILE) $(OBJC_MODELS_FILE) + +$(JAVA_MODELS_JAR): $(JAVA_MODELS_SOURCES) $(INFERANALYZE) $(INFERJAVA) $(JAVA_SCRIPTS) + make -C $(JAVA_MODELS) + +$(C_MODELS_FILE): $(C_MODELS_SOURCES) $(INFERANALYZE) $(INFERCLANG) + make -C $(C_MODELS) install + +$(CPP_MODELS_FILE): $(CPP_MODELS_SOURCES) $(INFERANALYZE) $(INFERCLANG) + make -C $(CPP_MODELS) install + +$(OBJC_MODELS_FILE): $(OBJC_MODELS_SOURCES) $(INFERANALYZE) $(INFERCLANG) +ifeq ($(PLATFORM), Darwin) + make -C $(OBJC_MODELS) install +else + @echo "Platform $(PLATFORM) not supported for objc models, skipping." +endif + +clean: + rm -f $(LIB_SPECS)/*.specs + $(MAKE) -C $(JAVA_MODELS) clean + $(MAKE) -C $(C_MODELS) clean + $(MAKE) -C $(CPP_MODELS) clean + $(MAKE) -C $(OBJC_MODELS) clean diff --git a/infer/models/build.pl b/infer/models/build.pl new file mode 100755 index 000000000..4f5d170d1 --- /dev/null +++ b/infer/models/build.pl @@ -0,0 +1,10 @@ +#!/usr/bin/perl + +# command to configure the build +$inferbuild_configure = "echo 'no configure required'"; + +# command to execute the build +$inferbuild_make = "make"; + +# command to clean up before/after the build +$inferbuild_clean = "make clean"; diff --git a/infer/models/c/Makefile b/infer/models/c/Makefile new file mode 100644 index 000000000..ff2e36841 --- /dev/null +++ b/infer/models/c/Makefile @@ -0,0 +1,24 @@ +SHELL := /bin/bash +CWD = $(shell pwd) +BINDIR = $(CWD)/../../bin +LIBDIR = $(CWD)/../../lib +LIB_SPECS = $(LIBDIR)/specs +C_MODELS_FILE = $(LIB_SPECS)/c_models + +INFER = ANALYZE_MODELS=1 $(BINDIR)/infer + +default: run_infer + +.PHONY: run_infer install clean + +run_infer: clean + $(INFER) -o $(CWD)/out --models_mode --no_failures_allowed -- make -C src -j + +install: run_infer + cp out/specs/*.specs $(LIB_SPECS) + touch $(C_MODELS_FILE) + rm -rf out + +clean: + if [ -a $(C_MODELS_FILE) ];then rm $(C_MODELS_FILE);fi + make -C src clean diff --git a/infer/models/c/src/Makefile b/infer/models/c/src/Makefile new file mode 100644 index 000000000..e49f25d07 --- /dev/null +++ b/infer/models/c/src/Makefile @@ -0,0 +1,23 @@ +SOURCES=$(wildcard *.c) +OBJECTS=$(SOURCES:.c=.o) +CC=gcc +FLAGS=-c -w + +default: test + +all: test + +.PHONY: test +test: $(OBJECTS) $(OBJECTSXX) + echo "test called\n" + +.PHONY: configure +configure: + echo "no configure required\n" + +clean: + rm -rf *.o + +.c.o: + $(CC) $(FLAGS) $< -o $@ + diff --git a/infer/models/c/src/glib.c b/infer/models/c/src/glib.c new file mode 100644 index 000000000..9b8a974af --- /dev/null +++ b/infer/models/c/src/glib.c @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2009-2013 Monoidics ltd. + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +// Basic modelling of some glib functions + +#include "infer_builtins.h" + +#include + +// similar to malloc, but never fails, and returns NULL when size==0 +void *g_malloc(size_t size) { + if(size==0) return NULL; + void *res = malloc(size); + INFER_EXCLUDE_CONDITION(!res); + return res; +} + +// modelled as free +void g_free(void *ptr) { + free(ptr); +} + +void *g_realloc(void *ptr, size_t size) { + if(size==0) { // return NULL and free ptr unless it is NULL + if(ptr) free(ptr); + return NULL; + } + int old_size; + old_size = __get_array_size(ptr); // force ptr to be an array + int can_enlarge; // nondeterministically choose whether the current block can be enlarged + if(can_enlarge) { + __set_array_size(ptr, size); // enlarge the block + return ptr; + } + int *newblock = malloc(size); + if(newblock) { + free(ptr); + return newblock; + } + else exit(0); // assume that new allocation does not fail +} + + +// simply return object, and assume it is not NULL +void *gtk_type_check_object_cast(void *object, void *cast_type) { + if(!object) exit(0); + return object; +} diff --git a/infer/models/c/src/infer_builtins.c b/infer/models/c/src/infer_builtins.c new file mode 100644 index 000000000..433714e57 --- /dev/null +++ b/infer/models/c/src/infer_builtins.c @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2009-2013 Monoidics ltd. + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +// builtins to be used to model library functions + +#include "infer_builtins.h" + +// model returning an arbitrary (nondeterministic) short +short __infer_nondet_short() { + short ret; + return ret; +} + +// model returning an arbitrary (nondeterministic) int +int __infer_nondet_int() { + int ret; + return ret; +} + +// model returning an arbitrary (nondeterministic) long int +long int __infer_nondet_long_int() { + long int ret; + return ret; +} + +// model returning an arbitrary (nondeterministic) long long int +long long int __infer_nondet_long_long_int() { + long long int ret; + return ret; +} + +// model returning an arbitrary (nondeterministic) unsigned long int +unsigned long int __infer_nondet_unsigned_long_int() { + unsigned long int ret; + return ret; +} + +// model returning an arbitrary (nondeterministic) pointer +void *__infer_nondet_ptr() { + void *res; + return res; +} + +// model returning an arbitrary (nondeterministic) float +float __infer_nondet_float() { + float ret; + return ret; +} + +// model returning an arbitrary (nondeterministic) double +double __infer_nondet_double() { + double ret; + return ret; +} + +// model returning an arbitrary (nondeterministic) long double +long double __infer_nondet_long_double() { + long double ret; + return ret; +} + +// model returning an arbitrary (nondeterministic) size_t +size_t __infer_nondet_size_t() { + size_t t; + return t; +} + +// model returning an arbitrary (nondeterministic) time_t +time_t __infer_nondet_time_t() { + time_t t; + return t; +} + +// model returning an arbitrary (nondeterministic) clock_t +clock_t __infer_nondet_clock_t() { + clock_t t; + return t; +} + +long infer__builtin_expect(long e, long x) { + if (e == x) { + return x; + } else { + return e; + } +} diff --git a/infer/models/c/src/infer_builtins.h b/infer/models/c/src/infer_builtins.h new file mode 100644 index 000000000..b381d3cda --- /dev/null +++ b/infer/models/c/src/infer_builtins.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2009-2013 Monoidics ltd. + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +// builtins to be used to model library functions + +#include +#include +#include + +// model returning an arbitrary (nondeterministic) short +short __infer_nondet_short(); + +// model returning an arbitrary (nondeterministic) int +int __infer_nondet_int(); + +// model returning an arbitrary (nondeterministic) long int +long int __infer_nondet_long_int(); + +// model returning an arbitrary (nondeterministic) long long int +long long int __infer_nondet_long_long_int(); + +// model returning an arbitrary (nondeterministic) unsigned long int +unsigned long int __infer_nondet_unsigned_long_int(); + +// model returning an arbitrary (nondeterministic) pointer +void *__infer_nondet_ptr(); + +// model returning an arbitrary (nondeterministic) float +float __infer_nondet_float(); + +// model returning an arbitrary (nondeterministic) double +double __infer_nondet_double(); + +// model returning an arbitrary (nondeterministic) long double +long double __infer_nondet_long_double(); + +// model returning an arbitrary (nondeterministic) size_t +size_t __infer_nondet_size_t(); + +// model returning an arbitrary (nondeterministic) time_t +time_t __infer_nondet_time_t(); + +// model returning an arbitrary (nondeterministic) clock_t +clock_t __infer_nondet_clock_t(); + +// assume that the cond is false +// and add any constraints to the precondition so that cond is false, if possible +#define INFER_EXCLUDE_CONDITION(cond) if (cond) while(1) + +// builtin: force arr to be an array and return the size +extern size_t __get_array_size(const void *arr); + +// builtin: change the attribute of ret to a file attribute +extern void __set_file_attribute(void *ret); + +// builtin: change the size of the array to size +extern void __set_array_size(void *ptr, size_t size); + +// builtin: set the flag to the given value for the procedure where this call appears +extern void __infer_set_flag(char *flag, char *value); diff --git a/infer/models/c/src/libc_basic.c b/infer/models/c/src/libc_basic.c new file mode 100644 index 000000000..75ef30d46 --- /dev/null +++ b/infer/models/c/src/libc_basic.c @@ -0,0 +1,1838 @@ +/* + * Copyright (c) 2009-2013 Monoidics ltd. + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +// Basic modelling of some libc functions + +// prevent _FORTIFY_SOURCE from changing some function prototypes +// https://securityblog.redhat.com/2014/03/26/fortify-and-you/ +#ifdef _FORTIFY_SOURCE +#undef _FORTIFY_SOURCE +#endif +#define _FORTIFY_SOURCE 0 + +#ifdef __APPLE__ // disable block instructions on mac +#ifdef __BLOCKS__ +#undef __BLOCKS__ +#endif +#ifdef _POSIX_C_SOURCE +#undef _POSIX_C_SOURCE +#endif +#endif + +#include "infer_builtins.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __APPLE__ // includes for statfs are system-dependent +#include +#include +#else +#include +#endif + +#ifndef __APPLE__ +/* struct __dirstream is abstract on Linux, at least on Debian 8.0 + we need it to be concrete to model closedir (5) */ +/* dummy __dirstream structure */ +struct __dirstream { + int fd; +}; +#endif + +#ifdef __CYGWIN__ // define __WAIT_STATUS as int * on cygwin +#define __WAIT_STATUS int * +#endif +#ifdef __APPLE__ // define __WAIT_STATUS as int * on mac +#define __WAIT_STATUS int * +#endif + +//long __builtin_expect(long, long); +void abort(void); // builtin: modeled internally +int access(const char *path, int mode); +char *asctime(const struct tm *timeptr); +double atof(const char *str); +int atoi(const char *str); +void *bsearch(const void *key, const void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)); +void *calloc(size_t nmemb, size_t size); +#ifdef clearerr // cygwin defines clearerr as a macro +#undef clearerr +#endif +void clearerr(FILE *stream); +clock_t clock(); +int close(int fildes); +int closedir(DIR *dirp); +size_t confstr(int name, char *buf, size_t len); +int creat(const char *path, mode_t mode); +char *ctime(const time_t *clock); +double difftime(time_t time1, time_t time0); +int dup(int fildes); +void endpwent(void); +void exit(int status); // builtin: modeled internally +int fclose (FILE *stream); +int fcntl(int fildes, int cmd, ...); +FILE *fdopen(int fildes, const char *mode); +#ifdef feof // cygwin defines feof as a macro +#undef feof +#endif +int feof(FILE *stream); +#ifdef ferror // cygwin defines ferror as a macro +#undef ferror +#endif +int ferror(FILE *stream); +int fflush(FILE *stream); +int fgetc(FILE *stream); +int fgetpos(FILE *__restrict stream, fpos_t *__restrict pos); +char *fgets( char *str, int num, FILE *stream ); +int fileno(FILE *stream); +int flock(int fd, int operation); +FILE *fopen (const char *filename, const char *mode); +pid_t fork(void); +int fprintf(FILE *stream, const char *format, ...); +int fputc(int c, FILE *stream); +int fputs(const char *str, FILE *stream); +size_t fread(void *__restrict ptr, size_t size, size_t nmemb, FILE *__restrict stream); +void free(void *ptr); // builtin: modeled internally +FILE *freopen(const char *__restrict filename, const char *__restrict mode, FILE *__restrict stream); +int fscanf(FILE *stream, const char *format, ...); // builtin: modeled internally +int fsctl(const char *path, unsigned long request, void *data, unsigned int options); +int fseek(FILE *stream, long int offset, int whence); +int fsetpos(FILE *stream, const fpos_t *pos); +int fstat(int fildes, struct stat *buf); +int fsync(int fildes); +long int ftell(FILE *stream); +int futimes(int fildes, const struct timeval times[2]); +size_t fwrite(const void *__restrict ptr, size_t size, size_t nmemb, FILE *__restrict stream); +int getc(FILE *stream); +#ifdef getchar // cygwin defines getchar as a macro +#undef getchar +#endif +int getchar(void); +char *getcwd (char *buffer, size_t size); +char *getenv(const char *name); +char *getlogin(); +char *getpass(const char *prompt); +pid_t getpid(void); +struct passwd *getpwent(void); +struct passwd *getpwnam(const char *login); +struct passwd *getpwuid(uid_t uid); +int getrusage(int who, struct rusage *r_usage); +char *gets(char *s); +#ifdef __APPLE__ +#define gettimeofday_tzp_decl void *__restrict tzp +#else +#ifdef __CYGWIN__ +#define gettimeofday_tzp_decl void *__restrict tzp +#else +#define gettimeofday_tzp_decl struct timezone *__restrict tzp +#endif +#endif +int gettimeofday(struct timeval *__restrict tp, gettimeofday_tzp_decl); +uid_t getuid(void); +struct tm *gmtime(const time_t *timer); +int isalnum(int x); +int isalpha(int x); +int isascii(int x); +int isatty(int fildes); +int isblank(int x); +int iscntrl(int x); +int isdigit(int x); +int isgraph(int x); +int islower(int x); +int isprint(int x); +int ispunct(int x); +int isspace(int x); +int isupper(int x); +int isxdigit(int x); +struct lconv *localeconv(); +struct tm *localtime(const time_t *clock); +struct tm *localtime_r(const time_t *__restrict timer, struct tm *__restrict result); +void longjmp(jmp_buf env, int val); +off_t lseek(int fildes, off_t offset, int whence); +void *malloc(size_t size); // builtin: modeled internally +int mblen(const char *s, size_t n); +size_t mbstowcs(wchar_t * __restrict pwcs, const char * __restrict s, size_t n); +int mbtowc(wchar_t * __restrict pwc, const char * __restrict s, size_t n); +void *memchr(const void *s, int c, size_t n); +int memcmp(const void *s1, const void *s2, size_t n); +void *memcpy(void *s1, const void *s2, size_t n); +void *memmove(void *s1, const void *s2, size_t n); +void *memset(void *s, int c, size_t n); +int mkdir (const char *filename, mode_t mode); +time_t mktime(struct tm *timeptr); +int munmap(void *addr, size_t len); +int open(const char *path, int oflag, ...); +DIR *opendir(const char *dirname); +int pause(void); +void perror(const char *s); +int pipe(int fildes[2]); +int printf(const char *format, ...); +int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg); // builtin: modeled internally +void pthread_exit(void *value_ptr); +int pthread_mutexattr_destroy(pthread_mutexattr_t *attr); +int pthread_mutexattr_init(pthread_mutexattr_t *attr); +int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type); +int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *type); +int pthread_mutex_destroy(pthread_mutex_t *mutex); +int pthread_mutex_init(pthread_mutex_t *__restrict mutex, const pthread_mutexattr_t *__restrict attr); +int pthread_mutex_lock(pthread_mutex_t *mutex); +int pthread_mutex_trylock(pthread_mutex_t *mutex); +int pthread_mutex_unlock(pthread_mutex_t *mutex); +int putc(int c, FILE *stream); +int puts(const char *str); +void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)); +int raise(int sig); +long random(void); +struct dirent *readdir(DIR *dirp); +ssize_t read(int fildes, void *buf, size_t nbyte); +char *readline(const char *prompt); +void *realloc(void *ptr, size_t size); +int remove(const char *path); +int rename(const char *old, const char *new); +void rewind(FILE *stream); +int scanf(const char *format, ...); // builtin: modeled internally +void setbuf(FILE * __restrict stream, char * __restrict buf); +int setitimer(int which, const struct itimerval *__restrict value, struct itimerval *__restrict ovalue); +int setjmp(jmp_buf env); +char *setlocale(int category, const char *locale); +int setlogin(const char *name); +int setpassent(int stayopen); +void setpwent(void); +int setvbuf(FILE * __restrict stream, char * __restrict buf, int mode, size_t size); +void *shmat(int shmid, const void *shmaddr, int shmflg); +int shmctl(int shmid, int cmd, struct shmid_ds *buf); +int shmdt(const void *shmaddr); +int shmget(key_t key, size_t size, int shmflg); +int sigaction(int sig, const struct sigaction *act, struct sigaction *oact); +void (*signal(int sig, void (*func)(int)))(int); +int sigprocmask(int how, const sigset_t *set, sigset_t *oset); +unsigned sleep(unsigned seconds); +int snprintf(char * __restrict s, size_t n, const char * __restrict format, ...); +int socket (int namespace, int style, int protocol); +int sprintf(char *s, const char *format, ...); +int sscanf(const char *s, const char *format, ...); // builtin: modeled internally +int stat(const char *path, struct stat *buf); +int statfs(const char *path, struct statfs *buf); +char *strcat(char *s1, const char *s2); +char *strchr(const char *s, int c); +int strcmp(const char *s1, const char *s2); +int strcoll(const char *s1, const char *s2); +char *strcpy(char *s1, const char *s2); +size_t strcspn(const char *s1, const char *s2); +char *strdup(const char *s); +size_t strftime(char *__restrict s, size_t maxsize, const char *__restrict format, const struct tm *__restrict timeptr); +int strerror_r(int errnum, char *strerrbuf, size_t buflen); +size_t strlcat(char *dst, const char *src, size_t size); +size_t strlcpy(char *dst, const char *src, size_t size); +size_t strlen(const char *s); +char *strlwr(char *s); +char *strncat(char *s1, const char *s2, size_t n); +int strncmp(const char *s1, const char *s2, size_t n); +char *strncpy(char *s1, const char *s2, size_t n); +char *strpbrk(const char *s1, const char *s2); +char *strrchr(const char *s, int c); +size_t strspn(const char *s1, const char *s2); +char *strstr(const char *s1, const char *s2); +double strtod(const char *str, char **endptr); +long strtol(const char *str, char **endptr, int base); +unsigned long strtoul(const char *str, char **endptr, int base); +char *strupr(char *s); +time_t time(time_t *tloc); +FILE *tmpfile(void); +char *tmpnam(char *s); +int toascii(int x); +int tolower(int x); +int toupper(int x); +int ungetc(int c, FILE *stream); +int unlink (const char *filename); +int usleep(useconds_t useconds); +int utimes(const char *path, const struct timeval times[2]); +int vfprintf(FILE *stream, const char *format, va_list arg); +int vfscanf(FILE *stream, const char *format, va_list arg); // builtin: modeled internally +int vprintf(const char *format, va_list arg); +int vscanf(const char *format, va_list arg); // builtin: modeled internally +int vsnprintf(char *s, size_t n, const char *format, va_list arg); +int vsprintf(char *s, const char *format, va_list arg); +int vsscanf(const char *s, const char *format, va_list arg); // builtin: modeled internally +pid_t wait(__WAIT_STATUS stat_loc); +size_t wcstombs(char * __restrict s, const wchar_t * __restrict pwcs, size_t n); +int wctomb(char *s, wchar_t wc); +ssize_t write(int fildes, const void *buf, size_t nbyte); +void *xcalloc(size_t nmemb, size_t size); +void *xmalloc(size_t size); + +// modelling of errno +// errno expands to different function calls on mac or other systems +// the function call returns the address of a global variable called "errno" +extern int errno; +#ifdef __APPLE__ +#define __ERRNO_FUN_NAME __error +#else +#define __ERRNO_FUN_NAME __errno_location +#endif +int *__ERRNO_FUN_NAME() { + return &errno; +} + +// the strings s1 and s2 need to be allocated +// check that s2 fits inside s1 +char *strcpy(char *s1, const char *s2) { + int size1; + int size2; + __infer_set_flag("ignore_return", ""); // no warnings if the return value is ignored + + size1 = __get_array_size(s1); + size2 = __get_array_size(s2); + INFER_EXCLUDE_CONDITION(size2>size1); + return s1; +} + +// the string s must be allocated; return the result of malloc with the same size +char *strdup(const char *s) { + int size; + size = __get_array_size(s); + return malloc(size); +} + +// the strings s1 and s2 need to be allocated +// check that s2 fits inside s1 +char *strcat(char *s1, const char *s2) { + int size1; + int size2; + + size1 = __get_array_size(s1); + size2 = __get_array_size(s2); + INFER_EXCLUDE_CONDITION(size2>size1); + return s1; +} + +// the string s must be allocated +// nondeterministically return 0 or a pointer inside the buffer +char *strchr(const char *s, int c) { + int size; + int nondet; + int offset; + nondet = __infer_nondet_int(); + offset = __infer_nondet_int(); + + size = __get_array_size(s); + if(nondet) return 0; + INFER_EXCLUDE_CONDITION(offset < 0 || offset >=size); + return (void *)s + offset; +} + +// s1 and s2 must be allocated. +// return a non-deterministic integer +int strcmp(const char *s1, const char *s2) { + int size_s1; + int size_s2; + int res; + res = __infer_nondet_int(); + + size_s1 = __get_array_size(s1); + size_s2 = __get_array_size(s2); + return res; +} + +// the string s must be allocated +// return the size of the buffer - 1 +size_t strlen(const char *s) { + int size; + size = __get_array_size(s); + return size-1; +} + +// s must be allocated +// return s +char *strlwr(char *s) { + int size1; + size1 = __get_array_size(s); + return s; +} + +// s1 and s2 must be allocated +// n should not be greater than the size of s1 or s2 +// return s1 +char *strncat(char *s1, const char *s2, size_t n) { + int size_s1; + int size_s2; + + size_s1 = __get_array_size(s1); + size_s2 = __get_array_size(s2); + INFER_EXCLUDE_CONDITION((n>size_s1) || (n>size_s2)); + return s1; +} + +// s1 and s2 must be allocated +// n should not be greater than the size of s1 or s2 +// return a non-deterministic integer +int strncmp(const char *s1, const char *s2, size_t n) { + int size_s1; + int size_s2; + int res; + res = __infer_nondet_int(); + + size_s1 = __get_array_size(s1); + size_s2 = __get_array_size(s2); + INFER_EXCLUDE_CONDITION((n>size_s1) || (n>size_s2)); + return res; +} + +// the strings s1 and s2 need to be allocated +// check that n characters fit in s1 (because even if s2 is shorter than n, null characters are appended to s1) +char *strncpy(char *s1, const char *s2, size_t n) { + int size1, size2; + __infer_set_flag("ignore_return", ""); // no warnings if the return value is ignored + + size1 = __get_array_size(s1); + size2 = __get_array_size(s2); + INFER_EXCLUDE_CONDITION(n > size1); + return s1; +} + +// the strings s1 and s2 must be allocated +// nondeterministically return 0 or a pointer inside the string s1 +char *strpbrk(const char *s1, const char *s2) { + int size1, size2; + int nondet; + int offset; + nondet = __infer_nondet_int(); + offset = __infer_nondet_int(); + + size1 = __get_array_size(s1); + size2 = __get_array_size(s2); + if(nondet) return 0; + INFER_EXCLUDE_CONDITION(offset < 0 || offset >=size1); + return (void *)s1 + offset; +} + +// modelled like strchr +char *strrchr(const char *s, int c) { + return strchr(s,c); +} + +// s1 and s2 must be allocated. +// return an integer between 0 ans the size of s1 +size_t strspn(const char *s1, const char *s2) { + int size_s1; + int size_s2; + int res; + res = __infer_nondet_int(); + + size_s1 = __get_array_size(s1); + size_s2 = __get_array_size(s2); + INFER_EXCLUDE_CONDITION(res < 0 || res > size_s1); + return res; +} + +// the strings s1 and s2 must be allocated +// nondeterministically return 0 or a pointer inside the string s1 +char *strstr(const char *s1, const char *s2) { + int size1, size2; + int nondet; + int offset; + nondet = __infer_nondet_int(); + offset = __infer_nondet_int(); + + size1 = __get_array_size(s1); + size2 = __get_array_size(s2); + if(nondet) return 0; + INFER_EXCLUDE_CONDITION(offset < 0 || offset >=size1); + return (void *)s1 + offset; +} + +// modeled using strtoul +double strtod(const char *str, char **endptr) { + return (double) strtoul(str, endptr, 0); +} + +// modeled like strtoul +long strtol(const char *str, char **endptr, int base) { + return (long) strtoul(str, endptr, base); +} + +// the string s must be allocated +// assign to endptr a pointer somewhere inside the string str +// result is nondeterministic +unsigned long strtoul(const char *str, char **endptr, int base) { + int size; + int offset; + int res; + size = __get_array_size(str); + offset = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(offset < 0 || offset >= size); + if(endptr) *endptr = (char *) (str + offset); + res = __infer_nondet_int(); + int errno_val = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(errno_val < 0); + errno = errno_val; + return res; +} + +// s must be allocated +// return s +char *strupr(char *s) { + int size1; + size1 = __get_array_size(s); + return s; +} + +// the array s must be allocated +// n should not be greater than the size of s +// nondeterministically return 0 or a pointer within the first n elements of s +void *memchr(const void *s, int c, size_t n) { + int size; + int nondet; + int offset; + nondet = __infer_nondet_int(); + offset = __infer_nondet_int(); + + size = __get_array_size(s); + INFER_EXCLUDE_CONDITION(n > size); + if(nondet) return 0; + INFER_EXCLUDE_CONDITION(offset < 0 || offset >= n); + return (void *)s + offset; +} + +// s1 and s2 must be allocated +// n should not be greater than the size of s1 or s2 +// return a non-deterministic integer +int memcmp(const void *s1, const void *s2, size_t n) { + int size_s1; + int size_s2; + + size_s1 = __get_array_size(s1); + size_s2 = __get_array_size(s2); + INFER_EXCLUDE_CONDITION((n>size_s1) || (n>size_s2)); + return __infer_nondet_int(); +} + +// s1 and s2 must be allocated +// n must be between 0 and the minumum of the sizes of s1 and s2 +void *memcpy(void *s1, const void *s2, size_t n) { + int size_s1; + int size_s2; + __infer_set_flag("ignore_return", ""); // no warnings if the return value is ignored + + size_s1 = __get_array_size(s1); + size_s2 = __get_array_size(s2); + INFER_EXCLUDE_CONDITION((n < 0) || (n>size_s1) || (n>size_s2)); + return s1; +} + +// modeld using memcpy +void *memmove(void *s1, const void *s2, size_t n) { + __infer_set_flag("ignore_return", ""); // no warnings if the return value is ignored + return memcpy(s1, s2, n); +} + +// s needs to be allocated +// n should not be greater than the size of s +void *memset(void *s, int c, size_t n) { + int size_s; + __infer_set_flag("ignore_return", ""); // no warnings if the return value is ignored + + size_s = __get_array_size(s); + INFER_EXCLUDE_CONDITION(n>size_s); + return s; +} + +// return a nondeterministic double +double atof(const char *str) { + return __infer_nondet_double(); +} + +// return a nondeterministic value between INT_MIN and INT_MAX +int atoi(const char *str) { + int retu = INT_MAX; + int retl = INT_MIN; + int ret; + if (__infer_nondet_int()) ret = retu; + else ret = retl; + return ret; +} + +// modeled using malloc and set file attribute +FILE *fopen (const char *filename, const char *mode) { + FILE *ret; + ret = malloc(sizeof(FILE)); + if(ret) __set_file_attribute(ret); + return ret; +} + +// modeled using fopen +FILE *tmpfile(void) { + return fopen("foo", ""); +} + +// use a global variable to model the return value of tmpnam +extern char *_tmpnam_global; +// return NULL, or if s is NULL return a global variable, otherwise check that s has size at least L_tmpnam and return s +char *tmpnam(char *s) { + int success; + int size; + + success = __infer_nondet_int(); + if(!success) return NULL; + if(s) { + size = __get_array_size(s); + INFER_EXCLUDE_CONDITION(size < L_tmpnam); + return s; + } + else return _tmpnam_global; +} + +// nondeterministically return NULL or the original stream +FILE *freopen(const char *__restrict filename, const char *__restrict mode, FILE *__restrict stream) { + int n; + n = __infer_nondet_int(); + if(n) return NULL; + else return stream; +} + +// modeled using free, can return EOF +int fclose (FILE *stream) { + int n; + free(stream); + n = __infer_nondet_int(); + if (n>0) return 0; + else return EOF; +} + +// modeled using malloc and set file attribute +// return the allocated ptr - 1 if malloc succeeds, otherwise return -1 +int open(const char *path, int oflag, ...) { + int *ret = malloc(sizeof(int)); + if(ret) { + __set_file_attribute(ret); + INFER_EXCLUDE_CONDITION(ret < (int *)1); // force result to be > 0 + return (long) ret; + } + return -1; +} + +// modeled using free, can return -1 +int close(int fildes) { + int n; + if (fildes != -1) + free((int *) (long) fildes); + n = __infer_nondet_int(); + if (n>0) return 0; + else return -1; +} + +// modeled as close followed by fopen +FILE *fdopen(int fildes, const char *mode) { + close(fildes); + return fopen("foo", mode); +} + +// return nonteterministically 0 or -1 +int fseek(FILE *stream, long int offset, int whence) { + int n; + n = __infer_nondet_int(); + if (n) return 0; + else return -1; +} + +// return nondeterministically a nonnegative value or -1 +long int ftell(FILE *stream) { + int n; + n = __infer_nondet_int(); + if (n>=0) return n; + else return -1; +} + +// on success return str otherwise null +char *fgets( char *str, int num, FILE *stream ) { + int n; + int size1; + n = __infer_nondet_int(); + + if (n>0) { + size1 = __get_array_size(str); + INFER_EXCLUDE_CONDITION(num>size1); + return str; + } + else return NULL; +} + +// string s must be allocated; return nondeterministically s or NULL +char *gets(char *s) { + int n; + int size; + + size = __get_array_size(s); + n = __infer_nondet_int(); + if(n) return s; + else return NULL; +} + +// str must be allocated, return a nondeterministic value +int puts(const char *str) { + int size1; + size1 = __get_array_size(str); + return __infer_nondet_int(); +} + +// modeled using puts +int fputs(const char *str, FILE *stream) { + return puts(str); +} + +// return a nondeterministic value +int getc(FILE *stream) +{ + return __infer_nondet_int(); +} + +// return a nondeterministic value +int fgetc(FILE *stream) +{ + return __infer_nondet_int(); +} + +// return nondeterministically c or EOF +int ungetc(int c, FILE *stream) { + int n; + + n = __infer_nondet_int(); + if (n) return c; + else return EOF; +} + +// modeled like putc +int fputc(int c, FILE *stream) +{ + return putc(c,stream); +} + +// on success return buffer otherwise null +char *getcwd (char *buffer, size_t size) { + int n; + int size_buf; + n = __infer_nondet_int(); + + if (n>0) { + size_buf= __get_array_size(buffer); + INFER_EXCLUDE_CONDITION(size>size_buf); + return buffer; + } + else return NULL; +} + +// return nonteterministically 0 or -1 +int rename(const char *old, const char *new) { + int n; + + n = __infer_nondet_int(); + if (n) return 0; + else return -1; +} + +// modeled as skip +void rewind(FILE *stream) { +} + +// modeled as just return a nondeterministic value +int setjmp(jmp_buf env) { + return __infer_nondet_int(); +} + +// modeled as exit() +void longjmp(jmp_buf env, int val) { + exit(0); +} + +// modeled as skip. +//If sleep() returns because the requested time has elapsed, +//the value returned shall be 0. If sleep() returns due to delivery of a signal, +//the return value shall be the "unslept" amount (the requested time minus the time actually slept) in seconds. +unsigned sleep(unsigned seconds) { + int n; + n = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(n<0); + return n; +} + +// the waiting is modeled as skip. Then the return value is a random value of a process or +// -1 in case of an error +pid_t wait(__WAIT_STATUS stat_loc) { + int n; + pid_t tmp; + n = __infer_nondet_int(); + tmp = __infer_nondet_int(); + if (n>0) return tmp; + else return -1; +} + +// return a nondeterministic pointer +void (*signal(int sig, void (*func)(int)))(int) { + void (*res) (int) = __infer_nondet_ptr(); + return res; +}; + +// modelled as exit +void pthread_exit(void *value_ptr) { exit(0); }; + +//INTENDED SEMANTICS: The setitimer() function shall set the timer specified by which +//to the value specified in the structure pointed to by value, and if ovalue is not a null pointer, +//store the previous value of the timer in the structure pointed to by ovalue. +int setitimer(int which, const struct itimerval *__restrict value, struct itimerval *__restrict ovalue) { + int n; + int tmp; + n = __infer_nondet_int(); + tmp = __infer_nondet_int(); + // not sure about this assignment. + //it should somehow change the value of the timer which. + //But which is just an int, so it looks like this is useless + which=tmp; + if (n>0) return 0; + else return -1; +} + +// pause returns only when a signal is received +// the return value is always -1 +int pause(void) { + return -1; +} + +// modeled with nondeterministic value n +pid_t fork(void) { + return __infer_nondet_int(); +} + +// allocate memory directly, and return -1 if malloc fails. +int shmget(key_t key, size_t size, int shmflg) { + void *res = malloc(size); + if(!res) return -1; + return (long) res; +} + +// simply return the first parameter +void *shmat(int shmid, const void *shmaddr, int shmflg) { + return (void *) (long) shmid; +} + +// return a non-deterministic value +int shmdt(const void *shmaddr) { + return __infer_nondet_int(); +} + +// if the command is IPC_RMID free the first parameter; return a non-deterministic value +int shmctl(int shmid, int cmd, struct shmid_ds *buf) { + int n; + void *shmaddr; + n = __infer_nondet_int(); + shmaddr = (void *) (long) shmid; + if(cmd == IPC_RMID) free(shmaddr); + return n; +} + +void *realloc(void *ptr, size_t size) { + if(ptr==0) { // if ptr in NULL, behave as malloc + return malloc(size); + } + int old_size; + int can_enlarge; + old_size = __get_array_size(ptr); // force ptr to be an array + can_enlarge = __infer_nondet_int(); // nondeterministically choose whether the current block can be enlarged + if(can_enlarge) { + __set_array_size(ptr, size); // enlarge the block + return ptr; + } + int *newblock = malloc(size); + if(newblock) { + free(ptr); + return newblock; + } + else { // if new allocation fails, do not free the old block + return newblock; + } +} + +// modelled as a call to malloc +void *calloc(size_t nmemb, size_t size) { + return malloc(nmemb *size); +} + +// character functions from ctype.h +// all modelled as returning a nondeterministic value +int isalnum(int x) { return __infer_nondet_int(); } +int isalpha(int x) { return __infer_nondet_int(); } +int isblank(int x) { return __infer_nondet_int(); } +int iscntrl(int x) { return __infer_nondet_int(); } +int isdigit(int x) { return __infer_nondet_int(); } +int isgraph(int x) { return __infer_nondet_int(); } +int islower(int x) { return __infer_nondet_int(); } +int isprint(int x) { return __infer_nondet_int(); } +int ispunct(int x) { return __infer_nondet_int(); } +int isspace(int x) { return __infer_nondet_int(); } +int isupper(int x) { return __infer_nondet_int(); } +int isxdigit(int x) { return __infer_nondet_int(); } +int tolower(int x) { return __infer_nondet_int(); } +int toupper(int x) { return __infer_nondet_int(); } +int isascii(int x) { return __infer_nondet_int(); } +int toascii(int x) { return __infer_nondet_int(); } + +//modeled as skip. n>0 models success. +//Upon successful completion a non-negative integer, namely the file descriptor, shall be returned; +// otherwise, -1 shall be returned. +int dup(int fildes) { + int n; + n = __infer_nondet_int(); + if (n>0) return fildes; + else return -1; + +}; + +// modeled as skip. +//The getuid() function shall always be successful and no return value is reserved to indicate the error. +uid_t getuid(void) { + return __infer_nondet_int(); +}; + + +//modeled as skip +//The getpid() function shall always be successful and no return value is reserved to indicate an error. +pid_t getpid(void) { + return __infer_nondet_int(); +}; + +// modeled as skip. +// success is modeled by n>0, if lseek fails (off_t)-1 should be returned. +off_t lseek(int fildes, off_t offset, int whence) { + int n; + off_t tmp; + n = __infer_nondet_int(); + tmp = __infer_nondet_int(); + + if (n>0) return tmp; + else return (off_t)-1; +}; + +// modeled using malloc and set file attribute +DIR *opendir(const char *dirname) { + DIR *ret; + ret = malloc(sizeof(void *)); + if(ret) __set_file_attribute(ret); + return ret; +} + +// modeled using free, can return EOF +int closedir(DIR *dirp) { + DIR tmp = *dirp; + int n; + n = __infer_nondet_int(); + free(dirp); + if (n>0) return 0; + else return EOF; +} + +// use a global variable to model the return value of readdir +extern struct dirent *_dirent_global; +// dirp must be allocated +// return 0 or the allocated pointerq _dirent_global, nondeterministically +struct dirent *readdir(DIR *dirp) { + int len; + int nondet; + struct dirent *ret = _dirent_global; + len = __get_array_size(dirp); + nondet = __infer_nondet_int(); + if(nondet) return 0; + return ret; +} + +// use a global variable to model the return value of getenv +extern char *_getenv_global; +// string name must be allocated +// return 0 or the allocated string _getenv_global, nondeterministically +char *getenv(const char *name) { + int size; + int nondet; + size =__get_array_size(name); + size =__get_array_size(_getenv_global); + nondet = __infer_nondet_int(); + if(nondet) return 0; + return _getenv_global; +} + +// use a global variable to model the return value of setlocale +extern char *_locale_global; +// string locale must be allocated +// return 0 or the allocated string _locale_global, nondeterministically +char *setlocale(int category, const char *locale) { + int size; + int nondet; + size =__get_array_size(locale); + size =__get_array_size(_locale_global); + nondet = __infer_nondet_int(); + if(nondet) return 0; + return _locale_global; +} + +// use a global variable to model the return value of localeconv +extern struct lconv *_lconv_global; +// return the allocated pointer _lconv_global +struct lconv *localeconv() { + int nondet; + struct lconv tmp; + tmp = *_lconv_global; + return _lconv_global; +} + +// use a global variable to model the return value of localtime +extern struct tm *_tm_global; +// return nondeterministically NULL or the global variable _tm_global +struct tm *localtime(const time_t *clock) { + int fail; + + fail = __infer_nondet_int(); + if (fail) return NULL; + else return _tm_global; +} + +// return nondeterministically NULL or the global variable _tm_global +struct tm *gmtime(const time_t *timer) +{ + int fail; + + fail = __infer_nondet_int(); + if (fail) return NULL; + else return _tm_global; +} + +// use a global variable to model the return value of asctime +extern char *_asctime_global; +// return the global variable _asctime_global +char *asctime(const struct tm *timeptr) +{ + return _asctime_global; +} + +// modelled using asctime and localtime +char *ctime(const time_t *clock) { + return asctime(localtime(clock)); +} + +// return a nondeterministic double +double difftime(time_t time1, time_t time0) +{ + return __infer_nondet_double(); +} + +// return a nondeterministic nonnegative value or -1 +time_t mktime(struct tm *timeptr) +{ + time_t res; + res = __infer_nondet_time_t(); + INFER_EXCLUDE_CONDITION(res < -1); + return res; +} + +// return a nondeterministic nonnegative value or -1 +clock_t clock() +{ + clock_t res; + res = __infer_nondet_clock_t(); + INFER_EXCLUDE_CONDITION(res < -1); + return res; +} + +// return a nondeterministic nonnegative value +size_t strftime(char *__restrict s, size_t maxsize, const char *__restrict format, const struct tm *__restrict timeptr) +{ + size_t res; + res = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(res < 0); + return res; +} + +// return a nondeterministic value of type time_t +time_t time(time_t *tloc) { + time_t t; + t = __infer_nondet_time_t(); + if(tloc) *tloc = t; + return t; +} + +// use a global variable to model the return value of getpwuid +extern struct passwd *_getpwuid_global; +// return either NULL or the global variable _getpwuid_global +struct passwd *getpwuid(uid_t uid) { + int found; + found = __infer_nondet_int(); + if (found) return _getpwuid_global; + else return NULL; +} + +// use a global variable to model the return value of getpwent +extern struct passwd *_getpwent_global; +// return either NULL or the global variable _getpwent_global +struct passwd *getpwent(void) { + int found; + found = __infer_nondet_int(); + if (found) return _getpwent_global; + else return NULL; +} + +// use a global variable to model the return value of getpwnam +extern struct passwd *_getpwnam_global; +// login must be allocated +// return either NULL or the global variable _getpnam_global +struct passwd *getpwnam(const char *login) { + int found; + int size1; + size1 = __get_array_size(login); + found = __infer_nondet_int(); + if (found) return _getpwnam_global; + else return NULL; +} + +// nondeterministically return 0 (failure) or 1 (success) +int setpassent(int stayopen) { + int success; + success = __infer_nondet_int(); + if(success) return 1; + else return 0; +} + +// modled as skip +void setpwent(void) { +} + +// modled as skip +void endpwent(void) { +} + +// use a global variable to model the return value of getlogin +extern char *_getlogin_global; +// string name must be allocated +// return 0 or the allocated string _getlogin_global, nondeterministically +char *getlogin() { + int size; + int nondet; + size = __get_array_size(_getlogin_global); + nondet = __infer_nondet_int(); + if(nondet) return 0; + return _getlogin_global; +} + +int setlogin(const char *name) { + int success; + int size1; + size1 = __get_array_size(name); + success = __infer_nondet_int(); + if (success) { + strcpy(_getlogin_global, name); + return 0; + } + else return -1; +} + +// use a global variable to model the return value of getpass +extern char *_getpass_global; +// prompt must be allocated +// return the global variable _getpass_global, which must be allocated +char *getpass(const char *prompt) { + int size1; + size1 = __get_array_size(prompt); + size1 = __get_array_size(_getpass_global); + return _getpass_global; +} + +// return a nondeterministic nonnegative integer +int printf(const char *format, ...) +{ + int res; + res = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(res < 0); + return res; +} + +// return a nondeterministic nonnegative integer +int fprintf(FILE *stream, const char *format, ...) +{ + int res; + res = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(res < 0); + return res; +} + +// return a nondeterministic nonnegative integer +int snprintf(char * __restrict s, size_t n, const char * __restrict format, ...) +{ + int res; + res = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(res < 0); + return res; +} + +// s must be allocated +// return a nondeterministic nonnegative integer +int sprintf(char *s, const char *format, ...) +{ + int res; + int size1; + size1 = __get_array_size(s); + res = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(res < 0); + return res; +} + +// return a nondeterministic nonnegative integer +int vfprintf(FILE *stream, const char *format, va_list arg) +{ + int res; + res = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(res < 0); + return res; +} + +// return a nondeterministic nonnegative integer +int vsprintf(char *s, const char *format, va_list arg) +{ + int res; + res = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(res < 0); + return res; +} + +// return a nondeterministic nonnegative integer +int vsnprintf(char *s, size_t n, const char *format, va_list arg) +{ + int res; + res = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(res < 0); + return res; +} + +// return a nondeterministic nonnegative integer +int vprintf(const char *format, va_list arg) +{ + int res; + res = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(res < 0); + return res; +} + +// forces path to be allocated +//return nondeterministically 0 or -1 +int utimes(const char *path, const struct timeval times[2]) { + int size1; + int success = __infer_nondet_int(); + + size1= __get_array_size(path); + //return random result + return (success > 0) ? 0 : -1; +} + + +// forces filename to be allocated +//return nondeterministically 0 or -1 +int unlink (const char *filename) { + int size1; + int success = __infer_nondet_int(); + size1=__get_array_size(filename); + return (success > 0) ? 0 : -1; +} + + +//return nondeterministically 0 or -1 +int usleep(useconds_t useconds) { + int success = __infer_nondet_int(); + return (success > 0) ? 0 : -1; +} + +// dst and src must be allocated +// return an integer between 0 and size +size_t strlcpy(char *dst, const char *src, size_t size) { + int size_src; + int size_dst; + int res; + + res = __infer_nondet_int(); + + // force src to be allocated + size_src = __get_array_size(src); + + // force dst to be allocated for at least size + size_dst = __get_array_size(dst); + INFER_EXCLUDE_CONDITION(size>size_dst); + + INFER_EXCLUDE_CONDITION(res > size || res < 0); + + return res; +} + +// dst and src must be allocated +// return an integer between 0 and size +size_t strlcat(char *dst, const char *src, size_t size) { + int size_src; + int size_dst; + int res; + + res = __infer_nondet_int(); + + //force src to be allocated + size_src = __get_array_size(src); + + //force dst to be allocated for at least size + size_dst = __get_array_size(dst); + INFER_EXCLUDE_CONDITION(size>size_dst); + + INFER_EXCLUDE_CONDITION(res > size || res < 0); + + return res; +} + +// path must be allocated +// assign nonteterministically to the contents of buf +// return 0 or -1 +int statfs(const char *path, struct statfs *buf) { + int success; + int size_path; + + success = __infer_nondet_int(); + + // force path to be allocated + size_path = __get_array_size(path); + + struct statfs s; // uninitialized struct statfs + *buf = s; + + return (success > 0) ? 0 : -1; +} + +// path must be allocated +// assign nonteterministically to the contents of buf +// return 0 or -1 +int stat(const char *path, struct stat *buf) { + int success; + int size_path; + + success = __infer_nondet_int(); + + // force path to be allocated + size_path = __get_array_size(path); + + struct stat s; // uninitialized struct stat + *buf = s; + + return (success > 0) ? 0 : -1; +} + +int remove(const char *path) { + int size_path; + int success; + + //force path to be allocated + size_path = __get_array_size(path); + + success = __infer_nondet_int(); + return (success > 0) ? 0 : -1; +} + +char *readline(const char *prompt) { + int size_prompt; + char *ret; + int size; + + //force prompt to be allocated when not null + if (prompt != NULL){ + size_prompt = __get_array_size(prompt); + } + + //return random string of positive size + size = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(size<0); + + ret = malloc(sizeof(size)); + return ret; +} + +long random(void) { + long ret; + + ret = __infer_nondet_long_int(); + INFER_EXCLUDE_CONDITION(ret<0); + return ret; +} + +int putc(int c, FILE *stream){ + int rand; + + rand = __infer_nondet_int(); + if (rand > 0) + return c; //success + else + return EOF; //failure +} + +int access(const char *path, int mode){ + int size; + int success; + + //force path to be allocated + size = __get_array_size(path); + + //determine return value + success = __infer_nondet_int(); + + return (success > 0) ? 0 : -1; +} + + +size_t confstr(int name, char *buf, size_t len){ + int size; + int ret; + + + //determine return value + ret = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(ret < 0); + + //buf should be allocated if len is not zero. Otherwise, we want buf=0. + if (len) size = __get_array_size(buf); + else INFER_EXCLUDE_CONDITION(buf !=0); + + return ret; +} + +// return a non-deterministic value +int fflush(FILE *stream){ + return __infer_nondet_int(); +} + +int flock(int fd, int operation){ + int ret; + + ret = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(ret < -1 || ret > 0); + + return ret; +} + +int fsync(int fildes){ + int ret; + + ret = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(ret < -1 || ret > 0); + + return ret; +} + + +int fsctl(const char *path, unsigned long request, void *data, unsigned int options){ + int size1; + int size2; + int ret; + + //forces path and data to be allocated + size1 = __get_array_size(path); + size2 = __get_array_size(data); + + ret = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(ret < -1 || ret > 0); + + return ret; +} + +int getrusage(int who, struct rusage *r_usage){ + int ret; + + INFER_EXCLUDE_CONDITION(r_usage == 0); + + ret = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(ret < -1 || ret > 0); + + return ret; +} + + + +int gettimeofday(struct timeval *__restrict tp, gettimeofday_tzp_decl){ + struct timeval tmp_tp; + struct timezone tmp_tzp; + int success; + + if (tp!=0) *tp = tmp_tp; + if (tzp!=0) *(struct timezone *)tzp = tmp_tzp; + + success = __infer_nondet_int(); + return success ? 0 : -1; +} + + +struct tm *localtime_r(const time_t *__restrict timer, struct tm *__restrict result){ + int success; + struct tm tmp; + + INFER_EXCLUDE_CONDITION(timer == 0); + INFER_EXCLUDE_CONDITION(result == 0); + + success = __infer_nondet_int(); + *result = tmp; + + return (success > 0) ? result : 0; +} + + + +int mkdir (const char *filename, mode_t mode){ + int size; + int ret; + + size = __get_array_size(filename); + + ret = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(ret < -1 || ret > 0); + + return ret; +} + + +int munmap(void *addr, size_t len){ + int ret; + + INFER_EXCLUDE_CONDITION(addr == 0); + ret = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(ret < -1 || ret > 0); + + return ret; +} + +// returns a nondeterministc value +int pthread_mutex_destroy(pthread_mutex_t *mutex){ + INFER_EXCLUDE_CONDITION(mutex == 0); + return __infer_nondet_int(); +} + + +// returns a nondeterministc value +int pthread_mutex_init(pthread_mutex_t *__restrict mutex, const pthread_mutexattr_t *__restrict attr){ + INFER_EXCLUDE_CONDITION(mutex == 0); + return __infer_nondet_int(); +} + +// returns a nondeterministc value +int pthread_mutex_lock(pthread_mutex_t *mutex){ + INFER_EXCLUDE_CONDITION(mutex == 0); + return __infer_nondet_int(); +} + +// returns a nondeterministc value +int pthread_mutex_trylock(pthread_mutex_t *mutex){ + INFER_EXCLUDE_CONDITION(mutex == 0); + return __infer_nondet_int(); +} + +// returns a nondeterministc value +int pthread_mutex_unlock(pthread_mutex_t *mutex){ + INFER_EXCLUDE_CONDITION(mutex == 0); + return __infer_nondet_int(); +} + +// returns a nondeterministc value +int pthread_mutexattr_destroy(pthread_mutexattr_t *attr){ + INFER_EXCLUDE_CONDITION(attr == 0); + return __infer_nondet_int(); +} + +// returns a nondeterministc value +int pthread_mutexattr_init(pthread_mutexattr_t *attr){ + INFER_EXCLUDE_CONDITION(attr == 0); + return __infer_nondet_int(); +} + +// returns a nondeterministc value +int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type){ + INFER_EXCLUDE_CONDITION(attr == 0); + return __infer_nondet_int(); +} + +// returns a nondeterministc value and forces type to be allocated and set its content +int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *type){ + INFER_EXCLUDE_CONDITION(attr == 0); + *type= __infer_nondet_int(); + return __infer_nondet_int(); +} + +// return a positive non-deterministic number or -1. +int fileno(FILE *stream){ + int ret = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(ret<-1 || ret==0 ); + return ret; +} + +int fstat(int fildes, struct stat *buf) { + int ret; + struct stat s; // uninitialized struct stat + *buf = s; + ret = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(ret < -1 || ret>0); + return ret; +} + +// treates as skyp. Return nondeterministically 0 or -1 +int futimes(int fildes, const struct timeval times[2]) { + int ret = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(ret < -1 || ret>0); + return ret; +} + +int getchar(void) { + //randomly produce an error + int ret = __infer_nondet_int(); + if (ret < 0) return EOF; + else return ret; +} + +int isatty(int fildes) { + int ret; + + ret = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(ret < 0 || ret > 1); + return ret; +} + +void perror(const char *s) { + int size = __get_array_size(s); + size = 0; +} + +int pipe(int fildes[2]) { + int ret = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(ret < -1 || ret > 0); + return ret; +} + +int raise(int sig){ + return __infer_nondet_int(); +} + +ssize_t read(int fildes, void *buf, size_t nbyte) { + if (nbyte==0) return 0; + INFER_EXCLUDE_CONDITION(__get_array_size(buf) < nbyte); + + int ret = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(ret < -1 || ret > nbyte); + return ret; +} + +int sigaction(int sig, const struct sigaction *act, struct sigaction *oact){ + int ret = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(ret < -1 || ret >0); + return ret; +} + +// return the first argument +//long __builtin_expect(long x, long y) { +// return x; +//} +//TODO: this function has been disabled because it cannot be compiled with LLVM. +//It is normally a builtin that should not be implemented and LLVM complains about this + +// modelled as skip +void clearerr(FILE *stream) { +} + +// return a nondeterministic value +int ferror(FILE *stream) { + return __infer_nondet_int(); +} + +// return a nondeterministic value +int feof(FILE *stream) { + return __infer_nondet_int(); +} + +// write to *pos and return either 0 or -1 +int fgetpos(FILE *__restrict stream, fpos_t *__restrict pos) { + int success; + fpos_t t; + *pos = t; + success = __infer_nondet_int(); + if(success) return 0; + else return -1; +} + +// read from *pos and return either 0 or -1 +int fsetpos(FILE *stream, const fpos_t *pos) { + int success; + fpos_t t; + t = *pos; + success = __infer_nondet_int(); + if(success) return 0; + else return -1; +} + +// return a value between 0 and nmemb +size_t fread(void *__restrict ptr, size_t size, size_t nmemb, FILE *__restrict stream) { + size_t res; + res = __infer_nondet_size_t(); + if(size == 0 || nmemb == 0) return 0; + INFER_EXCLUDE_CONDITION(res < 0 || res > nmemb); + return res; +} + +// return a value between 0 and nmemb +size_t fwrite(const void *__restrict ptr, size_t size, size_t nmemb, FILE *__restrict stream) { + size_t res; + res = __infer_nondet_size_t(); + if(size == 0 || nmemb == 0) return 0; + INFER_EXCLUDE_CONDITION(res < 0 || res > nmemb); + return res; +} + +size_t strcspn(const char *s1, const char *s2){ + int size1, size2; + size1 = __get_array_size(s1); + size2 = __get_array_size(s2);; + + int ret = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(ret < 0); + return ret; +} + +int strerror_r(int errnum, char *strerrbuf, size_t buflen) { + INFER_EXCLUDE_CONDITION(__get_array_size(strerrbuf) < buflen); + + return __infer_nondet_int(); +} + +ssize_t write(int fildes, const void *buf, size_t nbyte) { + INFER_EXCLUDE_CONDITION(__get_array_size(buf) < nbyte); + + int ret = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(ret < -1 || nbyte < ret); + return ret; +} + +int creat(const char *path, mode_t mode) { + int size; + size = __get_array_size(path); + + int ret = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(ret < -1); + return ret; +} + +// modeled as skip +int fcntl(int fildes, int cmd, ...){ + int ret = __infer_nondet_int(); + return ret; +} + +// modelled as skip +int sigprocmask(int how, const sigset_t *set, sigset_t *oset){ + int ret = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(ret < -1 || 0 < ret); + return ret; +} + +// modelled as skip +void setbuf(FILE * __restrict stream, char * __restrict buf) { +} + +// return nondeterministically 0 or EOF +int setvbuf(FILE * __restrict stream, char * __restrict buf, int mode, size_t size) { + int n; + + n = __infer_nondet_int(); + if (n) return 0; + else return EOF; +} + +// the array base must be allocated with at least nmemb elements +// nondeterministically return 0 or a pointer inside the array +void *bsearch(const void *key, const void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)) +{ + int base_size; + int nondet; + int offset; + nondet = __infer_nondet_int(); + offset = __infer_nondet_int(); + + base_size = __get_array_size(base); + INFER_EXCLUDE_CONDITION(nmemb > base_size); + if(nondet) return 0; + INFER_EXCLUDE_CONDITION(offset < 0 || offset >= nmemb); + return (void *)base + offset; +} + +// return a nondeterministic value +int mblen(const char *s, size_t n) { + return __infer_nondet_int(); +} + +// return a nondeterministic value +size_t mbstowcs(wchar_t * __restrict pwcs, const char * __restrict s, size_t n) { + return (size_t)__infer_nondet_int(); +} + +// return a nondeterministic value +int mbtowc(wchar_t * __restrict pwc, const char * __restrict s, size_t n) { + return __infer_nondet_int(); +} + +// modeled as skip +void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)) { +} + +// return a nondeterministic value +int strcoll(const char *s1, const char *s2) { + return __infer_nondet_int(); +} + +// return a nondeterministic value +size_t wcstombs(char * __restrict s, const wchar_t * __restrict pwcs, size_t n) { + return (size_t)__infer_nondet_int(); +} + +// return a nondeterministic value +int wctomb(char *s, wchar_t wc) { + return __infer_nondet_int(); +} + +// modeled like open +int socket (int namespace, int style, int protocol) { + int *ret = malloc(sizeof(int)); + if(ret) { + __set_file_attribute(ret); + INFER_EXCLUDE_CONDITION(ret < (int *)1); // force result to be >= 0 + return (long) (ret-1); + } + return -1; +} + +void *xcalloc(size_t nmemb, size_t size) { + void *ret = calloc(nmemb, size); + INFER_EXCLUDE_CONDITION(ret == NULL); + return ret; +} + +void *xmalloc(size_t size) { + void *ret = malloc(size); + INFER_EXCLUDE_CONDITION(ret == NULL); + return ret; +} diff --git a/infer/models/c/src/math.c b/infer/models/c/src/math.c new file mode 100644 index 000000000..3fd08dde1 --- /dev/null +++ b/infer/models/c/src/math.c @@ -0,0 +1,863 @@ +/* + * Copyright (c) 2009-2013 Monoidics ltd. + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +// modelling of math functions + +#include "infer_builtins.h" + +#include + +double acos(double x) +{ + return __infer_nondet_double(); +} + +float acosf(float x) +{ + return __infer_nondet_float(); +} + +long double acosl(long double x) +{ + return __infer_nondet_long_double(); +} + +double acosh(double x) +{ + return __infer_nondet_double(); +} + +float acoshf(float x) +{ + return __infer_nondet_float(); +} + +long double acoshl(long double x) +{ + return __infer_nondet_long_double(); +} + +double asin(double x) +{ + return __infer_nondet_double(); +} + +float asinf(float x) +{ + return __infer_nondet_float(); +} + +long double asinl(long double x) +{ + return __infer_nondet_long_double(); +} + +double asinh(double x) +{ + return __infer_nondet_double(); +} + +float asinhf(float x) +{ + return __infer_nondet_float(); +} + +long double asinhl(long double x) +{ + return __infer_nondet_long_double(); +} + +double atan(double x) +{ + return __infer_nondet_double(); +} + +float atanf(float x) +{ + return __infer_nondet_float(); +} + +long double atanl(long double x) +{ + return __infer_nondet_long_double(); +} + +double atanh(double x) +{ + return __infer_nondet_double(); +} + +float atanhf(float x) +{ + return __infer_nondet_float(); +} + +long double atanhl(long double x) +{ + return __infer_nondet_long_double(); +} + +double atan2(double y, double x) +{ + return __infer_nondet_double(); +} + +float atan2f(float y, float x) +{ + return __infer_nondet_float(); +} + +long double atan2l(long double y, long double x) +{ + return __infer_nondet_long_double(); +} + +double cbrt(double x) +{ + return __infer_nondet_double(); +} + +float cbrtf(float x) +{ + return __infer_nondet_float(); +} + +long double cbrtl(long double x) +{ + return __infer_nondet_long_double(); +} + +double ceil(double x) +{ + return __infer_nondet_double(); +} + +float ceilf(float x) +{ + return __infer_nondet_float(); +} + +long double ceill(long double x) +{ + return __infer_nondet_long_double(); +} + +double copysign(double x, double y) +{ + return __infer_nondet_double(); +} + +float copysignf(float x, float y) +{ + return __infer_nondet_float(); +} + +long double copysignl(long double x, long double y) +{ + return __infer_nondet_long_double(); +} + +double cos(double x) +{ + return __infer_nondet_double(); +} + +float cosf(float x) +{ + return __infer_nondet_float(); +} + +long double cosl(long double x) +{ + return __infer_nondet_long_double(); +} + +double cosh(double x) +{ + return __infer_nondet_double(); +} + +float coshf(float x) +{ + return __infer_nondet_float(); +} + +long double coshl(long double x) +{ + return __infer_nondet_long_double(); +} + +double exp(double x) +{ + return __infer_nondet_double(); +} + +float expf(float x) +{ + return __infer_nondet_float(); +} + +long double expl(long double x) +{ + return __infer_nondet_long_double(); +} + +double exp2(double x) +{ + return __infer_nondet_double(); +} + +float exp2f(float x) +{ + return __infer_nondet_float(); +} + +long double exp2l(long double x) +{ + return __infer_nondet_long_double(); +} + +double expm1(double x) +{ + return __infer_nondet_double(); +} + +float expm1f(float x) +{ + return __infer_nondet_float(); +} + +long double expm1l(long double x) +{ + return __infer_nondet_long_double(); +} + +double erf(double x) +{ + return __infer_nondet_double(); +} + +float erff(float x) +{ + return __infer_nondet_float(); +} + +long double erfl(long double x) +{ + return __infer_nondet_long_double(); +} + +double erfc(double x) +{ + return __infer_nondet_double(); +} + +float erfcf(float x) +{ + return __infer_nondet_float(); +} + +long double erfcl(long double x) +{ + return __infer_nondet_long_double(); +} + +double fabs(double x) +{ + return __infer_nondet_double(); +} + +float fabsf(float x) +{ + return __infer_nondet_float(); +} + +long double fabsl(long double x) +{ + return __infer_nondet_long_double(); +} + +double fdim(double x, double y) +{ + return __infer_nondet_double(); +} + +float fdimf(float x, float y) +{ + return __infer_nondet_float(); +} + +long double fdiml(long double x, long double y) +{ + return __infer_nondet_long_double(); +} + +double floor(double x) +{ + return __infer_nondet_double(); +} + +float floorf(float x) +{ + return __infer_nondet_float(); +} + +long double floorl(long double x) +{ + return __infer_nondet_long_double(); +} + +double fma(double x, double y, double z) +{ + return __infer_nondet_double(); +} + +float fmaf(float x, float y, float z) +{ + return __infer_nondet_float(); +} + +long double fmal(long double x, long double y,long double z) +{ + return __infer_nondet_long_double(); +} + +double fmax(double x, double y) +{ + return __infer_nondet_double(); +} + +float fmaxf(float x, float y) +{ + return __infer_nondet_float(); +} + +long double fmaxl(long double x, long double y) +{ + return __infer_nondet_long_double(); +} + +double fmin(double x, double y) +{ + return __infer_nondet_double(); +} + +float fminf(float x, float y) +{ + return __infer_nondet_float(); +} + +long double fminl(long double x, long double y) +{ + return __infer_nondet_long_double(); +} + +double fmod(double x, double y) +{ + return __infer_nondet_double(); +} + +float fmodf(float x, float y) +{ + return __infer_nondet_float(); +} + +long double fmodl(long double x, long double y) +{ + return __infer_nondet_long_double(); +} + +double frexp(double value, int *exp) +{ + *exp = __infer_nondet_int(); + return __infer_nondet_double(); +} + +float frexpf(float value, int *exp) +{ + *exp = __infer_nondet_int(); + return __infer_nondet_float(); +} + +long double frexpl(long double value, int *exp) +{ + *exp = __infer_nondet_int(); + return __infer_nondet_long_double(); +} + +double hypot(double x, double y) +{ + return __infer_nondet_double(); +} + +float hypotf(float x, float y) +{ + return __infer_nondet_float(); +} + +long double hypotl(long double x, long double y) +{ + return __infer_nondet_long_double(); +} + +int ilogb(double x) +{ + return __infer_nondet_int(); +} + +int ilogbf(float x) +{ + return __infer_nondet_int(); +} + +int ilogbl(long double x) +{ + return __infer_nondet_int(); +} + +double ldexp(double x, int exp) +{ + return __infer_nondet_double(); +} + +float ldexpf(float x, int exp) +{ + return __infer_nondet_float(); +} + +long double ldexpl(long double x, int exp) +{ + return __infer_nondet_long_double(); +} + +double lgamma(double x) +{ + return __infer_nondet_double(); +} + +float lgammaf(float x) +{ + return __infer_nondet_float(); +} + +long double lgammal(long double x) +{ + return __infer_nondet_long_double(); +} + +double log(double x) +{ + return __infer_nondet_double(); +} + +float logf(float x) +{ + return __infer_nondet_float(); +} + +long double logl(long double x) +{ + return __infer_nondet_long_double(); +} + +double log10(double x) +{ + return __infer_nondet_double(); +} + +float log10f(float x) +{ + return __infer_nondet_float(); +} + +long double log10l(long double x) +{ + return __infer_nondet_long_double(); +} + +double log1p(double x) +{ + return __infer_nondet_double(); +} + +float log1pf(float x) +{ + return __infer_nondet_float(); +} + +long double log1pl(long double x) +{ + return __infer_nondet_long_double(); +} + +#ifdef log2 +#undef log2 // disable expansion of log2 +#endif +double log2(double x) +{ + return __infer_nondet_double(); +} + +#ifdef log2f +#undef log2f // disable expansion of log2f +#endif +float log2f(float x) +{ + return __infer_nondet_float(); +} + +long double log2l(long double x) +{ + return __infer_nondet_long_double(); +} + +double logb(double x) +{ + return __infer_nondet_double(); +} + +float logbf(float x) +{ + return __infer_nondet_float(); +} + +long double logbl(long double x) +{ + return __infer_nondet_long_double(); +} + +long int lrint(double x) +{ + return __infer_nondet_long_int(); +} + +long int lrintf(float x) +{ + return __infer_nondet_long_int(); +} + +long int lrintl(long double x) +{ + return __infer_nondet_long_int(); +} + +long long int llrint(double x) +{ + return __infer_nondet_long_long_int(); +} + +long long int llrintf(float x) +{ + return __infer_nondet_long_long_int(); +} + +long long int llrintl(long double x) +{ + return __infer_nondet_long_long_int(); +} + +long int lround(double x) +{ + return __infer_nondet_long_int(); +} + +long int lroundf(float x) +{ + return __infer_nondet_long_int(); +} + +long int lroundl(long double x) +{ + return __infer_nondet_long_int(); +} + +long long int llround(double x) +{ + return __infer_nondet_long_long_int(); +} + +long long int llroundf(float x) +{ + return __infer_nondet_long_long_int(); +} + +long long int llroundl(long double x) +{ + return __infer_nondet_long_long_int(); +} + +double modf(double value, double *iptr) +{ + *iptr = __infer_nondet_double(); + return __infer_nondet_double(); +} + +float modff(float value, float *iptr) +{ + *iptr = __infer_nondet_float(); + return __infer_nondet_float(); +} + +long double modfl(long double value, long double *iptr) +{ + *iptr = __infer_nondet_long_double(); + return __infer_nondet_long_double(); +} + +double nearbyint(double x) +{ + return __infer_nondet_double(); +} + +double nan(const char *tagp) +{ + return __infer_nondet_double(); +} + +float nanf(const char *tagp) +{ + return __infer_nondet_float(); +} + +long double nanl(const char *tagp) +{ + return __infer_nondet_long_double(); +} + +float nearbyintf(float x) +{ + return __infer_nondet_float(); +} + +long double nearbyintl(long double x) +{ + return __infer_nondet_long_double(); +} + +double nextafter(double x, double y) +{ + return __infer_nondet_double(); +} + +float nextafterf(float x, float y) +{ + return __infer_nondet_float(); +} + +long double nextafterl(long double x, long double y) +{ + return __infer_nondet_long_double(); +} + +double nexttoward(double x, long double y) +{ + return __infer_nondet_double(); +} + +float nexttowardf(float x, long double y) +{ + return __infer_nondet_float(); +} + +long double nexttowardl(long double x, long double y) +{ + return __infer_nondet_long_double(); +} + +double pow(double x, double y) +{ + return __infer_nondet_double(); +} + +float powf(float x, float y) +{ + return __infer_nondet_float(); +} + +long double powl(long double x, long double y) +{ + return __infer_nondet_long_double(); +} + +double rint(double x) +{ + return __infer_nondet_double(); +} + +float rintf(float x) +{ + return __infer_nondet_float(); +} + +long double rintl(long double x) +{ + return __infer_nondet_long_double(); +} + +double remainder(double x, double y) +{ + return __infer_nondet_double(); +} + +float remainderf(float x, float y) +{ + return __infer_nondet_float(); +} + +long double remainderl(long double x, long double y) +{ + return __infer_nondet_long_double(); +} + +double round(double x) +{ + return __infer_nondet_double(); +} + +float roundf(float x) +{ + return __infer_nondet_float(); +} + +long double roundl(long double x) +{ + return __infer_nondet_long_double(); +} + +double scalbn(double x, int n) +{ + return __infer_nondet_double(); +} + +float scalbnf(float x, int n) +{ + return __infer_nondet_float(); +} + +long double scalbnl(long double x, int n) +{ + return __infer_nondet_long_double(); +} + +double scalbln(double x, long int n) +{ + return __infer_nondet_double(); +} + +float scalblnf(float x, long int n) +{ + return __infer_nondet_float(); +} + +long double scalblnl(long double x, long int n) +{ + return __infer_nondet_long_double(); +} + +double sin(double x) +{ + return __infer_nondet_double(); +} + +float sinf(float x) +{ + return __infer_nondet_float(); +} + +long double sinl(long double x) +{ + return __infer_nondet_long_double(); +} + +double sinh(double x) +{ + return __infer_nondet_double(); +} + +float sinhf(float x) +{ + return __infer_nondet_float(); +} + +long double sinhl(long double x) +{ + return __infer_nondet_long_double(); +} + +double sqrt(double x) +{ + return __infer_nondet_double(); +} + +float sqrtf(float x) +{ + return __infer_nondet_float(); +} + +long double sqrtl(long double x) +{ + return __infer_nondet_long_double(); +} + +double tan(double x) +{ + return __infer_nondet_double(); +} + +float tanf(float x) +{ + return __infer_nondet_float(); +} + +long double tanl(long double x) +{ + return __infer_nondet_long_double(); +} + +double tanh(double x) +{ + return __infer_nondet_double(); +} + +float tanhf(float x) +{ + return __infer_nondet_float(); +} + +long double tanhl(long double x) +{ + return __infer_nondet_long_double(); +} + +double tgamma(double x) +{ + return __infer_nondet_double(); +} + +float tgammaf(float x) +{ + return __infer_nondet_float(); +} + +long double tgammal(long double x) +{ + return __infer_nondet_long_double(); +} + +double trunc(double x) +{ + return __infer_nondet_double(); +} + +float truncf(float x) +{ + return __infer_nondet_float(); +} + +long double truncl(long double x) +{ + return __infer_nondet_long_double(); +} diff --git a/infer/models/c/src/wchar.c b/infer/models/c/src/wchar.c new file mode 100644 index 000000000..97104dd4f --- /dev/null +++ b/infer/models/c/src/wchar.c @@ -0,0 +1,362 @@ +/* + * Copyright (c) 2009-2013 Monoidics ltd. + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +// modelling of wide character functions + +#include "infer_builtins.h" + +#include +#include +#include +#include + +#define restrict + +#ifdef _WIN32 +#define CLIBCALL __cdecl +#else +#define CLIBCALL +#endif + +int CLIBCALL fwscanf(FILE * restrict stream, const wchar_t * restrict format, ...); // builtin: modeled internally +int CLIBCALL swscanf(const wchar_t * restrict s, const wchar_t * restrict format, ...); // builtin: modeled internally +int CLIBCALL vfwscanf(FILE * restrict stream, const wchar_t * restrict format, va_list arg); // builtin: modeled internally +int CLIBCALL vswscanf(const wchar_t * restrict s, const wchar_t * restrict format, va_list arg); // builtin: modeled internally +int CLIBCALL vwscanf(const wchar_t * restrict format, va_list arg); // builtin: modeled internally +int CLIBCALL wscanf(const wchar_t * restrict format, ...); // builtin: modeled internally + + +wint_t CLIBCALL btowc(int c) +{ + return __infer_nondet_int(); +} + +wint_t CLIBCALL fgetwc(FILE *stream) +{ + return __infer_nondet_int(); +} + +// modelled like fgets +wchar_t * CLIBCALL fgetws(wchar_t * restrict s, int n, FILE * restrict stream) +{ + return (wchar_t *) fgets((char *)s, n, stream); +} + +wint_t CLIBCALL fputwc(wchar_t c, FILE *stream) +{ + return __infer_nondet_int(); +} + +// modeled using fputs +int CLIBCALL fputws(const wchar_t * restrict s, FILE * restrict stream) +{ + return fputs((char *)s, stream); +} + +int CLIBCALL fwide(FILE *stream, int mode) +{ + return __infer_nondet_int(); +} + +// return a nondeterministic nonnegative integer +int CLIBCALL fwprintf(FILE * restrict stream, const wchar_t * restrict format, ...) +{ + int res; + res = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(res < 0); + return res; +} + + +#ifdef getwc +#undef getwc // disable expansion of getwc +#endif +wint_t CLIBCALL getwc(FILE *stream) +{ + return __infer_nondet_int(); +} + +#ifdef getwchar +#undef getwchar // disable expansion of getwchar +#endif +wint_t CLIBCALL getwchar() +{ + return __infer_nondet_int(); +} + +size_t CLIBCALL mbrlen(const char * restrict s, size_t n, mbstate_t * restrict ps) +{ + return __infer_nondet_int(); +} + +size_t CLIBCALL mbrtowc(wchar_t * restrict pwc, const char * restrict s, size_t n, mbstate_t * restrict ps) +{ + return __infer_nondet_int(); +} + +int CLIBCALL mbsinit(const mbstate_t *ps) +{ + return __infer_nondet_int(); +} + +size_t CLIBCALL mbsrtowcs(wchar_t * restrict dst, const char ** restrict src, size_t len, mbstate_t * restrict ps) +{ + return __infer_nondet_int(); +} + +#ifdef putwc +#undef putwc // disable expansion of putwc +#endif +wint_t CLIBCALL putwc(wchar_t c, FILE *stream) +{ + return __infer_nondet_int(); +} + +#ifdef putwchar +#undef putwchar // disable expansion of putwchar +#endif +wint_t CLIBCALL putwchar(wchar_t c) +{ + return __infer_nondet_int(); +} + +// s must be allocated +// return a nondeterministic nonnegative integer +int CLIBCALL swprintf(wchar_t * restrict s, size_t n, const wchar_t * restrict format, ...) +{ + int res; + int size1; + size1 = __get_array_size(s); + res = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(res < 0); + return res; +} + +wint_t CLIBCALL ungetwc(wint_t c, FILE *stream) +{ + return __infer_nondet_int(); +} + +// return a nondeterministic nonnegative integer +int CLIBCALL vfwprintf(FILE * restrict stream, const wchar_t * restrict format, va_list arg) +{ + int res; + res = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(res < 0); + return res; +} + +// return a nondeterministic nonnegative integer +int CLIBCALL vswprintf(wchar_t * restrict s, size_t n, const wchar_t * restrict format, va_list arg) +{ + int res; + res = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(res < 0); + return res; +} + +// return a nondeterministic nonnegative integer +int CLIBCALL vwprintf(const wchar_t * restrict format, va_list arg) +{ + int res; + res = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(res < 0); + return res; +} + +size_t CLIBCALL wcrtomb(char * restrict s, wchar_t wc, mbstate_t * restrict ps) +{ + return __infer_nondet_int(); +} + +size_t CLIBCALL wcsrtombs(char * restrict dst, const wchar_t ** restrict src, size_t len, mbstate_t * restrict ps) +{ + return __infer_nondet_int(); +} + +// return a nondeterministic nonnegative integer +int CLIBCALL wprintf(const wchar_t * restrict format, ...) +{ + int res; + res = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(res < 0); + return res; +} + +// modeled using strcat +wchar_t * CLIBCALL wcscat(wchar_t * restrict s1, const wchar_t * restrict s2) +{ + return (wchar_t *) strcat((char *) s1, (char *) s2); +} + +// modeled using strchr +wchar_t * CLIBCALL wcschr(const wchar_t *s, wchar_t c) +{ + return (wchar_t *) strchr((char *) s, c); +} + +// modeled using strcmp +int CLIBCALL wcscmp(const wchar_t *s1, const wchar_t *s2) +{ + return strcmp((char *) s1, (char *) s2); +} + +// modeled using strcmp +int CLIBCALL wcscoll(const wchar_t *s1, const wchar_t *s2) +{ + return strcmp((char *) s1, (char *) s2); +} + +// return a nondeterministic nonnegative integer +size_t CLIBCALL wcsftime(wchar_t * restrict s, size_t maxsize,const wchar_t * restrict format, const struct tm * restrict timeptr) +{ + int res; + res = __infer_nondet_int(); + INFER_EXCLUDE_CONDITION(res < 0); + return res; +} + +// modeled using strcpy +wchar_t * CLIBCALL wcscpy(wchar_t * restrict s1, const wchar_t * restrict s2) +{ + return (wchar_t *) strcpy((char *) s1, (char *) s2); +} + +// modeled using strcmp +size_t CLIBCALL wcscspn(const wchar_t *s1, const wchar_t *s2) +{ + return strcmp((char *) s1, (char *) s2); +} + +// modeled using strlen +size_t CLIBCALL wcslen(const wchar_t *s) +{ + return strlen((char *) s); +} + +// modeled using strncat +wchar_t * CLIBCALL wcsncat(wchar_t * restrict s1, const wchar_t * restrict s2, size_t n) +{ + return (wchar_t *) strncat((char *) s1, (char *) s2, n); +} + +// modeled using strncmp +int CLIBCALL wcsncmp(const wchar_t *s1, const wchar_t *s2, size_t n) +{ + return strncmp((char *) s1, (char *) s2, n); +} + +// modeled using strpbrk +wchar_t * CLIBCALL wcspbrk(const wchar_t *s1, const wchar_t *s2) +{ + return (wchar_t *) strpbrk((char *) s1, (char *) s2); +} + +// modeled using strchr +wchar_t * CLIBCALL wcsrchr(const wchar_t *s, wchar_t c) +{ + return (wchar_t *) strchr((char *) s, c); +} + +// modeled using strncpy +wchar_t * CLIBCALL wcsncpy(wchar_t * restrict s1, const wchar_t * restrict s2, size_t n) +{ + return (wchar_t *) strncpy((char *) s1, (char *) s2, n); +} + +// modeled using strspn +size_t CLIBCALL wcsspn(const wchar_t *s1, const wchar_t *s2) +{ + return strspn((char *) s1, (char *) s2); +} + +// modeled using strstr +wchar_t * CLIBCALL wcsstr(const wchar_t *s1, const wchar_t *s2) +{ + return (wchar_t *) strstr((char *) s1, (char *) s2); +} + +int CLIBCALL wctob(wint_t c) +{ + return __infer_nondet_int(); +} + +double CLIBCALL wcstod(const wchar_t * restrict nptr, wchar_t ** restrict endptr) +{ + return __infer_nondet_double(); +} + +float CLIBCALL wcstof(const wchar_t * restrict nptr, wchar_t ** restrict endptr) +{ + return __infer_nondet_float(); +} + +// simplified modeling which returns s1 +wchar_t * CLIBCALL wcstok(wchar_t * restrict s1, const wchar_t * restrict s2, wchar_t ** restrict ptr) +{ + return s1; +} + +long double CLIBCALL wcstold(const wchar_t * restrict nptr, wchar_t ** restrict endptr) +{ + return __infer_nondet_long_double(); +} + +long int CLIBCALL wcstol(const wchar_t * restrict nptr, wchar_t ** restrict endptr, int base) +{ + return __infer_nondet_long_int(); +} + +long long int CLIBCALL wcstoll(const wchar_t * restrict nptr, wchar_t ** restrict endptr, int base) +{ + return __infer_nondet_long_long_int(); +} + +unsigned long int CLIBCALL wcstoul(const wchar_t * restrict nptr, wchar_t ** restrict endptr, int base) +{ + return __infer_nondet_unsigned_long_int(); +} + +unsigned long long int CLIBCALL wcstoull(const wchar_t * restrict nptr, wchar_t ** restrict endptr, int base) +{ + return __infer_nondet_long_long_int(); +} + +// modeled using strncmp +size_t CLIBCALL wcsxfrm(wchar_t * restrict s1, const wchar_t * restrict s2, size_t n) +{ + return strncmp((char *) s1, (char *) s2, n); +} + +// modeled using memchr +wchar_t * CLIBCALL wmemchr(const wchar_t *s, wchar_t c, size_t n) +{ + return (wchar_t *) memchr((char *) s, c, n); +} + +// modeled using memcmp +int CLIBCALL wmemcmp(const wchar_t *s1, const wchar_t *s2, size_t n) +{ + return memcmp((char *) s1, (char *) s2, n); +} + +// modeled using memcpy +wchar_t * CLIBCALL wmemcpy(wchar_t * restrict s1, const wchar_t * restrict s2, size_t n) +{ + return (wchar_t *) memcpy((char *) s1, (char *) s2, n); +} + +// modeled using memmove +wchar_t * CLIBCALL wmemmove(wchar_t *s1, const wchar_t *s2, size_t n) +{ + return (wchar_t *) memmove((char *) s1, (char *) s2, n); +} + +// modeled using memset +wchar_t * CLIBCALL wmemset(wchar_t *s, wchar_t c, size_t n) +{ + return (wchar_t *) memset((char *) s, c, n); +} diff --git a/infer/models/c/src/wctype.c b/infer/models/c/src/wctype.c new file mode 100644 index 000000000..2cf4f6ac4 --- /dev/null +++ b/infer/models/c/src/wctype.c @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2009-2013 Monoidics ltd. + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +// modelling of wctype functions + +#define _DONT_USE_CTYPE_INLINE_ +#include + +#include "infer_builtins.h" + +#define restrict + +#ifdef _WIN32 +#define CLIBCALL __cdecl +#else +#define CLIBCALL +#endif + + +// Microsoft-specific +int CLIBCALL __iswcsymf(wint_t wc) +{ + return __infer_nondet_int(); +} + +// Microsoft-specific, inline +int CLIBCALL isleadbyte(int wc) +{ + return __infer_nondet_int(); +} + +int CLIBCALL iswalnum(wint_t wc) +{ + return __infer_nondet_int(); +} + +int CLIBCALL iswalpha(wint_t wc) +{ + return __infer_nondet_int(); +} + +int CLIBCALL iswblank(wint_t wc) +{ + return __infer_nondet_int(); +} + +int CLIBCALL iswcntrl(wint_t wc) +{ + return __infer_nondet_int(); +} + +int CLIBCALL iswctype(wint_t wc, wctype_t desc) +{ + return __infer_nondet_int(); +} + +int CLIBCALL iswdigit(wint_t wc) +{ + return __infer_nondet_int(); +} + +int CLIBCALL iswgraph(wint_t wc) +{ + return __infer_nondet_int(); +} + +int CLIBCALL iswlower(wint_t wc) +{ + return __infer_nondet_int(); +} + +int CLIBCALL iswprint(wint_t wc) +{ + return __infer_nondet_int(); +} + +int CLIBCALL iswpunct(wint_t wc) +{ + return __infer_nondet_int(); +} + +int CLIBCALL iswspace(wint_t wc) +{ + return __infer_nondet_int(); +} + +int CLIBCALL iswupper(wint_t wc) +{ + return __infer_nondet_int(); +} + +int CLIBCALL iswxdigit(wint_t wc) +{ + return __infer_nondet_int(); +} + +wint_t CLIBCALL towlower(wint_t wc) +{ + return __infer_nondet_int(); +} + +wint_t CLIBCALL towctrans(wint_t wc, wctrans_t desc) +{ + return __infer_nondet_int(); +} + +wint_t CLIBCALL towupper(wint_t wc) +{ + return __infer_nondet_int(); +} + +wctrans_t CLIBCALL wctrans(const char *property) +{ + return (wctrans_t)__infer_nondet_int(); +} + +wctype_t CLIBCALL wctype(const char *property) +{ + return (wctype_t)__infer_nondet_int(); +} diff --git a/infer/models/c/src/xlib.c b/infer/models/c/src/xlib.c new file mode 100644 index 000000000..b59df9cce --- /dev/null +++ b/infer/models/c/src/xlib.c @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2009-2013 Monoidics ltd. + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +// Basic modelling of some xlib functions + +#include "infer_builtins.h" + +#include + +// modelled using malloc +char *XGetAtomName(void *display, void *atom) { + int size; + INFER_EXCLUDE_CONDITION(size <= 0); + return malloc(size); +} + +// modelled as free, requires NONNULL pointer +void XFree(void *ptr) { + INFER_EXCLUDE_CONDITION(!ptr); + free(ptr); +} diff --git a/infer/models/cpp/Makefile b/infer/models/cpp/Makefile new file mode 100644 index 000000000..4521fb9cb --- /dev/null +++ b/infer/models/cpp/Makefile @@ -0,0 +1,24 @@ +SHELL := /bin/bash +CWD = $(shell pwd) +BINDIR = $(CWD)/../../bin +LIBDIR = $(CWD)/../../lib +LIB_SPECS = $(LIBDIR)/specs +CPP_MODELS_FILE = $(LIB_SPECS)/cpp_models + +INFER = ANALYZE_MODELS=1 $(BINDIR)/infer + +default: run_infer + +.PHONY: run_infer install clean + +run_infer: clean + $(INFER) -o $(CWD)/out --models_mode --no_failures_allowed -- make -C src + +install: run_infer + cp out/specs/*.specs $(LIB_SPECS) + touch $(CPP_MODELS_FILE) + rm -rf out + +clean: + if [ -a $(CPP_MODELS_FILE) ];then rm $(CPP_MODELS_FILE);fi + make -C src clean diff --git a/infer/models/cpp/src/Makefile b/infer/models/cpp/src/Makefile new file mode 100644 index 000000000..91306a0e9 --- /dev/null +++ b/infer/models/cpp/src/Makefile @@ -0,0 +1,22 @@ +SOURCES=$(wildcard *.cpp) +OBJECTS=$(SOURCES:.cpp=.o) +CXX=g++ +FLAGS=-c -w + +default: test + +all: test + +.PHONY: test +test: $(OBJECTS) + echo "test called\n" + +.PHONY: configure +configure: + echo "no configure required\n" + +clean: + rm -rf *.o + +.c.o: + $(CXX) $(FLAGS) $< -o $@ diff --git a/infer/models/cpp/src/shared_ptr.cpp b/infer/models/cpp/src/shared_ptr.cpp new file mode 100644 index 000000000..44778f17a --- /dev/null +++ b/infer/models/cpp/src/shared_ptr.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2009-2013 Monoidics ltd. + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +// models for shared_ptr + +extern "C" void __method_set_ignore_attribute(void **self, void *arg); + +// standard constructor +extern "C" void __infer_shared_ptr(void **self, void *arg) { + __method_set_ignore_attribute(self, arg); // auto memory management + *self = arg; +} + +// constructor taking a reference to a shared_ptr +extern "C" void __infer_shared_ptr_ref(void **arg1, void **arg2) { + *arg1 = *arg2; +} + +// operator= +extern "C" void** __infer_shared_ptr_eq(void **arg1, void **arg2) { + *arg1 = *arg2; + return arg1; +} + +// operator== +extern "C" int __infer_shared_ptr_eqeq(void **arg1, void **arg2) { + return (*arg1 == *arg2); +} + +// operator-> +extern "C" void* __infer_shared_ptr_arrow(void **arg) { + return *arg; +} + +// destructor +extern "C" void __infer_shared_ptr_destructor(void **arg) { +} diff --git a/infer/models/java/BUCK b/infer/models/java/BUCK new file mode 100644 index 000000000..92cd2024c --- /dev/null +++ b/infer/models/java/BUCK @@ -0,0 +1,11 @@ +java_library( + name = 'models', + srcs = glob(['src/**/*.java']), + visibility = [ + 'PUBLIC', + ], + deps = [ + '//dependencies/java/jackson:jackson', + '//infer/lib/java/android:android', + ] +) diff --git a/infer/models/java/Makefile b/infer/models/java/Makefile new file mode 100644 index 000000000..d77df5edd --- /dev/null +++ b/infer/models/java/Makefile @@ -0,0 +1,36 @@ +SHELL := /bin/bash +CWD = $(shell pwd) +BINDIR = $(CWD)/../../bin + +INFERJ = ANALYZE_MODELS=1 $(BINDIR)/inferJ --buck --analyzer infer --multicore 1 + +ANDROID_JAR = ../../lib/java/android/android-19.jar +JACKSON_JAR = ../../../dependencies/java/jackson/jackson-2.2.3.jar + +REMOVE = rm -rf +MAKE = mkdir + +CURRENT_DIR = $(shell pwd) + +MODELS_OUT = $(CURRENT_DIR)/models +CSV_REPORT = $(MODELS_OUT)/infer/report.csv +MODELS_JAR = $(CURRENT_DIR)/models.jar +DEPLOYED_MODELS_JAR = ../../lib/java/models.jar + +JAVA_SOURCES = $(shell find src -name "*.java") + +.PHONY: all clean + +all: clean $(MODELS_JAR) + +clean: + $(REMOVE) $(MODELS_OUT) + $(REMOVE) $(DEPLOYED_MODELS_JAR) + +$(CSV_REPORT): $(JAVA_SOURCES) + mkdir $(MODELS_OUT) + $(INFERJ) javac -bootclasspath $(ANDROID_JAR) -d $(MODELS_OUT) -classpath $(JACKSON_JAR) $(JAVA_SOURCES) + +$(MODELS_JAR): $(CSV_REPORT) + cd $(MODELS_OUT); jar cf $(MODELS_JAR) * + mv $(MODELS_JAR) $(DEPLOYED_MODELS_JAR) diff --git a/infer/models/java/build.xml b/infer/models/java/build.xml new file mode 100644 index 000000000..25e4840a4 --- /dev/null +++ b/infer/models/java/build.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/infer/models/java/src/android/app/Activity.java b/infer/models/java/src/android/app/Activity.java new file mode 100644 index 000000000..82d36e95c --- /dev/null +++ b/infer/models/java/src/android/app/Activity.java @@ -0,0 +1,71 @@ +/* +* Copyright (C) 2006 The Android Open Source Project +* +* 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 android.app; + +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.content.res.TypedArray; +import android.database.Cursor; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.text.SpannableStringBuilder; +import android.util.ArrayMap; +import android.util.AttributeSet; +import android.util.SparseArray; +import android.view.*; +import com.android.internal.app.ActionBarImpl; +import com.facebook.infer.models.InferUndefined; + +import java.util.ArrayList; +import java.util.HashMap; + +public abstract class Activity extends ContextThemeWrapper { + + public TypedArray obtainStyledAttributes(int[] attrs) { + return new TypedArray(null, attrs, attrs, 1); + } + + public TypedArray obtainStyledAttributes(int resid, int[] attrs) + throws NotFoundException { + if (InferUndefined.boolean_undefined()) { + throw new NotFoundException(); + } + return new TypedArray(null, attrs, attrs, 1); + } + + public TypedArray obtainStyledAttributes(AttributeSet set, + int[] attrs, int defStyleAttr, int defStyleRes) { + return new TypedArray(null, attrs, attrs, 1); + } + + public static class NotFoundException extends RuntimeException { + public NotFoundException() { + } + + public NotFoundException(String name) { + super(name); + } + } + + public TypedArray obtainAttributes(AttributeSet set, int[] attrs) { + return new TypedArray(null, attrs, attrs, 1); + } + +} diff --git a/infer/models/java/src/android/app/DownloadManager.java b/infer/models/java/src/android/app/DownloadManager.java new file mode 100644 index 000000000..2e8786c2c --- /dev/null +++ b/infer/models/java/src/android/app/DownloadManager.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * 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 android.app; + +import android.content.ContentResolver; +import android.database.Cursor; +import android.net.Uri; + + +public class DownloadManager { + + private ContentResolver mResolver; + private String mPackageName; + + public DownloadManager(ContentResolver resolver, String packageName) { + mResolver = resolver; + mPackageName = packageName; + } + + public static class Query { + } + + public Cursor query(Query query) { + return mResolver.query(null, null, null, null, null); + } + + +} diff --git a/infer/models/java/src/android/content/ContentProviderClient.java b/infer/models/java/src/android/content/ContentProviderClient.java new file mode 100644 index 000000000..f2b69a464 --- /dev/null +++ b/infer/models/java/src/android/content/ContentProviderClient.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * 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 android.content; + +import android.database.Cursor; +import android.database.sqlite.SQLiteCursor; +import android.net.Uri; +import android.os.CancellationSignal; +import android.os.Handler; +import android.os.RemoteException; +import dalvik.system.CloseGuard; + + +public class ContentProviderClient { + private static String TAG; + + private static Handler sAnrHandler; + + private ContentResolver mContentResolver; + private IContentProvider mContentProvider; + private String mPackageName; + private boolean mStable; + + private CloseGuard mGuard; + + private long mAnrTimeout; + private NotRespondingRunnable mAnrRunnable; + + private boolean mReleased; + + + ContentProviderClient( + ContentResolver contentResolver, IContentProvider contentProvider, boolean stable) { + mContentResolver = contentResolver; + mContentProvider = contentProvider; + mPackageName = contentResolver.mPackageName; + mStable = stable; + } + + public Cursor query(Uri url, String[] projection, String selection, + String[] selectionArgs, String sortOrder) throws RemoteException { + return query(url, projection, selection, selectionArgs, sortOrder, null); + } + + public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs, + String sortOrder, CancellationSignal cancellationSignal) throws RemoteException { + return new SQLiteCursor(null, null, null); + } + + private class NotRespondingRunnable { + } + + +} diff --git a/infer/models/java/src/android/content/ContentResolver.java b/infer/models/java/src/android/content/ContentResolver.java new file mode 100644 index 000000000..90be9493d --- /dev/null +++ b/infer/models/java/src/android/content/ContentResolver.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 android.content; + +import android.database.Cursor; +import android.database.sqlite.SQLiteCursor; +import android.net.Uri; +import android.os.CancellationSignal; + +import com.facebook.infer.models.InferUndefined; + +import java.util.Random; + + +public class ContentResolver { + + public static String SYNC_EXTRAS_ACCOUNT; + + public static String SYNC_EXTRAS_EXPEDITED; + + public static String SYNC_EXTRAS_FORCE; + + public static String SYNC_EXTRAS_IGNORE_SETTINGS; + + public static String SYNC_EXTRAS_IGNORE_BACKOFF; + + public static String SYNC_EXTRAS_DO_NOT_RETRY; + + public static String SYNC_EXTRAS_MANUAL; + + public static String SYNC_EXTRAS_UPLOAD; + + public static String SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS; + + public static String SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS; + + public static String SYNC_EXTRAS_EXPECTED_UPLOAD; + + public static String SYNC_EXTRAS_EXPECTED_DOWNLOAD; + + public static String SYNC_EXTRAS_PRIORITY; + + public static String SYNC_EXTRAS_DISALLOW_METERED; + + public static String SYNC_EXTRAS_INITIALIZE; + + public static Intent ACTION_SYNC_CONN_STATUS_CHANGED; + + public static String SCHEME_CONTENT; + public static String SCHEME_ANDROID_RESOURCE; + public static String SCHEME_FILE; + + public static String CURSOR_ITEM_BASE_TYPE; + + public static String CURSOR_DIR_BASE_TYPE; + + public static int SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS; + + public static int SYNC_ERROR_AUTHENTICATION; + + public static int SYNC_ERROR_IO; + + public static int SYNC_ERROR_PARSE; + + public static int SYNC_ERROR_CONFLICT; + + public static int SYNC_ERROR_TOO_MANY_DELETIONS; + + public static int SYNC_ERROR_TOO_MANY_RETRIES; + + public static int SYNC_ERROR_INTERNAL; + + private static String[] SYNC_ERROR_NAMES; + + public static int SYNC_OBSERVER_TYPE_SETTINGS; + public static int SYNC_OBSERVER_TYPE_PENDING; + public static int SYNC_OBSERVER_TYPE_ACTIVE; + + public static int SYNC_OBSERVER_TYPE_STATUS; + + public static int SYNC_OBSERVER_TYPE_ALL; + + private static boolean ENABLE_CONTENT_SAMPLE; + private static int SLOW_THRESHOLD_MILLIS; + private Random mRandom; + + public ContentResolver(Context context) { + mContext = context; + } + + public static String CONTENT_SERVICE_NAME; + + private static IContentService sContentService; + private final Context mContext; + String mPackageName; + private static String TAG; + + public final Cursor query(Uri uri, String[] projection, + String selection, String[] selectionArgs, String sortOrder) { + if (InferUndefined.boolean_undefined()) { + return null; + } else { + return query(uri, projection, selection, selectionArgs, sortOrder, null); + } + } + + public final Cursor query(final Uri uri, String[] projection, + String selection, String[] selectionArgs, String sortOrder, + CancellationSignal cancellationSignal) { + if (InferUndefined.boolean_undefined()) { + return null; + } else { + return new SQLiteCursor(null, null, null); + } + } + + public final ContentProviderClient acquireContentProviderClient(Uri uri) { + return new ContentProviderClient(this, null, true); + } + + public final ContentProviderClient acquireContentProviderClient(String name) { + return new ContentProviderClient(this, null, true); + } +} diff --git a/infer/models/java/src/android/content/Context.java b/infer/models/java/src/android/content/Context.java new file mode 100644 index 000000000..71fcd3e38 --- /dev/null +++ b/infer/models/java/src/android/content/Context.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 android.content; + +import android.content.res.TypedArray; +import android.util.AttributeSet; +import com.facebook.infer.models.InferUndefined; + +public class Context { + + public static int MODE_PRIVATE; + + public static int MODE_WORLD_READABLE; + + public static int MODE_WORLD_WRITEABLE; + + public static int MODE_APPEND; + + public static int MODE_MULTI_PROCESS; + + public static int MODE_ENABLE_WRITE_AHEAD_LOGGING; + + public static int BIND_AUTO_CREATE; + + public static int BIND_DEBUG_UNBIND; + + public static int BIND_NOT_FOREGROUND; + + public static int BIND_ABOVE_CLIENT; + + public static int BIND_ALLOW_OOM_MANAGEMENT; + + public static int BIND_WAIVE_PRIORITY; + + public static int BIND_IMPORTANT; + + public static int BIND_ADJUST_WITH_ACTIVITY; + + public static int BIND_VISIBLE; + + public static int BIND_SHOWING_UI; + + public static int BIND_NOT_VISIBLE; + + public ContentResolver getContentResolver() { + return new ContentResolver(this); + } + + public TypedArray obtainStyledAttributes(int[] attrs) { + return new TypedArray(null, attrs, attrs, 1); + } + + public TypedArray obtainStyledAttributes(int resid, int[] attrs) + throws NotFoundException { + if (InferUndefined.boolean_undefined()) { + throw new NotFoundException(); + } + return new TypedArray(null, attrs, attrs, 1); + } + + public TypedArray obtainStyledAttributes(AttributeSet set, + int[] attrs, int defStyleAttr, int defStyleRes) { + return new TypedArray(null, attrs, attrs, 1); + } + + public static class NotFoundException extends RuntimeException { + public NotFoundException() { + } + + public NotFoundException(String name) { + super(name); + } + } + + public TypedArray obtainAttributes(AttributeSet set, int[] attrs) { + return new TypedArray(null, attrs, attrs, 1); + } + + +} diff --git a/infer/models/java/src/android/content/IContentProvider.java b/infer/models/java/src/android/content/IContentProvider.java new file mode 100644 index 000000000..878f4e565 --- /dev/null +++ b/infer/models/java/src/android/content/IContentProvider.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 android.content; + +public interface IContentProvider { +} diff --git a/infer/models/java/src/android/content/IContentService.java b/infer/models/java/src/android/content/IContentService.java new file mode 100644 index 000000000..b3e9300d9 --- /dev/null +++ b/infer/models/java/src/android/content/IContentService.java @@ -0,0 +1,5 @@ +package android.content; + + +public interface IContentService { +} diff --git a/infer/models/java/src/android/content/res/Resources.java b/infer/models/java/src/android/content/res/Resources.java new file mode 100644 index 000000000..377f99951 --- /dev/null +++ b/infer/models/java/src/android/content/res/Resources.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 android.content.res; + +import android.graphics.drawable.Drawable; +import android.os.IBinder; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.util.LongSparseArray; +import android.util.TypedValue; +import com.facebook.infer.models.InferUndefined; +import libcore.icu.NativePluralRules; + +import java.lang.ref.WeakReference; + +public class Resources { + static String TAG; + private static boolean DEBUG_LOAD; + private static boolean DEBUG_CONFIG; + private static boolean DEBUG_ATTRIBUTES_CACHE; + private static boolean TRACE_FOR_PRELOAD; + private static boolean TRACE_FOR_MISS_PRELOAD; + + private static int ID_OTHER; + + private static Object sSync; + + private static LongSparseArray[] sPreloadedDrawables; + private static LongSparseArray sPreloadedColorDrawables; + private static LongSparseArray sPreloadedColorStateLists; + + private static boolean sPreloaded; + private static int sPreloadedDensity; + + Object mAccessLock; + Configuration mTmpConfig; + TypedValue mTmpValue; + LongSparseArray> mDrawableCache; + LongSparseArray> mColorStateListCache; + LongSparseArray> mColorDrawableCache; + boolean mPreloading; + + TypedArray mCachedStyledAttributes; + RuntimeException mLastRetrievedAttrs; + + private int mLastCachedXmlBlockIndex; + private int[] mCachedXmlBlockIds; + private XmlBlock[] mCachedXmlBlocks; + + AssetManager mAssets; + private Configuration mConfiguration; + DisplayMetrics mMetrics; + private NativePluralRules mPluralRule; + + private CompatibilityInfo mCompatibilityInfo; + private WeakReference mToken; + + public final class Theme { + public TypedArray obtainStyledAttributes(int[] attrs) { + return new TypedArray(null, attrs, attrs, 1); + } + + public TypedArray obtainStyledAttributes(int resid, int[] attrs) + throws NotFoundException { + if (InferUndefined.boolean_undefined()) { + throw new NotFoundException(); + } + return new TypedArray(null, attrs, attrs, 1); + } + + public TypedArray obtainStyledAttributes(AttributeSet set, + int[] attrs, int defStyleAttr, int defStyleRes) { + return new TypedArray(null, attrs, attrs, 1); + } + + } + + public static class NotFoundException extends RuntimeException { + public NotFoundException() { + } + + public NotFoundException(String name) { + super(name); + } + } + + public TypedArray obtainAttributes(AttributeSet set, int[] attrs) { + return new TypedArray(null, attrs, attrs, 1); + } + + static private int LAYOUT_DIR_CONFIG; + +} \ No newline at end of file diff --git a/infer/models/java/src/android/content/res/TypedArray.java b/infer/models/java/src/android/content/res/TypedArray.java new file mode 100644 index 000000000..de869103b --- /dev/null +++ b/infer/models/java/src/android/content/res/TypedArray.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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 android.content.res; + +import android.util.TypedValue; +import com.facebook.infer.models.InferBuiltins; + +public class TypedArray { + + private Resources mResources; + int[] mData; + int[] mIndices; + int mLength; + TypedValue mValue = new TypedValue(); + + public void recycle() { + // Release resource + if (mLength > 0) { + InferBuiltins.__set_mem_attribute(this.mValue); + } + } + + public TypedArray(Resources resources, int[] data, int[] indices, int len) { + mResources = resources; + mData = data; + mIndices = indices; + mLength = len; + + // Acquire resource + if (mLength > 0) { + InferBuiltins.__set_file_attribute(this.mValue); + } + } + +} diff --git a/infer/models/java/src/android/database/AbstractCursor.java b/infer/models/java/src/android/database/AbstractCursor.java new file mode 100644 index 000000000..1f6aec798 --- /dev/null +++ b/infer/models/java/src/android/database/AbstractCursor.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 android.database; + +public abstract class AbstractCursor extends CrossProcessCursor { +} \ No newline at end of file diff --git a/infer/models/java/src/android/database/CrossProcessCursor.java b/infer/models/java/src/android/database/CrossProcessCursor.java new file mode 100644 index 000000000..3dfa8742e --- /dev/null +++ b/infer/models/java/src/android/database/CrossProcessCursor.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 android.database; + +public class CrossProcessCursor extends Cursor { +} \ No newline at end of file diff --git a/infer/models/java/src/android/database/CrossProcessCursorWrapper.java b/infer/models/java/src/android/database/CrossProcessCursorWrapper.java new file mode 100644 index 000000000..a6bd1d588 --- /dev/null +++ b/infer/models/java/src/android/database/CrossProcessCursorWrapper.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 android.database; + +public abstract class CrossProcessCursorWrapper extends CrossProcessCursor { +} \ No newline at end of file diff --git a/infer/models/java/src/android/database/Cursor.java b/infer/models/java/src/android/database/Cursor.java new file mode 100644 index 000000000..ae5396fe1 --- /dev/null +++ b/infer/models/java/src/android/database/Cursor.java @@ -0,0 +1,19 @@ +/* +* Copyright (c) 2009-2013 Monoidics ltd. +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package android.database; + +import android.database.sqlite.SQLiteCursor; + +public class Cursor { + + public void close() { + if (this instanceof SQLiteCursor) { + ((SQLiteCursor) this).close(); + } + } + +} diff --git a/infer/models/java/src/android/database/CursorWrapper.java b/infer/models/java/src/android/database/CursorWrapper.java new file mode 100644 index 000000000..ede85e8ff --- /dev/null +++ b/infer/models/java/src/android/database/CursorWrapper.java @@ -0,0 +1,20 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package android.database; + +import android.database.sqlite.SQLiteCursor; + +public class CursorWrapper extends Cursor { + protected final Cursor mCursor; + + public CursorWrapper(Cursor cursor) { + mCursor = cursor; + } + + public void close() { + mCursor.close(); + } +} diff --git a/infer/models/java/src/android/database/sqlite/SQLiteConnectionPool.java b/infer/models/java/src/android/database/sqlite/SQLiteConnectionPool.java new file mode 100644 index 000000000..234b46282 --- /dev/null +++ b/infer/models/java/src/android/database/sqlite/SQLiteConnectionPool.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * 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 android.database.sqlite; + +public final class SQLiteConnectionPool { + +} diff --git a/infer/models/java/src/android/database/sqlite/SQLiteCursor.java b/infer/models/java/src/android/database/sqlite/SQLiteCursor.java new file mode 100644 index 000000000..2a86e3909 --- /dev/null +++ b/infer/models/java/src/android/database/sqlite/SQLiteCursor.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 android.database.sqlite; + +import android.database.Cursor; +import com.facebook.infer.models.InferBuiltins; +import com.facebook.infer.models.InferUndefined; + +import java.util.Map; + + +public class SQLiteCursor extends Cursor { + static final String TAG = "SQLiteCursor"; + static int NO_COUNT; + + private String mEditTable; + + private String[] mColumns; + + private SQLiteQuery mQuery; + + private SQLiteCursorDriver mDriver; + + private int mCount = NO_COUNT; + + private int mCursorWindowCapacity; + + private Map mColumnNameMap; + + private Throwable mStackTrace; + + @Deprecated + public SQLiteCursor(SQLiteDatabase db, SQLiteCursorDriver driver, + String editTable, SQLiteQuery query) { + this(driver, editTable, query); + } + + + public SQLiteCursor(SQLiteCursorDriver driver, String editTable, SQLiteQuery query) { + mEditTable = new String(); + InferBuiltins.__set_file_attribute(mEditTable); + } + + + public int getCount() { + return InferUndefined.int_undefined(); + } + + + public String[] getColumnNames() { + return new String[0]; + } + + public void close() { + InferBuiltins.__set_mem_attribute(mEditTable); + } +} diff --git a/infer/models/java/src/android/database/sqlite/SQLiteDatabase.java b/infer/models/java/src/android/database/sqlite/SQLiteDatabase.java new file mode 100644 index 000000000..e53697374 --- /dev/null +++ b/infer/models/java/src/android/database/sqlite/SQLiteDatabase.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 android.database.sqlite; + +import android.database.Cursor; +import android.database.DatabaseErrorHandler; +import android.os.CancellationSignal; + +import com.facebook.infer.models.InferUndefined; + +import dalvik.system.CloseGuard; + +import java.util.WeakHashMap; + + +public final class SQLiteDatabase { + + private SQLiteDatabase(String path, int openFlags, CursorFactory cursorFactory, + DatabaseErrorHandler errorHandler) { + } + + + public Cursor query(boolean distinct, String table, String[] columns, + String selection, String[] selectionArgs, String groupBy, + String having, String orderBy, String limit) { + return queryWithFactory(null, distinct, table, columns, selection, selectionArgs, + groupBy, having, orderBy, limit, null); + } + + + public Cursor query(boolean distinct, String table, String[] columns, + String selection, String[] selectionArgs, String groupBy, + String having, String orderBy, String limit, CancellationSignal cancellationSignal) { + return queryWithFactory(null, distinct, table, columns, selection, selectionArgs, + groupBy, having, orderBy, limit, cancellationSignal); + } + + + public Cursor queryWithFactory(CursorFactory cursorFactory, + boolean distinct, String table, String[] columns, + String selection, String[] selectionArgs, String groupBy, + String having, String orderBy, String limit) { + return queryWithFactory(cursorFactory, distinct, table, columns, selection, + selectionArgs, groupBy, having, orderBy, limit, null); + } + + + public Cursor queryWithFactory(CursorFactory cursorFactory, + boolean distinct, String table, String[] columns, + String selection, String[] selectionArgs, String groupBy, + String having, String orderBy, String limit, CancellationSignal cancellationSignal) { + return rawQueryWithFactory(cursorFactory, null, selectionArgs, table, cancellationSignal); + } + + public Cursor query(String table, String[] columns, String selection, + String[] selectionArgs, String groupBy, String having, + String orderBy) { + + return query(false, table, columns, selection, selectionArgs, groupBy, + having, orderBy, null /* limit */); + } + + public Cursor query(String table, String[] columns, String selection, + String[] selectionArgs, String groupBy, String having, + String orderBy, String limit) { + + return query(false, table, columns, selection, selectionArgs, groupBy, + having, orderBy, limit); + } + + public Cursor rawQuery(String sql, String[] selectionArgs) { + return rawQueryWithFactory(null, sql, selectionArgs, null, null); + } + + public Cursor rawQuery(String sql, String[] selectionArgs, + CancellationSignal cancellationSignal) { + return rawQueryWithFactory(null, sql, selectionArgs, null, cancellationSignal); + } + + public Cursor rawQueryWithFactory( + CursorFactory cursorFactory, String sql, String[] selectionArgs, + String editTable) { + return rawQueryWithFactory(cursorFactory, sql, selectionArgs, editTable, null); + } + + public Cursor rawQueryWithFactory( + CursorFactory cursorFactory, String sql, String[] selectionArgs, + String editTable, CancellationSignal cancellationSignal) { + return new SQLiteCursor(null, editTable, null); + } + + public interface CursorFactory { + public Cursor newCursor(SQLiteDatabase db, + SQLiteCursorDriver masterQuery, String editTable, + SQLiteQuery query); + } +} + diff --git a/infer/models/java/src/android/database/sqlite/SQLiteDatabaseConfiguration.java b/infer/models/java/src/android/database/sqlite/SQLiteDatabaseConfiguration.java new file mode 100644 index 000000000..b13f8e72f --- /dev/null +++ b/infer/models/java/src/android/database/sqlite/SQLiteDatabaseConfiguration.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * 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 android.database.sqlite; + +public final class SQLiteDatabaseConfiguration { +} diff --git a/infer/models/java/src/android/database/sqlite/SQLiteQueryBuilder.java b/infer/models/java/src/android/database/sqlite/SQLiteQueryBuilder.java new file mode 100644 index 000000000..8463fcddc --- /dev/null +++ b/infer/models/java/src/android/database/sqlite/SQLiteQueryBuilder.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 android.database.sqlite; + +import android.database.Cursor; +import android.os.CancellationSignal; + +import java.util.Map; +import java.util.regex.Pattern; + +public class SQLiteQueryBuilder { + private static String TAG; + private static Pattern sLimitPattern; + + private Map mProjectionMap; + private String mTables; + private StringBuilder mWhereClause; + private boolean mDistinct; + private SQLiteDatabase.CursorFactory mFactory; + private boolean mStrict; + + public SQLiteQueryBuilder() { + mDistinct = false; + mFactory = null; + } + + public Cursor query(SQLiteDatabase db, String[] projectionIn, + String selection, String[] selectionArgs, String groupBy, + String having, String sortOrder) { + return query(db, projectionIn, selection, selectionArgs, groupBy, having, sortOrder, + null /* limit */, null /* cancellationSignal */); + } + + public Cursor query(SQLiteDatabase db, String[] projectionIn, + String selection, String[] selectionArgs, String groupBy, + String having, String sortOrder, String limit) { + return query(db, projectionIn, selection, selectionArgs, + groupBy, having, sortOrder, limit, null); + } + + public Cursor query(SQLiteDatabase db, String[] projectionIn, + String selection, String[] selectionArgs, String groupBy, + String having, String sortOrder, String limit, + CancellationSignal cancellationSignal) { + return new SQLiteCursor(null, null, null); + } + + +} diff --git a/infer/models/java/src/android/provider/MediaStore.java b/infer/models/java/src/android/provider/MediaStore.java new file mode 100644 index 000000000..68df911b4 --- /dev/null +++ b/infer/models/java/src/android/provider/MediaStore.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * 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 android.provider; + +import android.content.ContentResolver; +import android.database.Cursor; +import android.net.Uri; + +/** + * The Media provider contains meta data for all available media on both internal + * and external storage devices. + */ +public final class MediaStore { + private static String TAG; + + public static String AUTHORITY; + + private static String CONTENT_AUTHORITY_SLASH; + + public static String ACTION_MTP_SESSION_END; + + public static String UNHIDE_CALL; + + public static String PARAM_DELETE_DATA; + + public static String INTENT_ACTION_MUSIC_PLAYER; + + public static String INTENT_ACTION_MEDIA_SEARCH; + + public static String INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH; + + public static String INTENT_ACTION_TEXT_OPEN_FROM_SEARCH; + + public static String INTENT_ACTION_VIDEO_PLAY_FROM_SEARCH; + + public static String EXTRA_MEDIA_ARTIST; + + public static String EXTRA_MEDIA_ALBUM; + + public static String EXTRA_MEDIA_TITLE; + + public static String EXTRA_MEDIA_FOCUS; + + public static String EXTRA_SCREEN_ORIENTATION; + + public static String EXTRA_FULL_SCREEN; + + public static String EXTRA_SHOW_ACTION_ICONS; + + public static String EXTRA_FINISH_ON_COMPLETION; + + public static String INTENT_ACTION_STILL_IMAGE_CAMERA; + + public static String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE; + + public static String INTENT_ACTION_VIDEO_CAMERA; + + public static String ACTION_IMAGE_CAPTURE; + + public static String ACTION_IMAGE_CAPTURE_SECURE; + + public static String ACTION_VIDEO_CAPTURE; + + public static String EXTRA_VIDEO_QUALITY; + + public static String EXTRA_SIZE_LIMIT; + + public static String EXTRA_DURATION_LIMIT; + + public static String EXTRA_OUTPUT; + + public static String UNKNOWN_STRING; + + + public static final class Images { + + + public static final class Media { + public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) { + return cr.query(uri, projection, null, null, null); + } + + public static final Cursor query(ContentResolver cr, Uri uri, String[] projection, + String where, String orderBy) { + return cr.query(uri, projection, where, null, orderBy); + } + + public static final Cursor query(ContentResolver cr, Uri uri, String[] projection, + String selection, String[] selectionArgs, String orderBy) { + return cr.query(uri, projection, selection, selectionArgs, orderBy); + } + } + } +} diff --git a/infer/models/java/src/com/facebook/infer/models/InferArray.java b/infer/models/java/src/com/facebook/infer/models/InferArray.java new file mode 100644 index 000000000..4fa88ae58 --- /dev/null +++ b/infer/models/java/src/com/facebook/infer/models/InferArray.java @@ -0,0 +1,43 @@ +/* +* Copyright (c) 2009-2013 Monoidics ltd. +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package com.facebook.infer.models; + +public class InferArray { + + public static Object[] clone(Object[] arr) { + return new Object[arr.length]; + } + + public static int[] clone(int[] arr) { + return new int[arr.length]; + } + + public static short[] clone(short[] arr) { + return new short[arr.length]; + } + + public static long[] clone(long[] arr) { + return new long[arr.length]; + } + + public static boolean[] clone(boolean[] arr) { + return new boolean[arr.length]; + } + + public static char[] clone(char[] arr) { + return new char[arr.length]; + } + + public static float[] clone(float[] arr) { + return new float[arr.length]; + } + + public static double[] clone(double[] arr) { + return new double[arr.length]; + } + +} diff --git a/infer/models/java/src/com/facebook/infer/models/InferBuiltins.java b/infer/models/java/src/com/facebook/infer/models/InferBuiltins.java new file mode 100644 index 000000000..996901f52 --- /dev/null +++ b/infer/models/java/src/com/facebook/infer/models/InferBuiltins.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2009-2013 Monoidics ltd. + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package com.facebook.infer.models; + +public class InferBuiltins { + + public static void __set_file_attribute(Object o) { + } + + public static void __set_mem_attribute(Object o) { + } + + public static void __set_lock_attribute(Object o) { + } + + public static void _exit() { + } + + public static void __infer_assume(boolean condition) { + } + + public static String __split_get_nth(String s, String sep, int n) { + return null; + } + +} diff --git a/infer/models/java/src/com/facebook/infer/models/InferCloseables.java b/infer/models/java/src/com/facebook/infer/models/InferCloseables.java new file mode 100644 index 000000000..f7abdeb1c --- /dev/null +++ b/infer/models/java/src/com/facebook/infer/models/InferCloseables.java @@ -0,0 +1,35 @@ +package com.facebook.infer.models; + +import com.squareup.okhttp.internal.StrictLineReader; + +import java.io.*; + +public final class InferCloseables { + + private InferCloseables() { + } + + public static void close(Closeable closeable, boolean swallowIOException) + throws IOException { + if (closeable == null) return; + if (closeable instanceof InputStream) { + ((InputStream) closeable).close(); + } else if (closeable instanceof OutputStream) { + ((OutputStream) closeable).close(); + } else if (closeable instanceof Reader) { + ((Reader) closeable).close(); + } else if (closeable instanceof Writer) { + ((Writer) closeable).close(); + } else if (closeable instanceof StrictLineReader) { + ((StrictLineReader) closeable).close(); + } + } + + public static void closeQuietly(Closeable closeable) { + try { + close(closeable, true); + } catch (IOException e) { + } + } + +} diff --git a/infer/models/java/src/com/facebook/infer/models/InferUndefined.java b/infer/models/java/src/com/facebook/infer/models/InferUndefined.java new file mode 100644 index 000000000..a8e7a672a --- /dev/null +++ b/infer/models/java/src/com/facebook/infer/models/InferUndefined.java @@ -0,0 +1,161 @@ +/* +* Copyright (c) 2009-2013 Monoidics ltd. +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package com.facebook.infer.models; + +import java.io.IOException; +import java.net.SocketException; +import java.sql.SQLException; + +public class InferUndefined { + + public static native boolean boolean_undefined(); + + public static native int int_undefined(); + + public static native long long_undefined(); + + public static native byte byte_undefined(); + + public static native void void_undefined(); + + public static native char char_undefined(); + + public static native short short_undefined(); + + public static native double double_undefined(); + + public static native float float_undefined(); + + public static native Object object_undefined(); + + public static native String string_undefined(); + + public static void can_throw_ioexception_void() throws IOException { + boolean undef = boolean_undefined(); + if (undef) { + } else + throw new IOException(); + } + + public static boolean can_throw_ioexception_boolean() throws IOException { + boolean undef = boolean_undefined(); + if (undef) { + return undef; + } else + throw new IOException(); + } + + public static int can_throw_ioexception_int() throws IOException { + boolean undef = boolean_undefined(); + if (undef) { + return int_undefined(); + } else + throw new IOException(); + } + + public static long can_throw_ioexception_long() throws IOException { + boolean undef = boolean_undefined(); + if (undef) { + return long_undefined(); + } else + throw new IOException(); + } + + public static byte can_throw_ioexception_byte() throws IOException { + boolean undef = boolean_undefined(); + if (undef) { + return byte_undefined(); + } else + throw new IOException(); + } + + public static short can_throw_ioexception_short() throws IOException { + boolean undef = boolean_undefined(); + if (undef) { + return short_undefined(); + } else + throw new IOException(); + } + + public static float can_throw_ioexception_float() throws IOException { + boolean undef = boolean_undefined(); + if (undef) { + return float_undefined(); + } else + throw new IOException(); + } + + public static double can_throw_ioexception_double() throws IOException { + boolean undef = boolean_undefined(); + if (undef) { + return double_undefined(); + } else + throw new IOException(); + } + + public static char can_throw_ioexception_char() throws IOException { + boolean undef = boolean_undefined(); + if (undef) { + return char_undefined(); + } else + throw new IOException(); + } + + public static String can_throw_ioexception_string() throws IOException { + boolean undef = boolean_undefined(); + if (undef) { + return string_undefined(); + } else + throw new IOException(); + } + + public static Object can_throw_ioexception_object() throws IOException { + boolean undef = boolean_undefined(); + if (undef) { + return object_undefined(); + } else + throw new IOException(); + } + + public static void can_throw_sqlexception_void() throws SQLException { + boolean undef = boolean_undefined(); + if (undef) { + } else + throw new SQLException(); + } + + public static int nonneg_int() { + int res = int_undefined(); + if (res < 0) + while (true) { + } + return res; + } + + public static void can_throw_socketexception_void() throws SocketException { + boolean undef = boolean_undefined(); + if (undef) { + } else + throw new SocketException(); + } + + public static int can_throw_socketexception_int() throws SocketException { + boolean undef = boolean_undefined(); + if (undef) { + return int_undefined(); + } else + throw new SocketException(); + } + + public static Object can_throw_socketexception_object() throws SocketException { + boolean undef = boolean_undefined(); + if (undef) { + return object_undefined(); + } else + throw new SocketException(); + } +} diff --git a/infer/models/java/src/com/fasterxml/jackson/core/JsonFactory.java b/infer/models/java/src/com/fasterxml/jackson/core/JsonFactory.java new file mode 100644 index 000000000..2720f9db5 --- /dev/null +++ b/infer/models/java/src/com/fasterxml/jackson/core/JsonFactory.java @@ -0,0 +1,96 @@ +/* Jackson JSON-processor. + * + * Copyright (c) 2007- Tatu Saloranta, tatu.saloranta@iki.fi + */ +package com.fasterxml.jackson.core; + +import com.fasterxml.jackson.core.io.CharacterEscapes; +import com.fasterxml.jackson.core.io.InputDecorator; +import com.fasterxml.jackson.core.io.OutputDecorator; +import com.fasterxml.jackson.core.json.PackageVersion; +import com.fasterxml.jackson.core.json.UTF8StreamJsonParser; +import com.fasterxml.jackson.core.sym.BytesToNameCanonicalizer; +import com.fasterxml.jackson.core.sym.CharsToNameCanonicalizer; +import com.fasterxml.jackson.core.util.BufferRecycler; + +import java.io.*; +import java.lang.ref.SoftReference; +import java.net.URL; + +public class JsonFactory + implements Versioned, java.io.Serializable { + private static long serialVersionUID; + + public static String FORMAT_NAME_JSON; + + protected static int DEFAULT_FACTORY_FEATURE_FLAGS; + + protected static int DEFAULT_PARSER_FEATURE_FLAGS; + + protected static int DEFAULT_GENERATOR_FEATURE_FLAGS; + + private static SerializableString DEFAULT_ROOT_VALUE_SEPARATOR; + + protected static ThreadLocal> _recyclerRef; + + protected transient CharsToNameCanonicalizer _rootCharSymbols; + + protected transient BytesToNameCanonicalizer _rootByteSymbols; + + protected ObjectCodec _objectCodec; + + protected int _factoryFeatures; + + protected int _parserFeatures; + + protected int _generatorFeatures; + + protected CharacterEscapes _characterEscapes; + + protected InputDecorator _inputDecorator; + + protected OutputDecorator _outputDecorator; + + protected SerializableString _rootValueSeparator; + + @Override + public Version version() { + return PackageVersion.VERSION; + } + + public JsonParser createParser(File f) + throws IOException, JsonParseException { + return createOwningParser(); + } + + public JsonParser createParser(URL url) + throws IOException, JsonParseException { + return createOwningParser(); + } + + public JsonParser createParser(InputStream in) + throws IOException, JsonParseException { + return createNonOwningParser(); + } + + public JsonParser createParser(Reader r) + throws IOException, JsonParseException { + return createNonOwningParser(); + } + + private JsonParser createOwningParser() + throws IOException, JsonParseException { + InputStream in = new FileInputStream(""); + return new UTF8StreamJsonParser(null, 0, in, null, null, + new byte[]{}, 0, 0, + false); + } + + private JsonParser createNonOwningParser() + throws IOException, JsonParseException { + return new UTF8StreamJsonParser(null, 0, null, null, null, + new byte[]{}, 0, 0, + false); + } + +} diff --git a/infer/models/java/src/com/fasterxml/jackson/core/JsonParser.java b/infer/models/java/src/com/fasterxml/jackson/core/JsonParser.java new file mode 100644 index 000000000..be05e22c3 --- /dev/null +++ b/infer/models/java/src/com/fasterxml/jackson/core/JsonParser.java @@ -0,0 +1,47 @@ +/* Jackson JSON-processor. + * + * Copyright (c) 2007- Tatu Saloranta, tatu.saloranta@iki.fi + */ + +package com.fasterxml.jackson.core; + +import com.facebook.infer.models.InferUndefined; +import com.fasterxml.jackson.core.json.UTF8StreamJsonParser; + +import java.io.Closeable; +import java.io.IOException; + +public abstract class JsonParser + implements Closeable, Versioned { + private static int MIN_BYTE_I; + private static int MAX_BYTE_I; + + private static int MIN_SHORT_I; + private static int MAX_SHORT_I; + + protected int _features; + + public void close() throws IOException { + if (this instanceof UTF8StreamJsonParser) { + ((UTF8StreamJsonParser) this).close(); + } + } + + private void throwExceptions() + throws JsonParseException, IOException { + if (InferUndefined.boolean_undefined()) { + throw new JsonParseException(null, null, null); + } + if (InferUndefined.boolean_undefined()) { + throw new IOException(); + } + } + + public Object readValueAs(Class valueType) + throws IOException, JsonProcessingException { + throwExceptions(); + return InferUndefined.object_undefined(); + } + +} + diff --git a/infer/models/java/src/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java b/infer/models/java/src/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java new file mode 100644 index 000000000..72045f803 --- /dev/null +++ b/infer/models/java/src/com/fasterxml/jackson/core/json/UTF8StreamJsonParser.java @@ -0,0 +1,136 @@ +package com.fasterxml.jackson.core.json; + +import com.facebook.infer.models.InferUndefined; +import com.fasterxml.jackson.core.Base64Variant; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.core.base.ParserBase; +import com.fasterxml.jackson.core.io.IOContext; +import com.fasterxml.jackson.core.sym.BytesToNameCanonicalizer; + +import java.io.IOException; +import java.io.InputStream; + +/* + * Fake UTF8StreamJsonParser + * This class contains a minimum set of methods in order to compile it for the + * models + */ +public final class UTF8StreamJsonParser + extends ParserBase { + + + protected ObjectCodec _objectCodec; + + protected BytesToNameCanonicalizer _symbols; + + protected int[] _quadBuffer; + + protected boolean _tokenIncomplete; + + protected InputStream _inputStream; + + protected byte[] _inputBuffer; + + protected boolean _bufferRecyclable; + + public UTF8StreamJsonParser(IOContext ctxt, int features, InputStream in, + ObjectCodec codec, BytesToNameCanonicalizer sym, + byte[] inputBuffer, int start, int end, + boolean bufferRecyclable) { + super(ctxt, features); + _inputStream = in; + _objectCodec = codec; + _symbols = sym; + _inputBuffer = inputBuffer; + _inputPtr = start; + _inputEnd = end; + _bufferRecyclable = bufferRecyclable; + } + + @Override + public void close() throws IOException { + if (_inputStream != null) { + _inputStream.close(); + } + } + + private void throwExceptions() + throws JsonParseException, IOException { + if (InferUndefined.boolean_undefined()) { + throw new JsonParseException(null, null, null); + } + if (InferUndefined.boolean_undefined()) { + throw new IOException(); + } + } + + /* + * Methods from ParserBase + */ + + @Override + protected boolean loadMore() + throws IOException { + return InferUndefined.can_throw_ioexception_boolean(); + } + + @Override + protected void _finishString() + throws IOException, JsonParseException { + throwExceptions(); + } + + @Override + protected void _closeInput() throws IOException { + close(); + } + + /* + * Methods from ParserMinimalBase + */ + + @Override + public byte[] getBinaryValue(Base64Variant b64variant) + throws IOException, JsonParseException { + throwExceptions(); + return new byte[]{InferUndefined.byte_undefined()}; + } + + @Override + public int getTextOffset() + throws IOException, JsonParseException { + throwExceptions(); + return InferUndefined.int_undefined(); + } + + @Override + public int getTextLength() + throws IOException, JsonParseException { + throwExceptions(); + return InferUndefined.int_undefined(); + } + + @Override + public char[] getTextCharacters() + throws IOException, JsonParseException { + throwExceptions(); + return new char[]{InferUndefined.char_undefined()}; + } + + @Override + public String getText() + throws IOException, JsonParseException { + throwExceptions(); + return InferUndefined.string_undefined(); + } + + @Override + public JsonToken nextToken() + throws IOException, JsonParseException { + throwExceptions(); + throw new IOException(); + } + +} diff --git a/infer/models/java/src/com/google/common/base/Preconditions.java b/infer/models/java/src/com/google/common/base/Preconditions.java new file mode 100644 index 000000000..995cbe520 --- /dev/null +++ b/infer/models/java/src/com/google/common/base/Preconditions.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * 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.common.base; + +import javax.annotation.Nullable; + + +public final class Preconditions { + + public static T checkNotNull(T reference) { + if (reference == null) { + throw new NullPointerException(); + } + return reference; + } + + public static T checkNotNull(T reference, @Nullable Object errorMessage) { + if (reference == null) { + throw new NullPointerException(); + } + return reference; + } + + public static T checkNotNull(T reference, + @Nullable String errorMessageTemplate, + @Nullable Object... errorMessageArgs) { + if (reference == null) { + // If either of these parameters is null, the right thing happens anyway + throw new NullPointerException(errorMessageTemplate); + } + return reference; + } + + public static void checkState(boolean expression) { + if (!expression) { + throw new IllegalStateException(); + } + } + + public static void checkState(boolean expression, + @Nullable Object errorMessage) { + if (!expression) { + throw new IllegalStateException(); + } + } + + public static void checkState(boolean expression, + @Nullable String errorMessageTemplate, + @Nullable Object... errorMessageArgs) { + if (!expression) { + throw new IllegalStateException(errorMessageTemplate); + } + } + +} diff --git a/infer/models/java/src/com/google/common/collect/ImmutableList.java b/infer/models/java/src/com/google/common/collect/ImmutableList.java new file mode 100644 index 000000000..289bb69ec --- /dev/null +++ b/infer/models/java/src/com/google/common/collect/ImmutableList.java @@ -0,0 +1,13 @@ +package com.google.common.collect; + +public class ImmutableList { + + private static final ImmutableList EMPTY = new ImmutableList(); + + @SuppressWarnings("unchecked") + public static ImmutableList of() { + while (EMPTY == null) {} + return (ImmutableList) EMPTY; + } + +} diff --git a/infer/models/java/src/com/google/common/collect/Iterators.java b/infer/models/java/src/com/google/common/collect/Iterators.java new file mode 100644 index 000000000..6a57778de --- /dev/null +++ b/infer/models/java/src/com/google/common/collect/Iterators.java @@ -0,0 +1,47 @@ +package com.google.common.collect; + +import java.util.NoSuchElementException; + +import javax.annotation.Nullable; + +public class Iterators { + + static final UnmodifiableListIterator EMPTY_LIST_ITERATOR = null; + + public static void FakeMethod() { + Object o1 = new Object() {}; + Object o2 = new Object() {}; + Object o3 = new Object() {}; + Object o4 = new Object() {}; + Object o5 = new Object() {}; + Object o6 = new Object() {}; + Object o7 = new Object() {}; + Object o8 = new Object() {}; + Object o9 = new Object() {}; + Object o10 = new Object() {}; + Object o11 = new Object() {}; + } + + public static UnmodifiableIterator singletonIterator(@Nullable final T value) { + return new UnmodifiableIterator() { + boolean done; + + @Override + public boolean hasNext() { + return !done; + } + + @Override + public T next() { + while (value == null) {} + if (done) { + throw new NoSuchElementException(); + } + done = true; + return value; +// return null; + } + }; + } + +} diff --git a/infer/models/java/src/com/google/common/io/Closeables.java b/infer/models/java/src/com/google/common/io/Closeables.java new file mode 100644 index 000000000..3de92d286 --- /dev/null +++ b/infer/models/java/src/com/google/common/io/Closeables.java @@ -0,0 +1,21 @@ +package com.google.common.io; + +import com.facebook.infer.models.InferCloseables; +import com.facebook.infer.models.InferUndefined; + +import java.io.Closeable; +import java.io.IOException; + +public final class Closeables { + + public static void close(Closeable closeable, boolean swallowIOException) throws IOException { + InferCloseables.close(closeable, swallowIOException); + if (!swallowIOException) + InferUndefined.can_throw_ioexception_void(); + } + + public static void closeQuietly(Closeable closeable) { + InferCloseables.closeQuietly(closeable); + } + +} \ No newline at end of file diff --git a/infer/models/java/src/com/squareup/okhttp/internal/StrictLineReader.java b/infer/models/java/src/com/squareup/okhttp/internal/StrictLineReader.java new file mode 100644 index 000000000..cdd0e9916 --- /dev/null +++ b/infer/models/java/src/com/squareup/okhttp/internal/StrictLineReader.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * 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.squareup.okhttp.internal; + +import com.facebook.infer.models.InferUndefined; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; + +public class StrictLineReader implements Closeable { + + private InputStream in; + private Charset charset; + private byte[] buf; + + public StrictLineReader(InputStream in, Charset charset) { + this(in, 8192, charset); + } + + public StrictLineReader(InputStream in, int capacity, Charset charset) { + if (in == null) { + throw new NullPointerException(); + } + if (capacity < 0) { + throw new IllegalArgumentException("capacity <= 0"); + } + + this.in = in; + this.charset = charset; + buf = new byte[capacity]; + } + + public void close() throws IOException { + in.close(); + InferUndefined.can_throw_ioexception_void(); + } + + public String readLine() throws IOException { + return InferUndefined.can_throw_ioexception_string(); + } + + + public int readInt() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + +} + diff --git a/infer/models/java/src/com/squareup/okhttp/internal/Util.java b/infer/models/java/src/com/squareup/okhttp/internal/Util.java new file mode 100644 index 000000000..e933d4176 --- /dev/null +++ b/infer/models/java/src/com/squareup/okhttp/internal/Util.java @@ -0,0 +1,18 @@ +package com.squareup.okhttp.internal; + +import com.facebook.infer.models.InferCloseables; + +import java.io.Closeable; +import java.io.IOException; +import java.nio.charset.Charset; + + +public class Util { + + public static final Charset US_ASCII = Charset.forName("US-ASCII"); + + public static void closeQuietly(Closeable closeable) throws IOException { + InferCloseables.closeQuietly(closeable); + } + +} diff --git a/infer/models/java/src/dalvik/system/CloseGuard.java b/infer/models/java/src/dalvik/system/CloseGuard.java new file mode 100644 index 000000000..c9c5308d2 --- /dev/null +++ b/infer/models/java/src/dalvik/system/CloseGuard.java @@ -0,0 +1,16 @@ +package dalvik.system; + +public class CloseGuard { + + + private static CloseGuard NOOP; + + private static volatile boolean ENABLED; + + private static volatile Reporter REPORTER; + + + public static interface Reporter { + public void report(String message, Throwable allocationSite); + } +} diff --git a/infer/models/java/src/java/io/BufferedInputStream.java b/infer/models/java/src/java/io/BufferedInputStream.java new file mode 100644 index 000000000..787033784 --- /dev/null +++ b/infer/models/java/src/java/io/BufferedInputStream.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 1994, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.io; + +import com.facebook.infer.models.InferUndefined; + +public class BufferedInputStream extends FilterInputStream { + + public BufferedInputStream(InputStream in) { + super(in); + } + + public BufferedInputStream(InputStream in, int size) { + super(in); + } + + public int available() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public void close() throws IOException { + if (in != null) { + in.close(); + } + } + + public int read() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(byte b[]) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(byte b[], int off, int len) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public void reset() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public long skip(long n) throws IOException { + return InferUndefined.can_throw_ioexception_long(); + } +} diff --git a/infer/models/java/src/java/io/BufferedOutputStream.java b/infer/models/java/src/java/io/BufferedOutputStream.java new file mode 100644 index 000000000..46eba2ebf --- /dev/null +++ b/infer/models/java/src/java/io/BufferedOutputStream.java @@ -0,0 +1,37 @@ +package java.io; + +import com.facebook.infer.models.InferUndefined; + +public class BufferedOutputStream extends FilterOutputStream { + + public BufferedOutputStream(OutputStream out) { + super(out); + } + + public BufferedOutputStream(OutputStream out, int size) { + super(out); + } + + public void close() throws IOException { + if (out != null) { + out.close(); + } + } + + public void flush() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(int b) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(byte b[], int off, int len) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(byte b[]) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + +} diff --git a/infer/models/java/src/java/io/BufferedReader.java b/infer/models/java/src/java/io/BufferedReader.java new file mode 100644 index 000000000..1adadc627 --- /dev/null +++ b/infer/models/java/src/java/io/BufferedReader.java @@ -0,0 +1,58 @@ +package java.io; + +import com.facebook.infer.models.InferUndefined; + +public class BufferedReader extends Reader { + + private Reader in; + private char[] buf; + private int pos; + private int end; + private int mark; + private int markLimit; + private boolean lastWasCR; + private boolean markedLastWasCR; + + + public BufferedReader(Reader in, int sz) { + this.in = in; + } + + public BufferedReader(Reader in) { + this.in = in; + } + + public void close() throws IOException { + if (in instanceof InputStreamReader) + ((InputStreamReader) in).close(); + } + + public int read() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(char cbuf[]) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(char[] cbuf, int off, int len) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public String readLine() throws IOException { + return InferUndefined.can_throw_ioexception_string(); + } + + public boolean ready() throws IOException { + return InferUndefined.can_throw_ioexception_boolean(); + } + + public void reset() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public long skip(long n) throws IOException { + return InferUndefined.can_throw_ioexception_long(); + } + +} diff --git a/infer/models/java/src/java/io/BufferedWriter.java b/infer/models/java/src/java/io/BufferedWriter.java new file mode 100644 index 000000000..483bc3a85 --- /dev/null +++ b/infer/models/java/src/java/io/BufferedWriter.java @@ -0,0 +1,56 @@ +package java.io; + +import com.facebook.infer.models.InferUndefined; + +public class BufferedWriter extends Writer { + + private Writer out; + + private char[] buf; + + private int pos; + + public BufferedWriter(Writer out) { + this.out = out; + } + + public BufferedWriter(Writer out, int sz) { + this.out = out; + } + + public void flush() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void newLine() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(char cbuf[]) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(char cbuf[], int off, int len) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(int c) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(String str) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(String str, int off, int len) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void close() throws IOException { + if (out instanceof OutputStreamWriter) { + ((OutputStreamWriter) out).close(); + } + } + + +} diff --git a/infer/models/java/src/java/io/DataInputStream.java b/infer/models/java/src/java/io/DataInputStream.java new file mode 100644 index 000000000..2b0ab9aa1 --- /dev/null +++ b/infer/models/java/src/java/io/DataInputStream.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 1994, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.io; + +import com.facebook.infer.models.InferUndefined; + +public class DataInputStream extends FilterInputStream { + + private byte[] scratch; + + public DataInputStream(InputStream in) { + super(in); + } + + public int read(byte b[]) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(byte b[], int off, int len) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public final boolean readBoolean() throws IOException { + return InferUndefined.can_throw_ioexception_boolean(); + } + + public final byte readByte() throws IOException { + return InferUndefined.can_throw_ioexception_byte(); + } + + public final char readChar() throws IOException { + return InferUndefined.can_throw_ioexception_char(); + } + + public final double readDouble() throws IOException { + return InferUndefined.can_throw_ioexception_double(); + } + + public final float readFloat() throws IOException { + return InferUndefined.can_throw_ioexception_float(); + } + + public final void readFully(byte b[]) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public final void readFully(byte b[], int off, int len) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public final int readInt() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public final long readLong() throws IOException { + return InferUndefined.can_throw_ioexception_long(); + } + + public final short readShort() throws IOException { + return InferUndefined.can_throw_ioexception_short(); + } + + public final int readUnsignedByte() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public final int readUnsignedShort() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public final String readUTF() throws IOException { + return InferUndefined.can_throw_ioexception_string(); + } + + public static final String readUTF(DataInput in) throws IOException { + return InferUndefined.can_throw_ioexception_string(); + } + + public final int skipBytes(int n) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + +} diff --git a/infer/models/java/src/java/io/DataOutputStream.java b/infer/models/java/src/java/io/DataOutputStream.java new file mode 100644 index 000000000..52372a0f1 --- /dev/null +++ b/infer/models/java/src/java/io/DataOutputStream.java @@ -0,0 +1,74 @@ +package java.io; + +import com.facebook.infer.models.InferUndefined; + +public class DataOutputStream extends FilterOutputStream { + + private byte[] scratch; + protected int written; + + public DataOutputStream(OutputStream out) { + super(out); + } + + public void flush() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(byte b[], int off, int len) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(byte b[]) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(int b) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public final void writeBoolean(boolean v) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public final void writeByte(int v) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public final void writeBytes(String s) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public final void writeChar(int v) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public final void writeChars(String s) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public final void writeDouble(double v) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public final void writeFloat(float v) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public final void writeInt(int v) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public final void writeLong(long v) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public final void writeShort(int v) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public final void writeUTF(String str) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + +} diff --git a/infer/models/java/src/java/io/FileInputStream.java b/infer/models/java/src/java/io/FileInputStream.java new file mode 100644 index 000000000..a0a6bc572 --- /dev/null +++ b/infer/models/java/src/java/io/FileInputStream.java @@ -0,0 +1,72 @@ +package java.io; + +import com.facebook.infer.models.InferBuiltins; +import com.facebook.infer.models.InferUndefined; +import dalvik.system.CloseGuard; + +import java.nio.FileChannelImpl; +import java.nio.channels.FileChannel; + +public class FileInputStream extends InputStream { + + private FileDescriptor fd; + private FileChannel channel; + + private void init() { + InferBuiltins.__set_file_attribute(this); + } + + public FileInputStream(String name) throws FileNotFoundException { + if (InferUndefined.boolean_undefined()) { + init(); + } else { + throw new FileNotFoundException(); + } + } + + public FileInputStream(File file) throws FileNotFoundException { + if (InferUndefined.boolean_undefined()) { + init(); + } else { + throw new FileNotFoundException(); + } + } + + public FileInputStream(FileDescriptor fdObj) { + init(); + } + + public FileChannel getChannel() { + channel = new FileChannelImpl(this, fd, InferUndefined.int_undefined()); + return channel; + } + + public int available() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public void close() throws IOException { + InferBuiltins.__set_mem_attribute(this); + InferUndefined.can_throw_ioexception_void(); + } + + @Override + public int read() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + @Override + public int read(byte b[]) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + @Override + public int read(byte b[], int off, int len) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public long skip(int n) throws IOException { + return InferUndefined.can_throw_ioexception_long(); + } + +} diff --git a/infer/models/java/src/java/io/FileOutputStream.java b/infer/models/java/src/java/io/FileOutputStream.java new file mode 100644 index 000000000..d1e6eb991 --- /dev/null +++ b/infer/models/java/src/java/io/FileOutputStream.java @@ -0,0 +1,85 @@ +package java.io; + +import com.facebook.infer.models.InferBuiltins; +import com.facebook.infer.models.InferUndefined; +import dalvik.system.CloseGuard; + +import java.nio.FileChannelImpl; +import java.nio.channels.FileChannel; + +public class FileOutputStream extends OutputStream { + + private FileDescriptor fd; + private boolean shouldClose; + + private FileChannel channel; + + private int mode; + + private CloseGuard guard; + + private void init() { + this.guard = new CloseGuard(); + InferBuiltins.__set_file_attribute(this.guard); + } + + public FileOutputStream(String name) throws FileNotFoundException { + if (InferUndefined.boolean_undefined()) { + init(); + } else { + throw new FileNotFoundException(); + } + } + + public FileOutputStream(String name, boolean append) throws FileNotFoundException { + if (InferUndefined.boolean_undefined()) { + init(); + } else { + throw new FileNotFoundException(); + } + } + + public FileOutputStream(File file) throws FileNotFoundException { + if (InferUndefined.boolean_undefined()) { + init(); + } else { + throw new FileNotFoundException(); + } + } + + public FileOutputStream(File file, boolean append) + throws FileNotFoundException { + if (InferUndefined.boolean_undefined()) { + init(); + } else { + throw new FileNotFoundException(); + } + } + + public FileOutputStream(FileDescriptor fdObj) { + init(); + } + + public FileChannel getChannel() { + channel = new FileChannelImpl(this, fd, InferUndefined.int_undefined()); + return channel; + } + + public void write(int b) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(byte b[]) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(byte b[], int off, int len) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void close() throws IOException { + InferBuiltins.__set_mem_attribute(this.guard); + InferUndefined.can_throw_ioexception_void(); + } + +} diff --git a/infer/models/java/src/java/io/FileReader.java b/infer/models/java/src/java/io/FileReader.java new file mode 100644 index 000000000..02636d0c8 --- /dev/null +++ b/infer/models/java/src/java/io/FileReader.java @@ -0,0 +1,17 @@ +package java.io; + +public class FileReader extends InputStreamReader { + + public FileReader(String fileName) throws FileNotFoundException { + super(new FileInputStream(fileName)); + } + + public FileReader(File file) throws FileNotFoundException { + super(new FileInputStream(file)); + } + + public FileReader(FileDescriptor fd) { + super(new FileInputStream(fd)); + } + +} diff --git a/infer/models/java/src/java/io/FileWriter.java b/infer/models/java/src/java/io/FileWriter.java new file mode 100644 index 000000000..406f54679 --- /dev/null +++ b/infer/models/java/src/java/io/FileWriter.java @@ -0,0 +1,26 @@ +package java.io; + +public class FileWriter extends OutputStreamWriter { + + public FileWriter(String fileName) throws IOException { + super(new FileOutputStream(fileName)); + } + + public FileWriter(String fileName, boolean append) throws IOException { + super(new FileOutputStream(fileName, append)); + } + + public FileWriter(File file) throws IOException { + super(new FileOutputStream(file)); + } + + public FileWriter(File file, boolean append) throws IOException { + super(new FileOutputStream(file, append)); + } + + public FileWriter(FileDescriptor fd) { + super(new FileOutputStream(fd)); + } + + +} diff --git a/infer/models/java/src/java/io/FilterInputStream.java b/infer/models/java/src/java/io/FilterInputStream.java new file mode 100644 index 000000000..73b7510ef --- /dev/null +++ b/infer/models/java/src/java/io/FilterInputStream.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 1994, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.io; + +import com.facebook.infer.models.InferUndefined; + +public class FilterInputStream extends InputStream { + + protected volatile InputStream in; + + protected FilterInputStream(InputStream in) { + this.in = in; + } + + public FilterInputStream() { + super(); + } + + public int available() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public void close() throws IOException { + if (in != null) + if (in instanceof FileInputStream) + ((FileInputStream) in).close(); + } + + public int read() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(byte b[]) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(byte b[], int off, int len) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public void reset() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + + public long skip(long n) throws IOException { + return InferUndefined.can_throw_ioexception_long(); + } + + +} diff --git a/infer/models/java/src/java/io/FilterOutputStream.java b/infer/models/java/src/java/io/FilterOutputStream.java new file mode 100644 index 000000000..e53c31ba5 --- /dev/null +++ b/infer/models/java/src/java/io/FilterOutputStream.java @@ -0,0 +1,41 @@ +package java.io; + +import com.facebook.infer.models.InferUndefined; + +public class FilterOutputStream extends OutputStream { + + protected OutputStream out; + + public FilterOutputStream() { + } + + public FilterOutputStream(OutputStream out) { + this.out = out; + } + + public void close() throws IOException { + if (out != null) { + if (out instanceof FileOutputStream) { + ((FileOutputStream) out).close(); + } + } + } + + public void flush() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(int b) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(byte b[]) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(byte b[], int off, int len) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + +} diff --git a/infer/models/java/src/java/io/FilterReader.java b/infer/models/java/src/java/io/FilterReader.java new file mode 100644 index 000000000..af217452d --- /dev/null +++ b/infer/models/java/src/java/io/FilterReader.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 1996, 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.io; + +import com.facebook.infer.models.InferUndefined; + +public abstract class FilterReader extends Reader { + + protected Reader in; + + protected FilterReader(Reader in) { + this.in = in; + } + + public void close() throws IOException { + if (in instanceof InputStreamReader) + ((InputStreamReader) in).close(); + } + + public int read() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(char cbuf[]) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(char cbuf[], int off, int len) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public boolean ready() throws IOException { + return InferUndefined.can_throw_ioexception_boolean(); + } + + public void reset() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public long skip(long n) throws IOException { + return InferUndefined.can_throw_ioexception_long(); + } + +} diff --git a/infer/models/java/src/java/io/InputStream.java b/infer/models/java/src/java/io/InputStream.java new file mode 100644 index 000000000..8f9fd7bdf --- /dev/null +++ b/infer/models/java/src/java/io/InputStream.java @@ -0,0 +1,29 @@ +package java.io; + +import com.facebook.infer.models.InferUndefined; + +public class InputStream implements Closeable { + + private int MAX_SKIP_BUFFER_SIZE; + + public void close() throws IOException { + if (this instanceof FileInputStream) { + ((FileInputStream) this).close(); + } else if (this instanceof FilterInputStream) { + ((FilterInputStream) this).close(); + } + } + + public int read() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(byte b[]) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(byte b[], int off, int len) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + +} diff --git a/infer/models/java/src/java/io/InputStreamReader.java b/infer/models/java/src/java/io/InputStreamReader.java new file mode 100644 index 000000000..ecae7cbc7 --- /dev/null +++ b/infer/models/java/src/java/io/InputStreamReader.java @@ -0,0 +1,62 @@ +package java.io; + +import com.facebook.infer.models.InferUndefined; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; + +public class InputStreamReader extends Reader { + + private InputStream in; + private boolean endOfInput; + private CharsetDecoder decoder; + private ByteBuffer bytes; + + public InputStreamReader(InputStream in) { + this.in = in; + } + + public InputStreamReader(InputStream in, String charsetName) + throws UnsupportedEncodingException { + if (charsetName == null) + throw new NullPointerException("charsetName"); + else if (charsetName == "UTF8" || charsetName == "UTF-8" + || charsetName == "US-ASCII" || charsetName == "ISO-8859-1" + || charsetName == "UTF-16BE" || charsetName == "UTF-16LE" + || charsetName == "UTF-16") { + this.in = in; + } else + throw new UnsupportedEncodingException(); + } + + public InputStreamReader(InputStream in, Charset cs) { + this.in = in; + } + + public InputStreamReader(InputStream in, CharsetDecoder dec) { + this.in = in; + } + + public void close() throws IOException { + in.close(); + } + + public int read() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(char cbuf[]) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(char[] cbuf, int off, int len) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public boolean ready() throws IOException { + return InferUndefined.can_throw_ioexception_boolean(); + } + + +} diff --git a/infer/models/java/src/java/io/ObjectInputStream.java b/infer/models/java/src/java/io/ObjectInputStream.java new file mode 100644 index 000000000..cc4f690a3 --- /dev/null +++ b/infer/models/java/src/java/io/ObjectInputStream.java @@ -0,0 +1,146 @@ +package java.io; + +import com.facebook.infer.models.InferUndefined; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class ObjectInputStream extends InputStream { + + private InputStream emptyStream; + private static Object UNSHARED_OBJ; + private boolean hasPushbackTC; + private byte pushbackTC; + private int nestedLevels; + private int nextHandle; + private DataInputStream input; + + private DataInputStream primitiveTypes; + private InputStream primitiveData; + private boolean enableResolve; + private ArrayList objectsRead; + private Object currentObject; + private ObjectStreamClass currentClass; + private InputValidationDesc[] validations; + + private boolean subclassOverridingImplementation; + private ClassLoader callerClassLoader; + private boolean mustResolve; + private int descriptorHandle; + + private static HashMap> PRIMITIVE_CLASSES; + + static class InputValidationDesc { + ObjectInputValidation validator; + int priority; + } + + private static ClassLoader bootstrapLoader; + private static ClassLoader systemLoader; + + private HashMap, List>> cachedSuperclasses; + + public ObjectInputStream(InputStream in) throws IOException { + this.input = new DataInputStream(in); + InferUndefined.can_throw_ioexception_void(); + } + + protected ObjectInputStream() throws IOException, SecurityException { + } + + public int available() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public void close() throws IOException { + input.close(); + } + + public void defaultReadObject() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public int read() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(byte b[]) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(byte b[], int off, int len) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public boolean readBoolean() throws IOException { + return InferUndefined.can_throw_ioexception_boolean(); + } + + public byte readByte() throws IOException { + return InferUndefined.can_throw_ioexception_byte(); + } + + public char readChar() throws IOException { + return InferUndefined.can_throw_ioexception_char(); + } + + public double readDouble() throws IOException { + return InferUndefined.can_throw_ioexception_double(); + } + + public ObjectInputStream.GetField readFields() throws IOException { + throw new IOException(); + } + + public float readFloat() throws IOException { + return InferUndefined.can_throw_ioexception_float(); + } + + public void readFully(byte[] buf) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void readFully(byte[] buf, int off, int len) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public int readInt() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public long readLong() throws IOException { + return InferUndefined.can_throw_ioexception_long(); + } + + public final Object readObject() throws IOException { + return InferUndefined.can_throw_ioexception_object(); + } + + public short readShort() throws IOException { + return InferUndefined.can_throw_ioexception_short(); + } + + public Object readUnshared() throws IOException, ClassNotFoundException { + return InferUndefined.can_throw_ioexception_object(); + } + + public int readUnsignedByte() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int readUnsignedShort() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public String readUTF() throws IOException { + return InferUndefined.can_throw_ioexception_string(); + } + + public int skipBytes(int len) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public static abstract class GetField { + } +} diff --git a/infer/models/java/src/java/io/ObjectOutputStream.java b/infer/models/java/src/java/io/ObjectOutputStream.java new file mode 100644 index 000000000..8dfd0583d --- /dev/null +++ b/infer/models/java/src/java/io/ObjectOutputStream.java @@ -0,0 +1,112 @@ +package java.io; + +import com.facebook.infer.models.InferUndefined; + +public class ObjectOutputStream extends OutputStream { + + private static Class[] WRITE_UNSHARED_PARAM_TYPES; + private static byte NOT_SC_BLOCK_DATA; + private int nestedLevels; + private DataOutputStream output; + private boolean enableReplace; + private DataOutputStream primitiveTypes; + private ByteArrayOutputStream primitiveTypesBuffer; + private SerializationHandleMap objectsWritten; + private int currentHandle; + private Object currentObject; + private ObjectStreamClass currentClass; + private int protocolVersion; + private StreamCorruptedException nestedException; + private EmulatedFieldsForDumping currentPutField; + private boolean subclassOverridingImplementation; + private ObjectStreamClass proxyClassDesc; + + public ObjectOutputStream(OutputStream out) throws IOException { + this.output = new DataOutputStream(out); + InferUndefined.can_throw_ioexception_void(); + } + + public void close() throws IOException { + output.close(); + } + + public void defaultWriteObject() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void flush() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void reset() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(byte b[]) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(byte b[], int off, int len) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(int b) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void writeBoolean(boolean val) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void writeByte(int val) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void writeBytes(String str) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void writeChar(int val) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void writeChars(String str) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void writeDouble(double val) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void writeFields() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void writeFloat(float val) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void writeInt(int val) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void writeLong(long val) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public final void writeObject(Object obj) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void writeShort(int val) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void writeUnshared(Object obj) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void writeUTF(String str) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } +} diff --git a/infer/models/java/src/java/io/OutputStream.java b/infer/models/java/src/java/io/OutputStream.java new file mode 100644 index 000000000..a46e5a419 --- /dev/null +++ b/infer/models/java/src/java/io/OutputStream.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 1994, 2004, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.io; + +import com.facebook.infer.models.InferUndefined; + +public abstract class OutputStream implements Closeable { + + public void close() throws IOException { + if (this instanceof FileOutputStream) { + ((FileOutputStream) this).close(); + } else if (this instanceof FilterOutputStream) { + ((FilterOutputStream) this).close(); + } + } + + public void write(int b) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(byte b[]) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(byte b[], int off, int len) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void flush() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } +} \ No newline at end of file diff --git a/infer/models/java/src/java/io/OutputStreamWriter.java b/infer/models/java/src/java/io/OutputStreamWriter.java new file mode 100644 index 000000000..97bac2c7d --- /dev/null +++ b/infer/models/java/src/java/io/OutputStreamWriter.java @@ -0,0 +1,69 @@ +package java.io; + +import com.facebook.infer.models.InferUndefined; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; + +public class OutputStreamWriter extends Writer { + + private OutputStream out; + + private CharsetEncoder encoder; + + private ByteBuffer bytes; + + public OutputStreamWriter(OutputStream out, String charsetName) + throws UnsupportedEncodingException { + if (charsetName == null) + throw new NullPointerException("charsetName"); + else if (charsetName == "UTF8" || charsetName == "UTF-8" + || charsetName == "US-ASCII" || charsetName == "ISO-8859-1" + || charsetName == "UTF-16BE" || charsetName == "UTF-16LE" + || charsetName == "UTF-16") { + this.out = out; + } else + throw new UnsupportedEncodingException(); + } + + public OutputStreamWriter(OutputStream out) { + this.out = out; + } + + public OutputStreamWriter(OutputStream out, Charset cs) { + this.out = out; + } + + public OutputStreamWriter(OutputStream out, CharsetEncoder enc) { + this.out = out; + } + + public void flush() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(char cbuf[]) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(char cbuf[], int off, int len) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(int c) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(String str) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(String str, int off, int len) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void close() throws IOException { + out.close(); + } +} diff --git a/infer/models/java/src/java/io/PipedInputStream.java b/infer/models/java/src/java/io/PipedInputStream.java new file mode 100644 index 000000000..0f4e334df --- /dev/null +++ b/infer/models/java/src/java/io/PipedInputStream.java @@ -0,0 +1,53 @@ +package java.io; + +import com.facebook.infer.models.InferBuiltins; +import com.facebook.infer.models.InferUndefined; + +public class PipedInputStream extends InputStream { + + private Thread lastReader; + + public PipedInputStream(PipedOutputStream src) throws IOException { + this(); + } + + public PipedInputStream(PipedOutputStream src, int pipeSize) + throws IOException { + this(); + } + + public PipedInputStream() { + lastReader = new Thread(); + InferBuiltins.__set_file_attribute(lastReader); + } + + public PipedInputStream(int pipeSize) { + this(); + } + + public void close() throws IOException { + InferBuiltins.__set_mem_attribute(lastReader); + InferUndefined.can_throw_ioexception_void(); + } + + public void connect(PipedOutputStream src) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public int available() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(byte b[]) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(byte b[], int off, int len) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + +} diff --git a/infer/models/java/src/java/io/PipedOutputStream.java b/infer/models/java/src/java/io/PipedOutputStream.java new file mode 100644 index 000000000..a95d7e654 --- /dev/null +++ b/infer/models/java/src/java/io/PipedOutputStream.java @@ -0,0 +1,44 @@ +package java.io; + +import com.facebook.infer.models.InferBuiltins; +import com.facebook.infer.models.InferUndefined; + +public class PipedOutputStream extends OutputStream { + + private PipedInputStream target; + + public PipedOutputStream(PipedInputStream snk) throws IOException { + this.target = snk; + InferBuiltins.__set_file_attribute(target); + } + + public PipedOutputStream() { + this.target = new PipedInputStream(); + InferBuiltins.__set_file_attribute(target); + } + + public void close() throws IOException { + InferBuiltins.__set_mem_attribute(target); + InferUndefined.can_throw_ioexception_void(); + } + + public void connect(PipedInputStream snk) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void flush() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(byte b[]) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(byte b[], int off, int len) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(int b) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } +} diff --git a/infer/models/java/src/java/io/PipedReader.java b/infer/models/java/src/java/io/PipedReader.java new file mode 100644 index 000000000..166f12eaa --- /dev/null +++ b/infer/models/java/src/java/io/PipedReader.java @@ -0,0 +1,67 @@ +package java.io; + +import com.facebook.infer.models.InferBuiltins; +import com.facebook.infer.models.InferUndefined; + +public class PipedReader extends Reader { + + private Thread lastReader; + + private Thread lastWriter; + + private boolean isClosed; + + private void init() throws IOException { + InferUndefined.can_throw_ioexception_void(); + this.lastReader = new Thread(); + InferBuiltins.__set_file_attribute(this.lastReader); + } + + public PipedReader() { + } + + public PipedReader(int pipeSize) { + } + + public PipedReader(PipedWriter src) throws IOException { + init(); + } + + public PipedReader(PipedWriter src, int pipeSize) throws IOException { + init(); + } + + public void connect(PipedWriter src) throws IOException { + init(); + } + + public int read() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(char[] cbuf, int off, int len) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public boolean ready() throws IOException { + return InferUndefined.can_throw_ioexception_boolean(); + } + + public void mark(int readAheadLimit) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void reset() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public long skip(long n) throws IOException { + return InferUndefined.can_throw_ioexception_long(); + } + + public void close() throws IOException { + InferBuiltins.__set_mem_attribute(this.lastReader); + InferUndefined.can_throw_ioexception_void(); + } + +} diff --git a/infer/models/java/src/java/io/PipedWriter.java b/infer/models/java/src/java/io/PipedWriter.java new file mode 100644 index 000000000..05bb66934 --- /dev/null +++ b/infer/models/java/src/java/io/PipedWriter.java @@ -0,0 +1,73 @@ +package java.io; + +import com.facebook.infer.models.InferBuiltins; +import com.facebook.infer.models.InferUndefined; + +public abstract class PipedWriter extends Writer { + + private PipedReader destination; + private boolean isClosed; + + private void init() throws IOException { + InferUndefined.can_throw_ioexception_void(); + this.destination = new PipedReader(); + InferBuiltins.__set_file_attribute(this.destination); + } + + public PipedWriter() { + } + + public PipedWriter(PipedReader snk) throws IOException { + init(); + } + + public void connect(PipedReader snk) throws IOException { + init(); + } + + public Writer append(char c) throws IOException { + InferUndefined.can_throw_ioexception_void(); + return this; + } + + public Writer append(CharSequence csq) throws IOException { + InferUndefined.can_throw_ioexception_void(); + return this; + } + + public Writer append(CharSequence csq, int start, int end) + throws IOException { + InferUndefined.can_throw_ioexception_void(); + return this; + } + + public void flush() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(char cbuf[]) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(char cbuf[], int off, int len) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(int c) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(String str) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(String str, int off, int len) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void close() throws IOException { + InferBuiltins.__set_mem_attribute(this.destination); + InferUndefined.can_throw_ioexception_void(); + } + +} diff --git a/infer/models/java/src/java/io/PrintStream.java b/infer/models/java/src/java/io/PrintStream.java new file mode 100644 index 000000000..b8a842e3a --- /dev/null +++ b/infer/models/java/src/java/io/PrintStream.java @@ -0,0 +1,65 @@ +package java.io; + +public class PrintStream extends FilterOutputStream implements Closeable { + + private boolean ioError; + private boolean autoFlush; + private String encoding; + + public PrintStream(OutputStream out) { + super(out); + } + + public PrintStream(OutputStream out, boolean autoFlush) { + super(out); + } + + public PrintStream(OutputStream out, boolean autoFlush, String encoding) + throws UnsupportedEncodingException { + super(out); + } + + public PrintStream(String fileName) throws FileNotFoundException { + super(new FileOutputStream(fileName)); + } + + public PrintStream(String fileName, String csn) + throws FileNotFoundException, UnsupportedEncodingException { + super(new FileOutputStream(fileName)); + } + + public PrintStream(File file) throws FileNotFoundException { + super(new FileOutputStream(file)); + } + + public PrintStream(File file, String csn) throws FileNotFoundException, + UnsupportedEncodingException { + super(new FileOutputStream(file)); + } + + public void close() { + if (out != null) { + try { + if (out instanceof FileOutputStream) + ((FileOutputStream) out).close(); + } catch (IOException e) { + } + } + } + + //the methods write and flush in this class do not throw IOExceptions, thus they are + //modelled as a void method that does nothing. + + public void write(byte b[], int off, int len) { + } + + public void write(byte b[]) throws IOException { + } + + public void write(int b) { + } + + public void flush() { + } + +} diff --git a/infer/models/java/src/java/io/PrintWriter.java b/infer/models/java/src/java/io/PrintWriter.java new file mode 100644 index 000000000..64ff6ebce --- /dev/null +++ b/infer/models/java/src/java/io/PrintWriter.java @@ -0,0 +1,99 @@ +package java.io; + +import com.facebook.infer.models.InferUndefined; + +public class PrintWriter extends Writer { + + protected Writer out; + private boolean ioError; + private boolean autoFlush; + + public PrintWriter(OutputStream out) { + this(new OutputStreamWriter(out)); + } + + public PrintWriter(OutputStream out, boolean autoFlush) { + this(new OutputStreamWriter(out), autoFlush); + } + + public PrintWriter(Writer wr) { + out = wr; + } + + public PrintWriter(Writer wr, boolean autoFlush) { + out = wr; + } + + public PrintWriter(File file) throws FileNotFoundException { + this(new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(file)))); + } + + public PrintWriter(File file, String csn) throws FileNotFoundException, + UnsupportedEncodingException { + this(new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(file)))); + } + + public PrintWriter(String fileName) throws FileNotFoundException { + this(new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(fileName)))); + } + + public PrintWriter(String fileName, String csn) + throws FileNotFoundException, UnsupportedEncodingException { + this(new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(fileName)))); + } + + public PrintWriter append(char c) throws IOException { + InferUndefined.can_throw_ioexception_void(); + return this; + } + + public PrintWriter append(CharSequence csq) throws IOException { + InferUndefined.can_throw_ioexception_void(); + return this; + } + + public PrintWriter append(CharSequence csq, int start, int end) + throws IOException { + InferUndefined.can_throw_ioexception_void(); + return this; + } + + public void close() { + if (out != null) { + try { + if (out instanceof OutputStreamWriter) { + ((OutputStreamWriter) out).close(); + } else if (out instanceof BufferedWriter) { + ((BufferedWriter) out).close(); + } + } catch (IOException x) { + } + } + } + + public void flush() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(char cbuf[]) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(char cbuf[], int off, int len) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(int c) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(String str) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(String str, int off, int len) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + +} diff --git a/infer/models/java/src/java/io/PushbackInputStream.java b/infer/models/java/src/java/io/PushbackInputStream.java new file mode 100644 index 000000000..fc853853d --- /dev/null +++ b/infer/models/java/src/java/io/PushbackInputStream.java @@ -0,0 +1,54 @@ +package java.io; + +import com.facebook.infer.models.InferUndefined; + +public class PushbackInputStream extends FilterInputStream { + + public PushbackInputStream(InputStream in, int size) { + super(in); + } + + public PushbackInputStream(InputStream in) { + super(in); + } + + public int available() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public void close() throws IOException { + super.close(); + } + + public int read() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(byte b[]) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(byte b[], int off, int len) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public void reset() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public long skip(long n) throws IOException { + return InferUndefined.can_throw_ioexception_long(); + } + + public void unread(byte[] b) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void unread(byte[] b, int off, int len) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void unread(int b) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } +} diff --git a/infer/models/java/src/java/io/PushbackReader.java b/infer/models/java/src/java/io/PushbackReader.java new file mode 100644 index 000000000..b0068a060 --- /dev/null +++ b/infer/models/java/src/java/io/PushbackReader.java @@ -0,0 +1,56 @@ +package java.io; + +import com.facebook.infer.models.InferUndefined; + +public class PushbackReader extends FilterReader { + + public PushbackReader(Reader in, int size) { + super(in); + } + + public PushbackReader(Reader in) { + super(in); + } + + public void close() throws IOException { + super.close(); + } + + public int read() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(char cbuf[]) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(char cbuf[], int off, int len) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public boolean ready() throws IOException { + return InferUndefined.can_throw_ioexception_boolean(); + } + + public void reset() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public long skip(long n) throws IOException { + return InferUndefined.can_throw_ioexception_long(); + } + + public void unread(char cbuf[]) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void unread(char cbuf[], int off, int len) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void unread(int c) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + +} diff --git a/infer/models/java/src/java/io/RandomAccessFile.java b/infer/models/java/src/java/io/RandomAccessFile.java new file mode 100644 index 000000000..d8f02ab78 --- /dev/null +++ b/infer/models/java/src/java/io/RandomAccessFile.java @@ -0,0 +1,174 @@ +package java.io; + +import com.facebook.infer.models.InferBuiltins; +import com.facebook.infer.models.InferUndefined; +import dalvik.system.CloseGuard; + +import java.nio.FileChannelImpl; +import java.nio.channels.FileChannel; + +public class RandomAccessFile implements Closeable { + + private FileDescriptor fd; + private boolean syncMetadata; + private FileChannel channel; + private int mode; + private CloseGuard guard; + + private byte[] scratch; + + + public RandomAccessFile(String name, String mode) + throws FileNotFoundException { + this.guard = new CloseGuard(); + InferBuiltins.__set_file_attribute(this.guard); + } + + public RandomAccessFile(File file, String mode) + throws FileNotFoundException { + this.guard = new CloseGuard(); + InferBuiltins.__set_file_attribute(this.guard); + } + + public FileChannel getChannel() { + channel = new FileChannelImpl(this, fd, InferUndefined.int_undefined()); + return channel; + } + + public void close() throws IOException { + InferBuiltins.__set_mem_attribute(this.guard); + InferUndefined.can_throw_ioexception_void(); + } + + public int read() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(byte b[], int off, int len) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(byte b[]) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public final void readFully(byte b[]) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public final void readFully(byte b[], int off, int len) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(int b) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(byte b[]) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(byte b[], int off, int len) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void seek(long pos) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public long length() throws IOException { + return InferUndefined.can_throw_ioexception_long(); + } + + public final boolean readBoolean() throws IOException { + return InferUndefined.can_throw_ioexception_boolean(); + } + + public final byte readByte() throws IOException { + return InferUndefined.can_throw_ioexception_byte(); + } + + public final int readUnsignedByte() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public final short readShort() throws IOException { + return InferUndefined.can_throw_ioexception_short(); + } + + public final int readUnsignedShort() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public final char readChar() throws IOException { + return InferUndefined.can_throw_ioexception_char(); + } + + public final int readInt() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public final long readLong() throws IOException { + return InferUndefined.can_throw_ioexception_long(); + } + + public final float readFloat() throws IOException { + return InferUndefined.can_throw_ioexception_float(); + } + + public final double readDouble() throws IOException { + return InferUndefined.can_throw_ioexception_double(); + } + + public final String readLine() throws IOException { + return InferUndefined.can_throw_ioexception_string(); + } + + public final String readUTF() throws IOException { + return InferUndefined.can_throw_ioexception_string(); + } + + public final void writeBoolean(boolean v) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public final void writeByte(int v) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public final void writeShort(int v) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public final void writeChar(int v) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public final void writeInt(int v) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public final void writeLong(long v) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public final void writeFloat(float v) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public final void writeDouble(double v) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public final void writeBytes(String s) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public final void writeChars(String s) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public final void writeUTF(String str) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } +} diff --git a/infer/models/java/src/java/io/Reader.java b/infer/models/java/src/java/io/Reader.java new file mode 100644 index 000000000..1cd863176 --- /dev/null +++ b/infer/models/java/src/java/io/Reader.java @@ -0,0 +1,45 @@ +package java.io; + +import com.facebook.infer.models.InferUndefined; + +public abstract class Reader implements Closeable { + + public void close() throws IOException { + if (this instanceof InputStreamReader) { + ((InputStreamReader) this).close(); + } else if (this instanceof BufferedReader) { + ((BufferedReader) this).close(); + } else if (this instanceof FilterReader) { + ((FilterReader) this).close(); + } + } + + public int read() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(char cbuf[]) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(char cbuf[], int off, int len) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(java.nio.CharBuffer target) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public boolean ready() throws IOException { + return InferUndefined.can_throw_ioexception_boolean(); + } + + public void reset() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public long skip(long n) throws IOException { + return InferUndefined.can_throw_ioexception_long(); + } + +} diff --git a/infer/models/java/src/java/io/Writer.java b/infer/models/java/src/java/io/Writer.java new file mode 100644 index 000000000..b814c3ab0 --- /dev/null +++ b/infer/models/java/src/java/io/Writer.java @@ -0,0 +1,60 @@ +package java.io; + +import com.facebook.infer.models.InferUndefined; + +public abstract class Writer implements Closeable { + + protected Object lock; + + public Writer append(char c) throws IOException { + InferUndefined.can_throw_ioexception_void(); + return this; + } + + public Writer append(CharSequence csq) throws IOException { + InferUndefined.can_throw_ioexception_void(); + return this; + } + + public Writer append(CharSequence csq, int start, int end) + throws IOException { + InferUndefined.can_throw_ioexception_void(); + return this; + } + + public void close() throws IOException { + if (this instanceof OutputStreamWriter) { + ((OutputStreamWriter) this).close(); + } else if (this instanceof BufferedWriter) { + ((BufferedWriter) this).close(); + } else if (this instanceof PrintWriter) { + ((PrintWriter) this).close(); + } + } + + public void flush() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(char cbuf[]) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(char cbuf[], int off, int len) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(int c) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(String str) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(String str, int off, int len) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + +} diff --git a/infer/models/java/src/java/lang/Class.java b/infer/models/java/src/java/lang/Class.java new file mode 100644 index 000000000..bd0ee38b8 --- /dev/null +++ b/infer/models/java/src/java/lang/Class.java @@ -0,0 +1,33 @@ +package java.lang; + +public final class Class { + private static long serialVersionUID; + + private transient int dexClassDefIndex; + + private transient int dexTypeIndex; + + private transient volatile boolean dexIndicesInitialized; + + transient String name; + + public String getName() { + return this.name; + } + + public static Class forName(String className) + throws ClassNotFoundException { + return new Class(); + } + + public boolean isAssignableFrom(Class cls) { + return false; + } + + public static Class getPrimitiveClass(String name) { + Class c = new Class(); + c.name = name; + return c; + } + +} diff --git a/infer/models/java/src/java/lang/NullPointerException.java b/infer/models/java/src/java/lang/NullPointerException.java new file mode 100644 index 000000000..99c9d3af4 --- /dev/null +++ b/infer/models/java/src/java/lang/NullPointerException.java @@ -0,0 +1,11 @@ +package java.lang; + +public class NullPointerException extends RuntimeException { + + public NullPointerException() { + } + + public NullPointerException(String s) { + } + +} diff --git a/infer/models/java/src/java/lang/Object.java b/infer/models/java/src/java/lang/Object.java new file mode 100644 index 000000000..a7c574daf --- /dev/null +++ b/infer/models/java/src/java/lang/Object.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 1994, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.lang; + +import com.facebook.infer.models.InferUndefined; + +public class Object { + + public Class getClass() { + Class c = new Class(); + c.name = InferUndefined.string_undefined(); + return c; + } + +} diff --git a/infer/models/java/src/java/lang/Process.java b/infer/models/java/src/java/lang/Process.java new file mode 100644 index 000000000..813208a7a --- /dev/null +++ b/infer/models/java/src/java/lang/Process.java @@ -0,0 +1,79 @@ +package java.lang; + +import com.facebook.infer.models.InferUndefined; + +import java.io.*; + +public abstract class Process { + + protected final int pid; + protected final InputStream inputStream; + protected final OutputStream outputStream; + //protected final InputStream errorStream; + + public Process(int pid, FileDescriptor in, FileDescriptor out, FileDescriptor err) { + this.pid = pid; + this.inputStream = new ProcessInputStream(in); + this.outputStream = new ProcessOutputStream(out); + //this.errorStream = new ProcessInputStream(err); // causes too many case splits for now + } + public int exitValue() { + return InferUndefined.int_undefined(); + } + + public InputStream getInputStream() { + return this.inputStream; + } + + public OutputStream getOutputStream() { + return this.outputStream; + } + + public int waitFor() throws InterruptedException { + return InferUndefined.int_undefined(); + } + + public void destroy() { + try { + inputStream.close(); + } catch (IOException e) {} + try { + outputStream.close(); + } catch (IOException e) {} + // causes too many case splits for now + /*try { + errorStream.close(); + } catch (IOException e) {}*/ + } + + private static class ProcessInputStream extends FileInputStream { + + private FileDescriptor fd; + + private ProcessInputStream(FileDescriptor fd) { + super(fd); + this.fd = fd; + } + + @Override + public void close() throws IOException { + super.close(); + } + } + + private static class ProcessOutputStream extends FileOutputStream { + + private FileDescriptor fd; + + private ProcessOutputStream(FileDescriptor fd) { + super(fd); + this.fd = fd; + } + + @Override + public void close() throws IOException { + super.close(); + } + } + +} diff --git a/infer/models/java/src/java/lang/ProcessManager.java b/infer/models/java/src/java/lang/ProcessManager.java new file mode 100644 index 000000000..e763fa4ba --- /dev/null +++ b/infer/models/java/src/java/lang/ProcessManager.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * 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 java.lang; + +import com.facebook.infer.models.InferUndefined; + +import java.io.*; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.Map; + + +final class ProcessManager { + + private Map processReferences; + + private ProcessReferenceQueue referenceQueue; + + public Process exec(String[] taintedCommand, String[] taintedEnvironment, File workingDirectory, + boolean redirectErrorStream) throws IOException { + + FileDescriptor in = new FileDescriptor(); + FileDescriptor out = new FileDescriptor(); + FileDescriptor err = new FileDescriptor(); + + ProcessImpl process = new ProcessImpl(InferUndefined.int_undefined(), in, out, err); + return process; + } + + static class ProcessReference extends WeakReference { + + final int processId; + + public ProcessReference(ProcessImpl referent, ProcessReferenceQueue referenceQueue) { + super(referent, referenceQueue); + this.processId = referent.pid; + } + } + + static class ProcessReferenceQueue extends ReferenceQueue { + } + + static class ProcessImpl extends Process { + + ProcessImpl(int pid, FileDescriptor in, FileDescriptor out, FileDescriptor err) { + super(pid, in, out, err); + } + + void setExitValue(int exitValue) { + } + } + + private static final ProcessManager instance = new ProcessManager(); + + public static ProcessManager getInstance() { + return instance; + } + +} diff --git a/infer/models/java/src/java/lang/Runtime.java b/infer/models/java/src/java/lang/Runtime.java new file mode 100644 index 000000000..960d3400f --- /dev/null +++ b/infer/models/java/src/java/lang/Runtime.java @@ -0,0 +1,49 @@ +package java.lang; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +public class Runtime { + + private static Runtime mRuntime; + private String[] mLibPaths; + private List shutdownHooks; + private static boolean finalizeOnExit; + private boolean shuttingDown; + private boolean tracingMethods; + + public static Runtime getRuntime() { + return mRuntime; + } + + private Runtime() { + } + + public Process exec(String command) throws IOException { + return exec(command, null, null); + } + + public Process exec(String command, String[] envp) throws IOException { + return exec(command, envp, null); + } + + public Process exec(String command, String[] envp, File dir) + throws IOException { + return ProcessManager.getInstance().exec(null, envp, null, false); + } + + public Process exec(String cmdarray[]) throws IOException { + return exec(cmdarray, null, null); + } + + public Process exec(String[] cmdarray, String[] envp) throws IOException { + return exec(cmdarray, envp, null); + } + + public Process exec(String[] cmdarray, String[] envp, File dir) + throws IOException { + return ProcessManager.getInstance().exec(cmdarray, envp, dir, false); + } + +} diff --git a/infer/models/java/src/java/lang/String.java b/infer/models/java/src/java/lang/String.java new file mode 100644 index 000000000..e6698a321 --- /dev/null +++ b/infer/models/java/src/java/lang/String.java @@ -0,0 +1,52 @@ +package java.lang; + +import com.facebook.infer.models.InferUndefined; + +public final class String { + + private final char[] value; + private final int offset; + private final int count; + + public int length() { + if (this == "") + return 0; + else { + return InferUndefined.nonneg_int(); + } + } + + public String() { + this.offset = 0; + this.count = 0; + this.value = new char[0]; + } + + public String(byte bytes[]) { + this(bytes, 0, bytes.length); + } + + + public String(byte bytes[], int offset, int length) { + checkBounds(bytes, offset, length); + char[] v = new char[bytes[0]]; /** yes, this could be improved **/ + this.offset = 0; + this.count = v.length; + this.value = v; + } + + private static void checkBounds(byte[] bytes, int offset, int length) { + if (length < 0) + throw new StringIndexOutOfBoundsException(length); + if (offset < 0) + throw new StringIndexOutOfBoundsException(offset); + if (offset > bytes.length - length) + throw new StringIndexOutOfBoundsException(offset + length); + } + + + public boolean equals(Object anObject) { + return this == anObject; + } + +} diff --git a/infer/models/java/src/java/lang/System.java b/infer/models/java/src/java/lang/System.java new file mode 100644 index 000000000..809a7db05 --- /dev/null +++ b/infer/models/java/src/java/lang/System.java @@ -0,0 +1,37 @@ +package java.lang; + +import com.facebook.infer.models.InferBuiltins; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.PrintStream; +import java.util.Properties; + +public final class System { + + private System() { + } + + public final static InputStream in; + + static { + byte[] arr = {0}; + in = new ByteArrayInputStream(arr); + } + + public final static PrintStream out = new PrintStream( + new ByteArrayOutputStream()); + + public final static PrintStream err = new PrintStream( + new ByteArrayOutputStream()); + + private static Properties systemProperties; + + private static String lineSeparator; + + public static void exit(int status) { + InferBuiltins._exit(); + } + +} diff --git a/infer/models/java/src/java/lang/Thread.java b/infer/models/java/src/java/lang/Thread.java new file mode 100644 index 000000000..a5122ad9d --- /dev/null +++ b/infer/models/java/src/java/lang/Thread.java @@ -0,0 +1,48 @@ +package java.lang; + +import java.util.List; + +public class Thread implements Runnable { + + private static int NANOS_PER_MILLI; + + public static int MAX_PRIORITY; + public static int MIN_PRIORITY; + + public static int NORM_PRIORITY; + + volatile VMThread vmThread; + volatile ThreadGroup group; + volatile boolean daemon; + volatile String name; + volatile int priority; + volatile long stackSize; + Runnable target; + private static int count; + + private long id; + ThreadLocal.Values localValues; + ThreadLocal.Values inheritableValues; + private List interruptActions; + private ClassLoader contextClassLoader; + private UncaughtExceptionHandler uncaughtHandler; + private static UncaughtExceptionHandler defaultUncaughtHandler; + boolean hasBeenStarted; + + private int parkState; + private Object parkBlocker; + + public static interface UncaughtExceptionHandler { + } + + public synchronized void start() { + run(); + } + + public void run() { + if (target != null) { + target.run(); + } + } + +} diff --git a/infer/models/java/src/java/lang/reflect/Array.java b/infer/models/java/src/java/lang/reflect/Array.java new file mode 100644 index 000000000..68c9d73ff --- /dev/null +++ b/infer/models/java/src/java/lang/reflect/Array.java @@ -0,0 +1,33 @@ +package java.lang.reflect; + +import com.facebook.infer.models.InferUndefined; + +public final class Array { + + public static Object newInstance(Class componentType, int length) + throws NegativeArraySizeException { + String name = componentType.getName(); + if (length < 0) { + throw new NegativeArraySizeException(); + } + if (name == "int") { + return new int[length]; + } else if (name == "short") { + return new short[length]; + } else if (name == "byte") { + return new byte[length]; + } else if (name == "boolean") { + return new boolean[length]; + } else if (name == "long") { + return new long[length]; + } else if (name == "float") { + return new float[length]; + } else if (name == "double") { + return new double[length]; + } else if (name == "char") { + return new char[length]; + } else + return InferUndefined.object_undefined(); + } + +} diff --git a/infer/models/java/src/java/net/HttpURLConnection.java b/infer/models/java/src/java/net/HttpURLConnection.java new file mode 100644 index 000000000..3a95df8f9 --- /dev/null +++ b/infer/models/java/src/java/net/HttpURLConnection.java @@ -0,0 +1,111 @@ + + +package java.net; + +import com.facebook.infer.models.InferBuiltins; + +public class HttpURLConnection extends URLConnection { + private static int DEFAULT_CHUNK_LENGTH; + + private static String[] PERMITTED_USER_METHODS; + + protected String method; + + protected int responseCode; + + protected String responseMessage; + + protected boolean instanceFollowRedirects; + + private static boolean followRedirects; + + protected int chunkLength; + + protected int fixedContentLength; + + protected long fixedContentLengthLong; + + public static int HTTP_ACCEPTED; + + public static int HTTP_BAD_GATEWAY; + + public static int HTTP_BAD_METHOD; + + public static int HTTP_BAD_REQUEST; + + public static int HTTP_CLIENT_TIMEOUT; + + public static int HTTP_CONFLICT; + + public static int HTTP_CREATED; + + public static int HTTP_ENTITY_TOO_LARGE; + + public static int HTTP_FORBIDDEN; + + public static int HTTP_GATEWAY_TIMEOUT; + + public static int HTTP_GONE; + + public static int HTTP_INTERNAL_ERROR; + + public static int HTTP_LENGTH_REQUIRED; + + public static int HTTP_MOVED_PERM; + + public static int HTTP_MOVED_TEMP; + + public static int HTTP_MULT_CHOICE; + + public static int HTTP_NO_CONTENT; + + public static int HTTP_NOT_ACCEPTABLE; + + public static int HTTP_NOT_AUTHORITATIVE; + + public static int HTTP_NOT_FOUND; + + public static int HTTP_NOT_IMPLEMENTED; + + public static int HTTP_NOT_MODIFIED; + + public static int HTTP_OK; + + public static int HTTP_PARTIAL; + + public static int HTTP_PAYMENT_REQUIRED; + + public static int HTTP_PRECON_FAILED; + + public static int HTTP_PROXY_AUTH; + + public static int HTTP_REQ_TOO_LONG; + + public static int HTTP_RESET; + + public static int HTTP_SEE_OTHER; + + public static int HTTP_SERVER_ERROR; + + public static int HTTP_USE_PROXY; + + public static int HTTP_UNAUTHORIZED; + + public static int HTTP_UNSUPPORTED_TYPE; + + public static int HTTP_UNAVAILABLE; + + public static int HTTP_VERSION; + + + public HttpURLConnection(URL url) { + super(url); + method = new String(); + InferBuiltins.__set_file_attribute(method); + } + + + public void disconnect() { + InferBuiltins.__set_mem_attribute(method); + } +} diff --git a/infer/models/java/src/java/net/JarURLConnection.java b/infer/models/java/src/java/net/JarURLConnection.java new file mode 100644 index 000000000..bd526027e --- /dev/null +++ b/infer/models/java/src/java/net/JarURLConnection.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 1997, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.net; + +public class JarURLConnection extends URLConnection { + + + protected URLConnection jarFileURLConnection; + private String entryName; + private URL fileURL; + private String file; + + protected JarURLConnection(URL url) throws MalformedURLException { + super(url); + } +} diff --git a/infer/models/java/src/java/net/PlainSocketImpl.java b/infer/models/java/src/java/net/PlainSocketImpl.java new file mode 100644 index 000000000..978652e7e --- /dev/null +++ b/infer/models/java/src/java/net/PlainSocketImpl.java @@ -0,0 +1,213 @@ +/* + * Copyright (c) 1995, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.net; + +import com.facebook.infer.models.InferBuiltins; +import com.facebook.infer.models.InferUndefined; +import dalvik.system.CloseGuard; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + + +class PlainSocketImpl extends SocketImpl { + private static InetAddress lastConnectedAddress; + + private static int lastConnectedPort; + + private boolean streaming = true; + + private boolean shutdownInput; + + private Proxy proxy; + + private CloseGuard guard; + + PlainSocketImpl() { + guard = new CloseGuard(); + InferBuiltins.__set_file_attribute(guard); + } + + + protected void create(boolean stream) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + + protected void connect(String host, int port) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + + protected void connect(InetAddress address, int port) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + + protected void connect(SocketAddress address, int timeout) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + + protected void bind(InetAddress host, int port) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + + protected void listen(int backlog) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + + protected void accept(SocketImpl s) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public synchronized InputStream getInputStream() throws IOException { + return new PlainSocketInputStream(this); + } + + public synchronized OutputStream getOutputStream() throws IOException { + return new PlainSocketOutputStream(this); + } + + + protected int available() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + protected void close() throws IOException { + InferBuiltins.__set_mem_attribute(guard); + InferUndefined.can_throw_ioexception_void(); + } + + protected void finalize() throws IOException { + close(); + } + + void socketCreate(boolean isServer) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + void socketConnect(InetAddress address, int port, int timeout) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + void socketBind(InetAddress address, int port) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + void socketListen(int count) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + + void socketAccept(SocketImpl s) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + int socketAvailable() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + void socketClose0(boolean useDeferredClose) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + void socketShutdown(int howto) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + void socketSetOption(int cmd, boolean on, Object value) throws SocketException { + InferUndefined.can_throw_socketexception_void(); + } + + int socketGetOption(int opt, Object iaContainerObj) throws SocketException { + return InferUndefined.can_throw_socketexception_int(); + } + + void socketSendUrgentData(int data) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public Object getOption(int opt) throws SocketException { + return InferUndefined.can_throw_socketexception_object(); + } + + public void setOption(int opt, Object val) throws SocketException { + InferUndefined.can_throw_socketexception_void(); + } + + protected void sendUrgentData(int data) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + private static class PlainSocketInputStream extends InputStream { + private PlainSocketImpl socketImpl; + + public PlainSocketInputStream(PlainSocketImpl socketImpl) { + this.socketImpl = socketImpl; + } + + public int available() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public void close() throws IOException { + socketImpl.close(); + } + + public int read() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + } + + private static class PlainSocketOutputStream extends OutputStream { + private PlainSocketImpl socketImpl; + + public PlainSocketOutputStream(PlainSocketImpl socketImpl) { + this.socketImpl = socketImpl; + } + + public void close() throws IOException { + socketImpl.close(); + } + + public void write(int oneByte) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(byte[] buffer, int offset, int byteCount) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + } + +} diff --git a/infer/models/java/src/java/net/ServerSocket.java b/infer/models/java/src/java/net/ServerSocket.java new file mode 100644 index 000000000..e960e4124 --- /dev/null +++ b/infer/models/java/src/java/net/ServerSocket.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 1995, 2007, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.net; + +import com.facebook.infer.models.InferUndefined; + +import java.io.IOException; + +public class ServerSocket { + + private static int DEFAULT_BACKLOG; + + private SocketImpl impl; + + static SocketImplFactory factory; + + private boolean isBound; + + private boolean isClosed; + + public ServerSocket() throws IOException { + impl = new PlainSocketImpl(); + } + + public ServerSocket(int port) throws IOException { + this(); + } + + public ServerSocket(int port, int backlog) throws IOException { + this(); + } + + public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException { + this(); + } + + public void close() throws IOException { + ((PlainSocketImpl) impl).close(); + } + + public Socket accept() throws IOException { + if (InferUndefined.boolean_undefined()) { + Socket s = new Socket((SocketImpl) null); + return s; + } else throw new IOException(); + } + +} diff --git a/infer/models/java/src/java/net/Socket.java b/infer/models/java/src/java/net/Socket.java new file mode 100644 index 000000000..80ed8a166 --- /dev/null +++ b/infer/models/java/src/java/net/Socket.java @@ -0,0 +1,83 @@ +package java.net; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class Socket { + + private static SocketImplFactory factory; + + SocketImpl impl; + private Proxy proxy; + + volatile boolean isCreated; + private boolean isBound; + private boolean isConnected; + private boolean isClosed; + private boolean isInputShutdown; + private boolean isOutputShutdown; + + private InetAddress localAddress; + + private Object connectLock; + + public Socket() { + try { + setImpl(); + } catch (IOException e) { + } + } + + void setImpl() throws IOException { + impl = new PlainSocketImpl(); + } + + public Socket(Proxy proxy) { + this(); + } + + protected Socket(SocketImpl impl) throws SocketException { + this(); + } + + public Socket(String host, int port) throws UnknownHostException, + IOException { + this(); + } + + public Socket(InetAddress address, int port) throws IOException { + this(); + } + + public Socket(String host, int port, InetAddress localAddr, int localPort) + throws IOException { + this(); + } + + public Socket(InetAddress address, int port, InetAddress localAddr, + int localPort) throws IOException { + this(); + } + + public Socket(String host, int port, boolean stream) throws IOException { + this(); + } + + public Socket(InetAddress host, int port, boolean stream) + throws IOException { + this(); + } + + public InputStream getInputStream() throws IOException { + return ((PlainSocketImpl) impl).getInputStream(); + } + + public OutputStream getOutputStream() throws IOException { + return ((PlainSocketImpl) impl).getOutputStream(); + } + + public synchronized void close() throws IOException { + ((PlainSocketImpl) impl).close(); + } +} diff --git a/infer/models/java/src/java/net/URL.java b/infer/models/java/src/java/net/URL.java new file mode 100644 index 000000000..a848c7689 --- /dev/null +++ b/infer/models/java/src/java/net/URL.java @@ -0,0 +1,72 @@ +package java.net; + +import javax.net.ssl.HttpsURLConnection; +import java.util.Hashtable; + +public final class URL implements java.io.Serializable { + + private static long serialVersionUID; + private static URLStreamHandlerFactory streamHandlerFactory; + private static Hashtable streamHandlers; + + private String protocol; + private String authority; + private String host; + private int port; + private String file; + private String ref; + + private transient String userInfo; + private transient String path; + private transient String query; + transient URLStreamHandler streamHandler; + private transient int hashCode; + + + public URL(String protocol, String host, int port, String file) throws MalformedURLException { + this(protocol, host, port, file, null); + } + + + public URL(String protocol, String host, String file) throws MalformedURLException { + this(protocol, host, -1, file); + } + + + public URL(String protocol, String host, int port, String file, + URLStreamHandler handler) throws MalformedURLException { + this.protocol = protocol; + this.host = host; + this.file = file; + this.port = port; + } + + public URL(String spec) throws MalformedURLException { + this(null, spec); + } + + public URL(URL context, String spec) throws MalformedURLException { + this(context, spec, null); + } + + public URL(URL context, String spec, URLStreamHandler handler) throws MalformedURLException { + protocol = spec; + } + + + public URLConnection openConnection(Proxy proxy) throws java.io.IOException { + if (protocol == "jar") { + return new JarURLConnection(this); + } else if (protocol == "http") { + return new HttpURLConnection(this); + } else if (protocol == "https") { + return new HttpsURLConnection(this); + } else + return new URLConnection(this); + } + + public URLConnection openConnection() throws java.io.IOException { + return openConnection(null); + } + +} diff --git a/infer/models/java/src/java/net/URLConnection.java b/infer/models/java/src/java/net/URLConnection.java new file mode 100644 index 000000000..3c1c0406d --- /dev/null +++ b/infer/models/java/src/java/net/URLConnection.java @@ -0,0 +1,48 @@ +package java.net; + +import java.io.*; +import java.util.Hashtable; + +public class URLConnection { + + protected URL url; + private String contentType; + private static boolean defaultAllowUserInteraction; + private static boolean defaultUseCaches; + ContentHandler defaultHandler; + private long lastModified; + protected long ifModifiedSince; + protected boolean useCaches; + protected boolean connected; + protected boolean doOutput; + protected boolean doInput; + protected boolean allowUserInteraction; + + private static ContentHandlerFactory contentHandlerFactory; + private int readTimeout; + private int connectTimeout; + static Hashtable contentHandlers; + private static FileNameMap fileNameMap; + + + public URLConnection(URL url) { + this.url = url; + } + + public URL getURL() { + return url; + } + + public InputStream getInputStream() throws IOException { + if (this instanceof HttpURLConnection) { + byte[] arr = {1}; + return new ByteArrayInputStream(arr); + } else return new FileInputStream(""); + } + + public OutputStream getOutputStream() throws IOException { + if (this instanceof HttpURLConnection) { + return new ByteArrayOutputStream(); + } else return new FileOutputStream(""); + } +} diff --git a/infer/models/java/src/java/nio/FileChannelImpl.java b/infer/models/java/src/java/nio/FileChannelImpl.java new file mode 100644 index 000000000..2647cabf0 --- /dev/null +++ b/infer/models/java/src/java/nio/FileChannelImpl.java @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 java.nio; + +import com.facebook.infer.models.InferUndefined; + +import java.io.*; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; +import java.util.Comparator; +import java.util.SortedSet; + +public class FileChannelImpl extends FileChannel { + private static Comparator LOCK_COMPARATOR; + + private Object stream; + private FileDescriptor fd; + private int mode; + + private SortedSet locks; + + public FileChannelImpl(Object stream, FileDescriptor fd, int mode) { + this.fd = fd; + this.stream = stream; + this.mode = mode; + } + + public void implCloseChannel() throws IOException { + if (stream instanceof FileInputStream) { + ((FileInputStream) stream).close(); + } else if (stream instanceof FileOutputStream) { + ((FileOutputStream) stream).close(); + } else if (stream instanceof RandomAccessFile) { + ((RandomAccessFile) stream).close(); + } + } + + public long position() throws IOException { + return InferUndefined.can_throw_ioexception_long(); + } + + public FileChannel position(long newPosition) throws IOException { + if (InferUndefined.boolean_undefined()) + throw new IOException(); + else return this; + } + + public int read(ByteBuffer buffer, long position) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(ByteBuffer buffer) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public long read(ByteBuffer[] buffers, int offset, int length) throws IOException { + return InferUndefined.can_throw_ioexception_long(); + } + + public long size() throws IOException { + return InferUndefined.can_throw_ioexception_long(); + } + + public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException { + return InferUndefined.can_throw_ioexception_long(); + } + + public long transferTo(long position, long count, WritableByteChannel target) throws IOException { + return InferUndefined.can_throw_ioexception_long(); + } + + public FileChannel truncate(long size) throws IOException { + if (InferUndefined.boolean_undefined()) + throw new IOException(); + else return this; + } + + public int write(ByteBuffer buffer, long position) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int write(ByteBuffer buffer) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public long write(ByteBuffer[] buffers, int offset, int length) throws IOException { + return InferUndefined.can_throw_ioexception_long(); + } + + public FileDescriptor getFD() { + return fd; + } + +} diff --git a/infer/models/java/src/java/nio/channels/FileChannel.java b/infer/models/java/src/java/nio/channels/FileChannel.java new file mode 100644 index 000000000..01b59c72d --- /dev/null +++ b/infer/models/java/src/java/nio/channels/FileChannel.java @@ -0,0 +1,33 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 java.nio.channels; + +import java.nio.channels.spi.AbstractInterruptibleChannel; + +public abstract class FileChannel extends AbstractInterruptibleChannel { + + public static class MapMode { + + public static MapMode PRIVATE; + + public static MapMode READ_ONLY; + + public static MapMode READ_WRITE; + + private String displayName; + } +} diff --git a/infer/models/java/src/java/nio/channels/spi/AbstractInterruptibleChannel.java b/infer/models/java/src/java/nio/channels/spi/AbstractInterruptibleChannel.java new file mode 100644 index 000000000..e63e669f0 --- /dev/null +++ b/infer/models/java/src/java/nio/channels/spi/AbstractInterruptibleChannel.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 java.nio.channels.spi; + +import java.io.IOException; +import java.nio.FileChannelImpl; + +public class AbstractInterruptibleChannel { + + public final void close() throws IOException { + if (this instanceof FileChannelImpl) { + ((FileChannelImpl) this).implCloseChannel(); + } + } + +} diff --git a/infer/models/java/src/java/security/DigestInputStream.java b/infer/models/java/src/java/security/DigestInputStream.java new file mode 100644 index 000000000..6427dfc62 --- /dev/null +++ b/infer/models/java/src/java/security/DigestInputStream.java @@ -0,0 +1,30 @@ +package java.security; + +import com.facebook.infer.models.InferUndefined; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class DigestInputStream extends FilterInputStream { + + protected MessageDigest digest; + + private boolean isOn; + + public DigestInputStream(InputStream stream, MessageDigest digest) { + super(stream); + } + + public int read() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(byte b[]) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(byte b[], int off, int len) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } +} diff --git a/infer/models/java/src/java/security/DigestOutputStream.java b/infer/models/java/src/java/security/DigestOutputStream.java new file mode 100644 index 000000000..f28b4f342 --- /dev/null +++ b/infer/models/java/src/java/security/DigestOutputStream.java @@ -0,0 +1,29 @@ +package java.security; + +import com.facebook.infer.models.InferUndefined; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +public class DigestOutputStream extends FilterOutputStream { + + protected MessageDigest digest; + private boolean isOn; + + public DigestOutputStream(OutputStream stream, MessageDigest digest) { + super(stream); + } + + public void write(byte b[], int off, int len) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(byte b[]) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(int b) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } +} diff --git a/infer/models/java/src/java/util/HashMap.java b/infer/models/java/src/java/util/HashMap.java new file mode 100644 index 000000000..b16af2497 --- /dev/null +++ b/infer/models/java/src/java/util/HashMap.java @@ -0,0 +1,67 @@ +package java.util; +import java.io.*; + +import com.facebook.infer.models.InferUndefined; + +/** + * A recency abstraction for hashmaps that remembers only the last two + * keys known to exist in the map. + * + * get(key) can return null when key is not in the hashmap, and + * containsKey(key) and put(key, value) are used to protect against + * such nulls. Slightly unsound for the other reason get() can return + * null, when a pair (key,null) is in the map. Then when + * containsKey(key) is true, we will not report an NPE on a subsequent + * get(key). +*/ + +public abstract class HashMap extends AbstractMap + implements Map, Cloneable, Serializable { + + private Object lastKey1 = null; + private Object lastKey2 = null; + + public boolean containsKey(Object key) { + // doesn't actually check if _containsKey(key). If you just put a + // key in the map, why would you check if it's still there? + + if (InferUndefined.boolean_undefined()) { + pushKey(key); + return true; + } else { + return false; + } + } + + public V get(Object key) { + if (_containsKey(key)) { + return (V)InferUndefined.object_undefined(); + } else if (InferUndefined.boolean_undefined()) { + pushKey(key); + return (V)InferUndefined.object_undefined(); + } + + return null; + } + + public V put(Object key, Object value) { + pushKey(key); + + if (InferUndefined.boolean_undefined()) { + return (V)InferUndefined.object_undefined(); + } + return null; + } + + + /** some sort of circular buffer simulator */ + private void pushKey(Object key) { + lastKey2 = lastKey1; + lastKey1 = key; + } + + private boolean _containsKey(Object key) { + return lastKey1 == key || lastKey2 == key; + } + +} diff --git a/infer/models/java/src/java/util/Properties.java b/infer/models/java/src/java/util/Properties.java new file mode 100644 index 000000000..1cebb4d4b --- /dev/null +++ b/infer/models/java/src/java/util/Properties.java @@ -0,0 +1,61 @@ +package java.util; + +import com.facebook.infer.models.InferUndefined; + +import javax.xml.parsers.DocumentBuilder; +import java.io.*; + +public class Properties extends Hashtable { + + private static long serialVersionUID; + + private transient DocumentBuilder builder; + + private static String PROP_DTD_NAME; + + private static String PROP_DTD; + + protected Properties defaults; + + private static int NONE, SLASH, UNICODE, CONTINUE, KEY_DONE, IGNORE; + + + public Properties() { + } + + public Properties(Properties defaults) { + this.defaults = defaults; + } + + public synchronized void load(Reader reader) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public synchronized void load(InputStream inStream) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void store(Writer writer, String comments) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void store(OutputStream out, String comments) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public synchronized void loadFromXML(InputStream in) throws IOException, + InvalidPropertiesFormatException { + in.close(); + } + + public synchronized void storeToXML(OutputStream os, String comment) + throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public synchronized void storeToXML(OutputStream os, String comment, + String encoding) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + +} diff --git a/infer/models/java/src/java/util/Scanner.java b/infer/models/java/src/java/util/Scanner.java new file mode 100644 index 000000000..02de10f78 --- /dev/null +++ b/infer/models/java/src/java/util/Scanner.java @@ -0,0 +1,52 @@ +package java.util; + +import com.facebook.infer.models.InferUndefined; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.IOException; +import java.lang.IllegalArgumentException; + + +public class Scanner extends Object { + + InputStream src; + private void init(InputStream source) { + src = source; + } + public Scanner(InputStream source) { + init(source); + } + + public Scanner(InputStream source, String charsetName) throws IllegalArgumentException { + if (InferUndefined.boolean_undefined()) { + init(source); + } else { + throw new IllegalArgumentException(); + } + } + + public Scanner(File source) throws FileNotFoundException { + init(new FileInputStream(source)); + } + + public Scanner(File source, String charsetName) + throws FileNotFoundException, IllegalArgumentException { + if (InferUndefined.boolean_undefined()) { + init(new FileInputStream(source)); + } else { + throw new IllegalArgumentException(); + } + } + + public void close() { + try { + if (src != null) { + src.close(); + } + } catch (IOException e) { + + } + } +} diff --git a/infer/models/java/src/java/util/Vector.java b/infer/models/java/src/java/util/Vector.java new file mode 100644 index 000000000..c606efa2d --- /dev/null +++ b/infer/models/java/src/java/util/Vector.java @@ -0,0 +1,42 @@ +package java.util; + +import com.facebook.infer.models.InferUndefined; + +public class Vector extends AbstractList { + + private static long serialVersionUID; + protected int elementCount; + protected Object[] elementData; + protected int capacityIncrement; + private static int DEFAULT_SIZE; + + E elementData(int index) { + return (E) elementData[index]; + } + + public Enumeration elements() { + return new Enumeration() { + int count; + + public boolean hasMoreElements() { + return count > 0; + } + + public E nextElement() { + if (count > 0) + return (E) InferUndefined.object_undefined(); + else + throw new NoSuchElementException(); + } + }; + } + + public E get(int index) { + return elementData(index); + } + + public int size() { + return InferUndefined.nonneg_int(); + } + +} diff --git a/infer/models/java/src/java/util/jar/JarFile.java b/infer/models/java/src/java/util/jar/JarFile.java new file mode 100644 index 000000000..c1be0a8d7 --- /dev/null +++ b/infer/models/java/src/java/util/jar/JarFile.java @@ -0,0 +1,54 @@ +package java.util.jar; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + + +public class JarFile extends ZipFile { + + public static String MANIFEST_NAME; + static String META_DIR; + private Manifest manifest; + private ZipEntry manifestEntry; + + JarVerifier verifier; + + private boolean closed; + + public JarFile(String name) throws IOException { + super(name); + } + + public JarFile(String name, boolean verify) throws IOException { + super(name); + } + + public JarFile(File file) throws IOException { + super(""); + } + + public JarFile(File file, boolean verify) throws IOException { + this(file, verify, 0); + } + + public JarFile(File file, boolean verify, int mode) throws IOException { + super(""); + } + + public Manifest getManifest() throws IOException { + throw new IOException(); + } + + public synchronized InputStream getInputStream(ZipEntry ze) + throws IOException { + return super.getInputStream(ze); + } + + public void close() throws IOException { + super.close(); + } + +} diff --git a/infer/models/java/src/java/util/jar/JarInputStream.java b/infer/models/java/src/java/util/jar/JarInputStream.java new file mode 100644 index 000000000..e63679671 --- /dev/null +++ b/infer/models/java/src/java/util/jar/JarInputStream.java @@ -0,0 +1,36 @@ +package java.util.jar; + +import com.facebook.infer.models.InferUndefined; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.zip.ZipInputStream; + +public class JarInputStream extends ZipInputStream { + + private Manifest manifest; + + private boolean eos; + + private JarEntry mEntry; + + private JarEntry jarEntry; + + private boolean isMeta; + + private JarVerifier verifier; + + private OutputStream verStream; + + public JarInputStream(InputStream in) throws IOException { + super(in); + InferUndefined.can_throw_ioexception_void(); + } + + public JarInputStream(InputStream in, boolean verify) throws IOException { + super(in); + InferUndefined.can_throw_ioexception_void(); + } + +} diff --git a/infer/models/java/src/java/util/jar/JarOutputStream.java b/infer/models/java/src/java/util/jar/JarOutputStream.java new file mode 100644 index 000000000..0ea6b53f8 --- /dev/null +++ b/infer/models/java/src/java/util/jar/JarOutputStream.java @@ -0,0 +1,28 @@ +package java.util.jar; + +import com.facebook.infer.models.InferUndefined; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +public class JarOutputStream extends ZipOutputStream { + + private Manifest manifest; + + public JarOutputStream(OutputStream out, Manifest man) throws IOException { + super(out); + InferUndefined.can_throw_ioexception_void(); + } + + public JarOutputStream(OutputStream out) throws IOException { + super(out); + InferUndefined.can_throw_ioexception_void(); + } + + public void putNextEntry(ZipEntry ze) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + +} diff --git a/infer/models/java/src/java/util/zip/CheckedInputStream.java b/infer/models/java/src/java/util/zip/CheckedInputStream.java new file mode 100644 index 000000000..15f984ff3 --- /dev/null +++ b/infer/models/java/src/java/util/zip/CheckedInputStream.java @@ -0,0 +1,32 @@ +package java.util.zip; + +import com.facebook.infer.models.InferUndefined; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class CheckedInputStream extends FilterInputStream { + + private Checksum check; + + public CheckedInputStream(InputStream in, Checksum cksum) { + super(in); + } + + public int read() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(byte b[]) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(byte b[], int off, int len) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public long skip(long n) throws IOException { + return InferUndefined.can_throw_ioexception_long(); + } +} diff --git a/infer/models/java/src/java/util/zip/CheckedOutputStream.java b/infer/models/java/src/java/util/zip/CheckedOutputStream.java new file mode 100644 index 000000000..afeb6dc47 --- /dev/null +++ b/infer/models/java/src/java/util/zip/CheckedOutputStream.java @@ -0,0 +1,28 @@ +package java.util.zip; + +import com.facebook.infer.models.InferUndefined; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +public class CheckedOutputStream extends FilterOutputStream { + + private Checksum check; + + public CheckedOutputStream(OutputStream out, Checksum cksum) { + super(out); + } + + public void write(int b) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(byte b[], int off, int len) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(byte b[]) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } +} diff --git a/infer/models/java/src/java/util/zip/DeflaterInputStream.java b/infer/models/java/src/java/util/zip/DeflaterInputStream.java new file mode 100644 index 000000000..acd364966 --- /dev/null +++ b/infer/models/java/src/java/util/zip/DeflaterInputStream.java @@ -0,0 +1,51 @@ +package java.util.zip; + +import com.facebook.infer.models.InferUndefined; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class DeflaterInputStream extends FilterInputStream { + + + public DeflaterInputStream(InputStream in) { + super(in); + } + + public DeflaterInputStream(InputStream in, Deflater defl) { + super(in); + } + + public DeflaterInputStream(InputStream in, Deflater defl, int bufLen) { + super(in); + } + + public int available() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public void close() throws IOException { + super.close(); + } + + public int read() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(byte b[]) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(byte b[], int off, int len) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public void reset() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public long skip(long n) throws IOException { + return InferUndefined.can_throw_ioexception_long(); + } +} diff --git a/infer/models/java/src/java/util/zip/DeflaterOutputStream.java b/infer/models/java/src/java/util/zip/DeflaterOutputStream.java new file mode 100644 index 000000000..b3a959bcd --- /dev/null +++ b/infer/models/java/src/java/util/zip/DeflaterOutputStream.java @@ -0,0 +1,53 @@ +package java.util.zip; + +import com.facebook.infer.models.InferUndefined; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +public class DeflaterOutputStream extends FilterOutputStream { + + public DeflaterOutputStream(OutputStream out, Deflater def, int size) { + super(out); + } + + public DeflaterOutputStream(OutputStream out, Deflater def) { + super(out); + } + + boolean usesDefaultDeflater; + + public DeflaterOutputStream(OutputStream out) { + super(out); + } + + public void close() throws IOException { + super.close(); + } + + public void deflate() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void finish() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void flush() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(byte b[], int off, int len) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(byte b[]) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(int b) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + +} diff --git a/infer/models/java/src/java/util/zip/GZIPInputStream.java b/infer/models/java/src/java/util/zip/GZIPInputStream.java new file mode 100644 index 000000000..1cfb76339 --- /dev/null +++ b/infer/models/java/src/java/util/zip/GZIPInputStream.java @@ -0,0 +1,36 @@ +package java.util.zip; + +import com.facebook.infer.models.InferUndefined; + +import java.io.IOException; +import java.io.InputStream; + +public class GZIPInputStream extends InflaterInputStream { + + private static int FCOMMENT; + private static int FEXTRA; + private static int FHCRC; + private static int FNAME; + + public static int GZIP_MAGIC; + protected CRC32 crc; + protected boolean eos; + + public GZIPInputStream(InputStream in, int size) throws IOException { + super(in); + if (!InferUndefined.boolean_undefined()) { + throw new IOException(); + } + } + + public GZIPInputStream(InputStream in) throws IOException { + super(in); + if (!InferUndefined.boolean_undefined()) { + throw new IOException(); + } + } + + public void close() throws IOException { + super.close(); + } +} diff --git a/infer/models/java/src/java/util/zip/GZIPOutputStream.java b/infer/models/java/src/java/util/zip/GZIPOutputStream.java new file mode 100644 index 000000000..d0d11e4db --- /dev/null +++ b/infer/models/java/src/java/util/zip/GZIPOutputStream.java @@ -0,0 +1,26 @@ +package java.util.zip; + +import com.facebook.infer.models.InferUndefined; + +import java.io.IOException; +import java.io.OutputStream; + +public class GZIPOutputStream extends DeflaterOutputStream { + + protected CRC32 crc; + + public GZIPOutputStream(OutputStream out, int size) throws IOException { + super(out); + if (!InferUndefined.boolean_undefined()) { + throw new IOException(); + } + } + + public GZIPOutputStream(OutputStream out) throws IOException { + super(out); + if (!InferUndefined.boolean_undefined()) { + throw new IOException(); + } + } + +} diff --git a/infer/models/java/src/java/util/zip/InflaterInputStream.java b/infer/models/java/src/java/util/zip/InflaterInputStream.java new file mode 100644 index 000000000..2da154390 --- /dev/null +++ b/infer/models/java/src/java/util/zip/InflaterInputStream.java @@ -0,0 +1,52 @@ +package java.util.zip; + +import com.facebook.infer.models.InferUndefined; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class InflaterInputStream extends FilterInputStream { + + public InflaterInputStream(InputStream in, Inflater inf, int size) { + super(in); + } + + public InflaterInputStream(InputStream in, Inflater inf) { + super(in); + } + + public InflaterInputStream(InputStream in) { + super(in); + } + + public int available() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public void close() throws IOException { + super.close(); + } + + public int read() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(byte b[]) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(byte b[], int off, int len) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public void reset() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + + public long skip(long n) throws IOException { + return InferUndefined.can_throw_ioexception_long(); + } + +} diff --git a/infer/models/java/src/java/util/zip/InflaterOutputStream.java b/infer/models/java/src/java/util/zip/InflaterOutputStream.java new file mode 100644 index 000000000..4ad55a47e --- /dev/null +++ b/infer/models/java/src/java/util/zip/InflaterOutputStream.java @@ -0,0 +1,47 @@ +package java.util.zip; + +import com.facebook.infer.models.InferUndefined; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +public class InflaterOutputStream extends FilterOutputStream { + + public InflaterOutputStream(OutputStream out) { + super(out); + } + + public InflaterOutputStream(OutputStream out, Inflater infl) { + super(out); + } + + public InflaterOutputStream(OutputStream out, Inflater infl, int bufLen) { + super(out); + } + + public void close() throws IOException { + super.close(); + } + + public void finish() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void flush() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(byte b[], int off, int len) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(byte b[]) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(int b) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + +} diff --git a/infer/models/java/src/java/util/zip/ZipFile.java b/infer/models/java/src/java/util/zip/ZipFile.java new file mode 100644 index 000000000..d3c669f71 --- /dev/null +++ b/infer/models/java/src/java/util/zip/ZipFile.java @@ -0,0 +1,93 @@ +package java.util.zip; + +import com.facebook.infer.models.InferBuiltins; +import com.facebook.infer.models.InferUndefined; +import dalvik.system.CloseGuard; + +import java.io.*; +import java.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.NoSuchElementException; + +public class ZipFile { + static int GPBF_ENCRYPTED_FLAG; + static int GPBF_DATA_DESCRIPTOR_FLAG; + + static int GPBF_UTF8_FLAG; + static int GPBF_UNSUPPORTED_MASK; + public static int OPEN_READ; + public static int OPEN_DELETE; + + private String filename; + private File fileToDeleteOnClose; + private RandomAccessFile raf; + private LinkedHashMap entries; + private String comment; + private CloseGuard guard; + + + public ZipFile(String name) throws IOException { + this.filename = new String(); + InferUndefined.can_throw_ioexception_void(); + //Had to throw before setting attribute else + // whenInferRunsOnJarFileClosedThenResourceLeakIsNotFound fails + InferBuiltins.__set_file_attribute(this.filename); + } + + public ZipFile(File file, int mode) throws IOException { + this.filename = new String(); + InferUndefined.can_throw_ioexception_void(); + InferBuiltins.__set_file_attribute(this.filename); + } + + public ZipFile(File file) throws ZipException, IOException { + this(file, 0); + } + + public InputStream getInputStream(ZipEntry entry) throws IOException { + FileInputStream in = new FileInputStream(""); + return new InflaterInputStream(in, null, 0) { + private boolean isClosed = false; + + public void close() throws IOException { + super.close(); + } + + protected void fill() throws IOException { + } + + private boolean eof; + + public int available() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + }; + } + + public void close() throws IOException { + InferBuiltins.__set_mem_attribute(this.filename); + InferUndefined.can_throw_ioexception_void(); + } + + protected void finalize() throws IOException { + close(); + } + + public Enumeration entries() { + + return new Enumeration() { + private boolean hasEls; + + public boolean hasMoreElements() { + return hasEls; + } + + public ZipEntry nextElement() throws NoSuchElementException { + if (hasEls) + return new ZipEntry(""); + else + throw new NoSuchElementException(); + } + }; + } +} diff --git a/infer/models/java/src/java/util/zip/ZipInputStream.java b/infer/models/java/src/java/util/zip/ZipInputStream.java new file mode 100644 index 000000000..3412f44c8 --- /dev/null +++ b/infer/models/java/src/java/util/zip/ZipInputStream.java @@ -0,0 +1,39 @@ +package java.util.zip; + +import com.facebook.infer.models.InferUndefined; + +import java.io.BufferedInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class ZipInputStream extends InflaterInputStream { + + private ZipEntry currentEntry; + + public ZipInputStream(InputStream in) { + super(in); + } + + public ZipEntry getNextEntry() throws IOException { + boolean undef = InferUndefined.boolean_undefined(); + if (undef) { + return currentEntry; + } else + throw new IOException(); + } + + public void closeEntry() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void close() throws IOException { + if (in != null) + if (in instanceof FileInputStream) { + ((FileInputStream) in).close(); + } else if (in instanceof BufferedInputStream) { + ((BufferedInputStream) in).close(); + } + } + +} diff --git a/infer/models/java/src/java/util/zip/ZipOutputStream.java b/infer/models/java/src/java/util/zip/ZipOutputStream.java new file mode 100644 index 000000000..dc5eb89c6 --- /dev/null +++ b/infer/models/java/src/java/util/zip/ZipOutputStream.java @@ -0,0 +1,55 @@ +package java.util.zip; + +import com.facebook.infer.models.InferUndefined; + +import java.io.*; +import java.util.HashSet; + +public class ZipOutputStream extends DeflaterOutputStream { + + public static int DEFLATED; + + public static int STORED; + + private static int ZIP_VERSION_2_0; + + private byte[] commentBytes; + + private HashSet entries; + + private int defaultCompressionMethod; + + private int compressionLevel; + + private ByteArrayOutputStream cDir; + + private ZipEntry currentEntry; + + private CRC32 crc; + + private int offset, curOffset, nameLength; + + private byte[] nameBytes; + + public ZipOutputStream(OutputStream out) { + super(out); + } + + public void putNextEntry(ZipEntry e) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void closeEntry() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void close() throws IOException { + if (out != null) { + if (out instanceof FileOutputStream) { + ((FileOutputStream) out).close(); + } else if (out instanceof BufferedOutputStream) { + ((BufferedOutputStream) out).close(); + } + } + } +} diff --git a/infer/models/java/src/javax/crypto/CipherInputStream.java b/infer/models/java/src/javax/crypto/CipherInputStream.java new file mode 100644 index 000000000..282f46f44 --- /dev/null +++ b/infer/models/java/src/javax/crypto/CipherInputStream.java @@ -0,0 +1,52 @@ +package javax.crypto; + +import com.facebook.infer.models.InferUndefined; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class CipherInputStream extends FilterInputStream { + + private static int I_BUFFER_SIZE; + + private Cipher cipher; + private byte[] inputBuffer; + private byte[] outputBuffer; + private int outputIndex; + private int outputLength; + private boolean finished; + + public CipherInputStream(InputStream is, Cipher c) { + super(is); + } + + protected CipherInputStream(InputStream is) { + super(is); + } + + public int available() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public void close() throws IOException { + super.close(); + } + + public int read() throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(byte b[]) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public int read(byte b[], int off, int len) throws IOException { + return InferUndefined.can_throw_ioexception_int(); + } + + public long skip(long n) throws IOException { + return InferUndefined.can_throw_ioexception_long(); + } + +} diff --git a/infer/models/java/src/javax/crypto/CipherOutputStream.java b/infer/models/java/src/javax/crypto/CipherOutputStream.java new file mode 100644 index 000000000..5f29eb7e3 --- /dev/null +++ b/infer/models/java/src/javax/crypto/CipherOutputStream.java @@ -0,0 +1,40 @@ +package javax.crypto; + +import com.facebook.infer.models.InferUndefined; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +public class CipherOutputStream extends FilterOutputStream { + + private Cipher cipher; + + public CipherOutputStream(OutputStream os, Cipher c) { + super(os); + } + + protected CipherOutputStream(OutputStream os) { + super(os); + } + + public void close() throws IOException { + super.close(); + } + + public void write(int b) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(byte b[]) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void write(byte b[], int off, int len) throws IOException { + InferUndefined.can_throw_ioexception_void(); + } + + public void flush() throws IOException { + InferUndefined.can_throw_ioexception_void(); + } +} diff --git a/infer/models/java/src/javax/net/ssl/HttpsURLConnection.java b/infer/models/java/src/javax/net/ssl/HttpsURLConnection.java new file mode 100644 index 000000000..c19ba2e89 --- /dev/null +++ b/infer/models/java/src/javax/net/ssl/HttpsURLConnection.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.net.ssl; + +import com.facebook.infer.models.InferBuiltins; + +import java.net.HttpURLConnection; +import java.net.URL; + +public class HttpsURLConnection extends HttpURLConnection { + + protected HostnameVerifier hostnameVerifier; + + private SSLSocketFactory sslSocketFactory; + + /** + * Creates an HttpsURLConnection using the + * URL specified. + * + * @param url the URL + */ + public HttpsURLConnection(URL url) { + super(url); + } + + public void disconnect() { + InferBuiltins.__set_mem_attribute(method); + } + +} diff --git a/infer/models/java/src/junit/framework/Assert.java b/infer/models/java/src/junit/framework/Assert.java new file mode 100644 index 000000000..9d8a19a61 --- /dev/null +++ b/infer/models/java/src/junit/framework/Assert.java @@ -0,0 +1,28 @@ +package junit.framework; + +public class Assert { + + public static void assume(boolean condition) { + if (!condition) { + while (true) { + } + } + } + + public static void assertTrue(boolean condition) { + assume(condition); + } + + public static void assertTrue(String message, boolean condition) { + assume(condition); + } + + public static void assertFalse(boolean condition) { + assume(!condition); + } + + public static void assertFalse(String message, boolean condition) { + assume(!condition); + } + +} diff --git a/infer/models/objc/Makefile b/infer/models/objc/Makefile new file mode 100644 index 000000000..67f9b6daa --- /dev/null +++ b/infer/models/objc/Makefile @@ -0,0 +1,24 @@ +SHELL := /bin/bash +CWD = $(shell pwd) +BINDIR = $(CWD)/../../bin +LIBDIR = $(CWD)/../../lib +LIB_SPECS = $(LIBDIR)/specs +OBJC_MODELS_FILE = $(LIB_SPECS)/objc_models + +INFER = ANALYZE_MODELS=1 $(BINDIR)/infer + +default: run_infer + +.PHONY: run_infer install clean + +run_infer: clean + $(INFER) -o $(CWD)/out --models_mode --no_failures_allowed -- make -C src -j + +install: run_infer + cp out/specs/*.specs $(LIB_SPECS) + touch $(OBJC_MODELS_FILE) + rm -rf out + +clean: + if [ -a $(OBJC_MODELS_FILE) ];then rm $(OBJC_MODELS_FILE);fi + make -C src clean diff --git a/infer/models/objc/src/CoreFoundation/CFArray.c b/infer/models/objc/src/CoreFoundation/CFArray.c new file mode 100644 index 000000000..df8dd3387 --- /dev/null +++ b/infer/models/objc/src/CoreFoundation/CFArray.c @@ -0,0 +1,44 @@ +#import +#import + +CFArrayRef __cf_alloc(CFArrayRef); +CFArrayRef __cf_non_null_alloc(CFArrayRef); + +CFArrayRef CFArrayCreate ( CFAllocatorRef allocator, + const void **values, + CFIndex numValues, + const CFArrayCallBacks *callBacks ) { + CFArrayRef c; + return __cf_alloc(c); +} + +CFArrayRef CFNetworkCopyProxiesForURL ( CFURLRef url, CFDictionaryRef proxySettings ) { + CFArrayRef c; + return __cf_alloc(c); +} + +CFArrayRef CFStringCreateArrayWithFindResults ( CFAllocatorRef alloc, + CFStringRef theString, + CFStringRef stringToFind, + CFRange rangeToSearch, + CFStringCompareFlags compareOptions ) { + CFArrayRef c; + return __cf_alloc(c); +} + +CFArrayRef CFPreferencesCopyKeyList ( CFStringRef applicationID, + CFStringRef userName, + CFStringRef hostName ) { + CFArrayRef c; + return __cf_non_null_alloc(c); +} + +CFArrayRef CNCopySupportedInterfaces ( void ) { + CFArrayRef c; + return __cf_non_null_alloc(c); +} + +CFArrayRef ABAddressBookCopyArrayOfAllPeople ( ABAddressBookRef addressBook ) { + CFArrayRef c; + return __cf_non_null_alloc(c); +} diff --git a/infer/models/objc/src/CoreFoundation/CFBinaryHeap.c b/infer/models/objc/src/CoreFoundation/CFBinaryHeap.c new file mode 100644 index 000000000..7543a1d32 --- /dev/null +++ b/infer/models/objc/src/CoreFoundation/CFBinaryHeap.c @@ -0,0 +1,12 @@ +#import + +CFBinaryHeapRef __cf_alloc(CFBinaryHeapRef); + + +CFBinaryHeapRef CFBinaryHeapCreate ( CFAllocatorRef allocator, + CFIndex capacity, + const CFBinaryHeapCallBacks *callBacks, + const CFBinaryHeapCompareContext *compareContext) { + CFBinaryHeapRef c; + return __cf_alloc(c); +} diff --git a/infer/models/objc/src/CoreFoundation/CFBitVector.c b/infer/models/objc/src/CoreFoundation/CFBitVector.c new file mode 100644 index 000000000..7440f49ca --- /dev/null +++ b/infer/models/objc/src/CoreFoundation/CFBitVector.c @@ -0,0 +1,11 @@ +#import + +CFBitVectorRef __cf_alloc(CFBitVectorRef); +CFBitVectorRef __cf_non_null_alloc(CFBitVectorRef); + +CFBitVectorRef CFBitVectorCreate ( CFAllocatorRef allocator, + const UInt8 *bytes, + CFIndex numBits ) { + CFBitVectorRef c; + return __cf_non_null_alloc(c); +} diff --git a/infer/models/objc/src/CoreFoundation/CFDate.c b/infer/models/objc/src/CoreFoundation/CFDate.c new file mode 100644 index 000000000..3969edb81 --- /dev/null +++ b/infer/models/objc/src/CoreFoundation/CFDate.c @@ -0,0 +1,10 @@ +#import + +CFDateRef __cf_alloc(CFDateRef); +CFDateRef __cf_non_null_alloc(CFDateRef); + + +CFDateRef CFDateCreate ( CFAllocatorRef allocator, CFAbsoluteTime at ) { + CFDateRef c; + return __cf_non_null_alloc(c); +} diff --git a/infer/models/objc/src/CoreFoundation/CFDictionary.c b/infer/models/objc/src/CoreFoundation/CFDictionary.c new file mode 100644 index 000000000..c7c8f3d03 --- /dev/null +++ b/infer/models/objc/src/CoreFoundation/CFDictionary.c @@ -0,0 +1,65 @@ +#import +#import +#import + +CFDictionaryRef __cf_non_null_alloc(CFDictionaryRef); + +CFDictionaryRef __cf_alloc(CFDictionaryRef); + +CFDictionaryRef CGImageSourceCopyPropertiesAtIndex ( CGImageSourceRef isrc, + size_t index, + CFDictionaryRef options ) +{ + CFDictionaryRef c; + return __cf_non_null_alloc(c); +} + + +CFDictionaryRef CFDictionaryCreate ( CFAllocatorRef allocator, + const void **keys, + const void **values, + CFIndex numValues, + const CFDictionaryKeyCallBacks *keyCallBacks, + const CFDictionaryValueCallBacks *valueCallBacks ) { + CFDictionaryRef c; + return __cf_alloc(c); +} + +CFDictionaryRef CFDictionaryCreateCopy ( CFAllocatorRef allocator, + CFDictionaryRef theDict ) { + CFDictionaryRef c; + return __cf_alloc(c); +} + +CFDictionaryRef CFNetworkCopySystemProxySettings ( void ) { + CFDictionaryRef c; + return __cf_non_null_alloc(c); +} + +CFDictionaryRef CGImageSourceCopyProperties ( CGImageSourceRef isrc, CFDictionaryRef options ) { + CFDictionaryRef c; + return __cf_non_null_alloc(c); +} + +CFDictionaryRef CMCopyDictionaryOfAttachments ( CFAllocatorRef allocator, + CMAttachmentBearerRef target, + CMAttachmentMode attachmentMode ) { + CFDictionaryRef c; + return __cf_alloc(c); +} + +CFDictionaryRef CFHTTPMessageCopyAllHeaderFields ( CFHTTPMessageRef message ) { + CFDictionaryRef c; + return __cf_alloc(c); +} + +CFDictionaryRef CNCopyCurrentNetworkInfo ( CFStringRef interfaceName ) { + CFDictionaryRef c; + return __cf_non_null_alloc(c); +} + +CFDictionaryRef CMTimeCopyAsDictionary (CMTime time, + CFAllocatorRef allocator ) { + CFDictionaryRef c; + return __cf_alloc(c); +} diff --git a/infer/models/objc/src/CoreFoundation/CFError.c b/infer/models/objc/src/CoreFoundation/CFError.c new file mode 100644 index 000000000..bfd1432b7 --- /dev/null +++ b/infer/models/objc/src/CoreFoundation/CFError.c @@ -0,0 +1,15 @@ +#import + +CFErrorRef __cf_alloc(CFErrorRef); +CFErrorRef __cf_non_null_alloc(CFErrorRef); + + +CFErrorRef CFReadStreamCopyError ( CFReadStreamRef stream ) { + CFErrorRef c; + return __cf_alloc(c); +} + +CFErrorRef CFWriteStreamCopyError ( CFWriteStreamRef stream ) { + CFErrorRef c; + return __cf_alloc(c); +} diff --git a/infer/models/objc/src/CoreFoundation/CFHTTPMessage.c b/infer/models/objc/src/CoreFoundation/CFHTTPMessage.c new file mode 100644 index 000000000..c058717c9 --- /dev/null +++ b/infer/models/objc/src/CoreFoundation/CFHTTPMessage.c @@ -0,0 +1,32 @@ +#import + +CFHTTPMessageRef __cf_alloc(CFHTTPMessageRef); + + +CFHTTPMessageRef CFHTTPMessageCreateCopy ( CFAllocatorRef alloc, + CFHTTPMessageRef message ) { + CFHTTPMessageRef c; + return __cf_alloc(c); +} + +CFHTTPMessageRef CFHTTPMessageCreateEmpty ( CFAllocatorRef alloc, + Boolean isRequest ) { + CFHTTPMessageRef c; + return __cf_alloc(c); +} + +CFHTTPMessageRef CFHTTPMessageCreateRequest ( CFAllocatorRef alloc, + CFStringRef requestMethod, + CFURLRef url, + CFStringRef httpVersion ) { + CFHTTPMessageRef c; + return __cf_alloc(c); +} + +CFHTTPMessageRef CFHTTPMessageCreateResponse ( CFAllocatorRef alloc, + CFIndex statusCode, + CFStringRef statusDescription, + CFStringRef httpVersion ) { + CFHTTPMessageRef c; + return __cf_alloc(c); +} diff --git a/infer/models/objc/src/CoreFoundation/CFLocale.c b/infer/models/objc/src/CoreFoundation/CFLocale.c new file mode 100644 index 000000000..bf9a5b44c --- /dev/null +++ b/infer/models/objc/src/CoreFoundation/CFLocale.c @@ -0,0 +1,15 @@ +#import + +CFLocaleRef __cf_alloc(CFLocaleRef); +CFLocaleRef __cf_non_null_alloc(CFLocaleRef); + +CFLocaleRef CFLocaleCopyCurrent(void) { + CFLocaleRef c; + return __cf_non_null_alloc(c); +} + +CFLocaleRef CFLocaleCreate ( CFAllocatorRef allocator, + CFStringRef localeIdentifier ) { + CFLocaleRef c; + return __cf_alloc(c); +} diff --git a/infer/models/objc/src/CoreFoundation/CFMutableArray.c b/infer/models/objc/src/CoreFoundation/CFMutableArray.c new file mode 100644 index 000000000..8f4ac9011 --- /dev/null +++ b/infer/models/objc/src/CoreFoundation/CFMutableArray.c @@ -0,0 +1,11 @@ +#import + +CFMutableArrayRef __cf_alloc(CFMutableArrayRef); +CFMutableArrayRef __cf_non_null_alloc(CFMutableArrayRef); + +CFMutableArrayRef CFArrayCreateMutable ( CFAllocatorRef allocator, + CFIndex capacity, + const CFArrayCallBacks *callBacks ) { + CFMutableArrayRef c; + return __cf_alloc(c); +} diff --git a/infer/models/objc/src/CoreFoundation/CFMutableAttributedString.c b/infer/models/objc/src/CoreFoundation/CFMutableAttributedString.c new file mode 100644 index 000000000..d0c28b061 --- /dev/null +++ b/infer/models/objc/src/CoreFoundation/CFMutableAttributedString.c @@ -0,0 +1,9 @@ +#import + +CFMutableAttributedStringRef __cf_non_null_alloc(CFMutableAttributedStringRef); + +CFMutableAttributedStringRef CFAttributedStringCreateMutable (CFAllocatorRef alloc, + CFIndex maxLength ) { + CFMutableAttributedStringRef c; + return __cf_non_null_alloc(c); +} diff --git a/infer/models/objc/src/CoreFoundation/CFMutableDictionary.c b/infer/models/objc/src/CoreFoundation/CFMutableDictionary.c new file mode 100644 index 000000000..5ba7765f3 --- /dev/null +++ b/infer/models/objc/src/CoreFoundation/CFMutableDictionary.c @@ -0,0 +1,22 @@ +#import +#import + +CFMutableDictionaryRef __cf_non_null_alloc(CFMutableDictionaryRef); + +CFMutableDictionaryRef __cf_alloc(CFMutableDictionaryRef); + +CFMutableDictionaryRef CFDictionaryCreateMutable ( CFAllocatorRef allocator, + CFIndex capacity, + const CFDictionaryKeyCallBacks *keyCallBacks, + const CFDictionaryValueCallBacks *valueCallBacks ) { + + CFMutableDictionaryRef c; + return __cf_alloc(c); +} + +CFMutableDictionaryRef CFDictionaryCreateMutableCopy ( CFAllocatorRef allocator, + CFIndex capacity, + CFDictionaryRef theDict ) { + CFMutableDictionaryRef c; + return __cf_non_null_alloc(c); +} diff --git a/infer/models/objc/src/CoreFoundation/CFMutableSet.c b/infer/models/objc/src/CoreFoundation/CFMutableSet.c new file mode 100644 index 000000000..3c10a7864 --- /dev/null +++ b/infer/models/objc/src/CoreFoundation/CFMutableSet.c @@ -0,0 +1,18 @@ +#import + +CFMutableSetRef __cf_alloc(CFMutableSetRef); +CFMutableSetRef __cf_non_null_alloc(CFMutableSetRef); + +CFMutableSetRef CFSetCreateMutable (CFAllocatorRef allocator, + CFIndex capacity, + const CFSetCallBacks *callBacks) { + CFMutableSetRef c; + return __cf_alloc(c); +} + +CFMutableSetRef CFSetCreateMutableCopy ( CFAllocatorRef allocator, + CFIndex capacity, + CFSetRef theSet ) { + CFMutableSetRef c; + return __cf_non_null_alloc(c); +} diff --git a/infer/models/objc/src/CoreFoundation/CFNumber.c b/infer/models/objc/src/CoreFoundation/CFNumber.c new file mode 100644 index 000000000..187aa3e03 --- /dev/null +++ b/infer/models/objc/src/CoreFoundation/CFNumber.c @@ -0,0 +1,11 @@ +#import + +CFNumberRef __cf_alloc(CFNumberRef); + + +CFNumberRef CFNumberCreate ( CFAllocatorRef allocator, + CFNumberType theType, + const void *valuePtr ) { + CFNumberRef c; + return __cf_alloc(c); +} diff --git a/infer/models/objc/src/CoreFoundation/CFPreferences.c b/infer/models/objc/src/CoreFoundation/CFPreferences.c new file mode 100644 index 000000000..f67fbf5c1 --- /dev/null +++ b/infer/models/objc/src/CoreFoundation/CFPreferences.c @@ -0,0 +1,12 @@ +#import + +CFDictionaryRef __cf_alloc(CFDictionaryRef); +CFDictionaryRef __cf_non_null_alloc(CFDictionaryRef); + +CFDictionaryRef CFPreferencesCopyMultiple(CFArrayRef keysToFetch, + CFStringRef appName, + CFStringRef user, + CFStringRef host) { + CFDictionaryRef c; + return __cf_non_null_alloc(c); +} diff --git a/infer/models/objc/src/CoreFoundation/CFRunLoop.c b/infer/models/objc/src/CoreFoundation/CFRunLoop.c new file mode 100644 index 000000000..092ca6a70 --- /dev/null +++ b/infer/models/objc/src/CoreFoundation/CFRunLoop.c @@ -0,0 +1,18 @@ +#import + +CFRunLoopSourceRef __cf_alloc(CFRunLoopSourceRef); +CFRunLoopSourceRef __cf_non_null_alloc(CFRunLoopSourceRef); + +CFRunLoopSourceRef CFRunLoopSourceCreate ( CFAllocatorRef allocator, + CFIndex order, + CFRunLoopSourceContext *context ) { + CFRunLoopSourceRef c; + return __cf_non_null_alloc(c); +} + +CFRunLoopSourceRef CFSocketCreateRunLoopSource ( CFAllocatorRef allocator, + CFSocketRef s, + CFIndex order ) { + CFRunLoopSourceRef c; + return __cf_non_null_alloc(c); +} diff --git a/infer/models/objc/src/CoreFoundation/CFRunLoopObserver.c b/infer/models/objc/src/CoreFoundation/CFRunLoopObserver.c new file mode 100644 index 000000000..f330f8aef --- /dev/null +++ b/infer/models/objc/src/CoreFoundation/CFRunLoopObserver.c @@ -0,0 +1,26 @@ +#import + +CFRunLoopObserverRef __cf_alloc(CFRunLoopObserverRef); +CFRunLoopSourceRef __cf_non_null_alloc(CFRunLoopSourceRef); + +CFRunLoopObserverRef CFRunLoopObserverCreate ( CFAllocatorRef allocator, + CFOptionFlags activities, + Boolean repeats, + CFIndex order, + CFRunLoopObserverCallBack callout, + CFRunLoopObserverContext *context) { + CFRunLoopObserverRef c; + return __cf_non_null_alloc(c); +} + + +CFRunLoopObserverRef CFRunLoopObserverCreateWithHandler ( CFAllocatorRef allocator, + CFOptionFlags activities, + Boolean repeats, + CFIndex order, + void (^block)( + CFRunLoopObserverRef observer, + CFRunLoopActivity activity) ) { + CFRunLoopObserverRef c; + return __cf_non_null_alloc(c); +} diff --git a/infer/models/objc/src/CoreFoundation/CFSocket.c b/infer/models/objc/src/CoreFoundation/CFSocket.c new file mode 100644 index 000000000..a62bb00b2 --- /dev/null +++ b/infer/models/objc/src/CoreFoundation/CFSocket.c @@ -0,0 +1,14 @@ +#import + +CFSocketRef __cf_alloc(CFSocketRef); + +CFSocketRef CFSocketCreate ( CFAllocatorRef allocator, + SInt32 protocolFamily, + SInt32 socketType, + SInt32 protocol, + CFOptionFlags callBackTypes, + CFSocketCallBack callout, + const CFSocketContext *context ) { + CFSocketRef c; + return __cf_alloc(c); +} diff --git a/infer/models/objc/src/CoreFoundation/CFString.c b/infer/models/objc/src/CoreFoundation/CFString.c new file mode 100644 index 000000000..4ce090bb8 --- /dev/null +++ b/infer/models/objc/src/CoreFoundation/CFString.c @@ -0,0 +1,24 @@ +#import +#import + +CFStringRef __cf_alloc(CFStringRef); + +void __get_array_size(const UInt8); + +CFStringRef CFStringCreateWithBytesNoCopy ( + CFAllocatorRef alloc, + const UInt8 *bytes, + CFIndex numBytes, + CFStringEncoding encoding, + Boolean isExternalRepresentation, + CFAllocatorRef contentsDeallocator ) { + CFStringRef c; + CFStringRef s = __cf_alloc(c); + if (s) { + if (bytes) { + __get_array_size(bytes); + free(bytes); + } + } + return s; +} diff --git a/infer/models/objc/src/CoreFoundation/CFStringTokenizer.c b/infer/models/objc/src/CoreFoundation/CFStringTokenizer.c new file mode 100644 index 000000000..5ce5accdb --- /dev/null +++ b/infer/models/objc/src/CoreFoundation/CFStringTokenizer.c @@ -0,0 +1,13 @@ +#import + +CFStringTokenizerRef __cf_alloc(CFStringTokenizerRef); +CFStringTokenizerRef __cf_non_null_alloc(CFStringTokenizerRef); + +CFStringTokenizerRef CFStringTokenizerCreate ( CFAllocatorRef alloc, + CFStringRef string, + CFRange range, + CFOptionFlags options, + CFLocaleRef locale ) { + CFStringTokenizerRef c; + return __cf_non_null_alloc(c); +} diff --git a/infer/models/objc/src/CoreFoundation/CFUUID.c b/infer/models/objc/src/CoreFoundation/CFUUID.c new file mode 100644 index 000000000..14a4206d1 --- /dev/null +++ b/infer/models/objc/src/CoreFoundation/CFUUID.c @@ -0,0 +1,15 @@ +#import + +CFUUIDRef __cf_alloc(CFUUIDRef); +CFUUIDRef __cf_non_null_alloc(CFUUIDRef); + +CFUUIDRef CFUUIDCreate ( CFAllocatorRef alloc ){ + CFUUIDRef c; + return __cf_non_null_alloc(c); +} + +CFUUIDRef CFUUIDCreateFromUUIDBytes ( CFAllocatorRef alloc, CFUUIDBytes bytes ) { + + CFUUIDRef c; + return __cf_non_null_alloc(c); +} diff --git a/infer/models/objc/src/CoreFoundation/CTFont.c b/infer/models/objc/src/CoreFoundation/CTFont.c new file mode 100644 index 000000000..e60114071 --- /dev/null +++ b/infer/models/objc/src/CoreFoundation/CTFont.c @@ -0,0 +1,10 @@ +#import + +CTFontRef __cf_alloc(CTFontRef); + +CTFontRef CTFontCreateWithName ( CFStringRef name, + CGFloat size, + const CGAffineTransform *matrix ) { + CTFontRef c; + return __cf_alloc(c); +} diff --git a/infer/models/objc/src/CoreFoundation/CTFramesetter.c b/infer/models/objc/src/CoreFoundation/CTFramesetter.c new file mode 100644 index 000000000..4622fc5f4 --- /dev/null +++ b/infer/models/objc/src/CoreFoundation/CTFramesetter.c @@ -0,0 +1,8 @@ +#import + +CTFramesetterRef __cf_alloc(CTFramesetterRef); + +CTFramesetterRef CTFramesetterCreateWithAttributedString ( CFAttributedStringRef string ) { + CTFramesetterRef c; + return __cf_alloc(c); +} diff --git a/infer/models/objc/src/CoreFoundation/CTParagraphStyle.c b/infer/models/objc/src/CoreFoundation/CTParagraphStyle.c new file mode 100644 index 000000000..0f2951376 --- /dev/null +++ b/infer/models/objc/src/CoreFoundation/CTParagraphStyle.c @@ -0,0 +1,12 @@ +#import +#import + +CTParagraphStyleRef __cf_alloc(CTParagraphStyleRef); +CTParagraphStyleRef __cf_non_null_alloc(CTParagraphStyleRef); + + +CTParagraphStyleRef CTParagraphStyleCreate ( const CTParagraphStyleSetting *settings, + size_t settingCount ) { + CTParagraphStyleRef c; + return __cf_alloc(c); +} diff --git a/infer/models/objc/src/CoreFoundation/SCNetworkReachability.c b/infer/models/objc/src/CoreFoundation/SCNetworkReachability.c new file mode 100644 index 000000000..f8664c048 --- /dev/null +++ b/infer/models/objc/src/CoreFoundation/SCNetworkReachability.c @@ -0,0 +1,17 @@ +#import +#import "SystemConfiguration/SCNetworkReachability.h" + +SCNetworkReachabilityRef __cf_non_null_alloc(SCNetworkReachabilityRef); + +SCNetworkReachabilityRef SCNetworkReachabilityCreateWithName ( + CFAllocatorRef allocator, + const char *nodename ) { + SCNetworkReachabilityRef c; + return __cf_non_null_alloc(c); +} + +SCNetworkReachabilityRef SCNetworkReachabilityCreateWithAddress ( CFAllocatorRef allocator, + const struct sockaddr *address ) { + SCNetworkReachabilityRef c; + return __cf_non_null_alloc(c); +} diff --git a/infer/models/objc/src/CoreFoundation/SecCertificate.c b/infer/models/objc/src/CoreFoundation/SecCertificate.c new file mode 100644 index 000000000..e48fc420b --- /dev/null +++ b/infer/models/objc/src/CoreFoundation/SecCertificate.c @@ -0,0 +1,12 @@ +#import + +SecCertificateRef __cf_alloc(SecCertificateRef); + +SecCertificateRef __cf_non_null_alloc(SecCertificateRef); + + +SecCertificateRef SecCertificateCreateWithData ( CFAllocatorRef allocator, + CFDataRef data ) { + SecCertificateRef c; + return __cf_alloc(c); +} diff --git a/infer/models/objc/src/CoreFoundation/SecKey.c b/infer/models/objc/src/CoreFoundation/SecKey.c new file mode 100644 index 000000000..6b6c844f6 --- /dev/null +++ b/infer/models/objc/src/CoreFoundation/SecKey.c @@ -0,0 +1,22 @@ +#import +#import + +SecKeyRef __cf_alloc(SecKeyRef); + +SecPolicyRef __cf_non_null_alloc(SecPolicyRef); + +SecKeyRef SecTrustCopyPublicKey ( SecTrustRef trust ){ + SecKeyRef c; + return __cf_alloc(c); +} + + +SecPolicyRef SecPolicyCreateSSL ( Boolean server, CFStringRef hostname ) { + SecPolicyRef c; + return __cf_non_null_alloc(c); +} + +SecPolicyRef SecPolicyCreateBasicX509 ( void ) { + SecPolicyRef c; + return __cf_non_null_alloc(c); +} diff --git a/infer/models/objc/src/CoreGraphics/CGColor.c b/infer/models/objc/src/CoreGraphics/CGColor.c new file mode 100644 index 000000000..8d6f9fc98 --- /dev/null +++ b/infer/models/objc/src/CoreGraphics/CGColor.c @@ -0,0 +1,36 @@ +#import + +CGColorRef __cf_non_null_alloc(CGColorRef); + +void __objc_release_cf(CGColorRef); + +void CGColorRelease (CGColorRef color) { + if (color) __objc_release_cf(color); +} + +CGColorRef CGColorCreate(CGColorSpaceRef space, const CGFloat components[]) { + CGColorRef c; + return __cf_non_null_alloc(c); +} + +CGColorRef CGColorCreateCopyWithAlpha ( CGColorRef color, CGFloat alpha ) { + CGColorRef c; + return __cf_non_null_alloc(c); +} + +//FB own code + +CGColorRef FBColorCreateWithGray(CGFloat gray, CGFloat a) { + CGColorRef c; + return __cf_non_null_alloc(c); +} + +CGColorRef FBColorCreateWithRGBA(uint8_t r, uint8_t g, uint8_t b, CGFloat a) { + CGColorRef c; + return __cf_non_null_alloc(c); +} + +CGColorRef FBColorCreateWithRGB(uint8_t r, uint8_t g, uint8_t b) { + CGColorRef c; + return __cf_non_null_alloc(c); +} diff --git a/infer/models/objc/src/CoreGraphics/CGColorSpace.c b/infer/models/objc/src/CoreGraphics/CGColorSpace.c new file mode 100644 index 000000000..ac7bff85c --- /dev/null +++ b/infer/models/objc/src/CoreGraphics/CGColorSpace.c @@ -0,0 +1,19 @@ +#import + +void __objc_release_cf(CGColorSpaceRef); + +void CGColorSpaceRelease ( CGColorSpaceRef space ) { + if (space) __objc_release_cf(space); +} + +CGColorSpaceRef __cf_alloc(CGColorSpaceRef); + +CGColorSpaceRef CGColorSpaceCreateDeviceRGB ( void ) { + CGColorSpaceRef c; + return __cf_alloc(c); +} + +CGColorSpaceRef CGColorSpaceCreateDeviceGray ( void ) { + CGColorSpaceRef c; + return __cf_alloc(c); +} diff --git a/infer/models/objc/src/CoreGraphics/CGContext.c b/infer/models/objc/src/CoreGraphics/CGContext.c new file mode 100644 index 000000000..6a47096c0 --- /dev/null +++ b/infer/models/objc/src/CoreGraphics/CGContext.c @@ -0,0 +1,22 @@ +#import +#import +#import + +void __objc_release_cf(CGContextRef); + +void CGContextRelease ( CGContextRef c ){ + if (c) __objc_release_cf(c); +} + +CGContextRef __cf_alloc(CGContextRef); + +CGContextRef CGBitmapContextCreate ( void *data, + size_t width, + size_t height, + size_t bitsPerComponent, + size_t bytesPerRow, + CGColorSpaceRef space, + CGBitmapInfo bitmapInfo ) { + CGContextRef c; + return __cf_alloc(c); +} diff --git a/infer/models/objc/src/CoreGraphics/CGDataConsumer.c b/infer/models/objc/src/CoreGraphics/CGDataConsumer.c new file mode 100644 index 000000000..5222dec14 --- /dev/null +++ b/infer/models/objc/src/CoreGraphics/CGDataConsumer.c @@ -0,0 +1,8 @@ +#import + +void __objc_release_cf(CGDataConsumerRef); + +void CGDataConsumerRelease ( CGDataConsumerRef consumer ) { + if (consumer) __objc_release_cf(consumer); +} + diff --git a/infer/models/objc/src/CoreGraphics/CGDataProvider.c b/infer/models/objc/src/CoreGraphics/CGDataProvider.c new file mode 100644 index 000000000..9caf17bc3 --- /dev/null +++ b/infer/models/objc/src/CoreGraphics/CGDataProvider.c @@ -0,0 +1,42 @@ +#import + +void __objc_release_cf(CGDataProviderRef); + +void CGDataProviderRelease ( CGDataProviderRef provider ){ + if (provider) __objc_release_cf(provider); +} + +CGDataProviderRef __cf_alloc(CGDataProviderRef); + +CGDataProviderRef __cf_non_null_alloc(CGDataProviderRef); + +CGDataProviderRef CGDataProviderCreateWithCFData ( CFDataRef data ) { + CGDataProviderRef c; + return __cf_non_null_alloc(c); +} + +CGDataProviderRef CGDataProviderCreateWithData ( void *info, + const void *data, size_t size, + CGDataProviderReleaseDataCallback releaseData ) { + CGDataProviderRef c; + return __cf_non_null_alloc(c); +} + +CGDataProviderRef CGDataProviderCreateWithURL ( CFURLRef url ) { + CGDataProviderRef c; + return __cf_alloc(c); +} + + +CGDataProviderRef CGDataProviderCreateDirect ( void *info, + off_t size, + const CGDataProviderDirectCallbacks *callbacks) { + CGDataProviderRef c; + return __cf_non_null_alloc(c); +} + +CGDataProviderRef CGDataProviderCreateSequential ( void *info, + const CGDataProviderSequentialCallbacks *callbacks ) { + CGDataProviderRef c; + return __cf_non_null_alloc(c); +} diff --git a/infer/models/objc/src/CoreGraphics/CGFont.c b/infer/models/objc/src/CoreGraphics/CGFont.c new file mode 100644 index 000000000..3643bddce --- /dev/null +++ b/infer/models/objc/src/CoreGraphics/CGFont.c @@ -0,0 +1,14 @@ +#import + +void __objc_release_cf(CGFontRef); + +CGFontRef __cf_alloc(CGFontRef); + +void CGFontRelease ( CGFontRef font ) { + if (font) __objc_release_cf(font); +} + +CGFontRef CGFontCreateWithDataProvider ( CGDataProviderRef provider ) { + CGFontRef c; + return __cf_alloc(c); +} diff --git a/infer/models/objc/src/CoreGraphics/CGFunction.c b/infer/models/objc/src/CoreGraphics/CGFunction.c new file mode 100644 index 000000000..d1b74be32 --- /dev/null +++ b/infer/models/objc/src/CoreGraphics/CGFunction.c @@ -0,0 +1,8 @@ +#import + +void __objc_release_cf(CGFunctionRef); + +void CGFunctionRelease ( CGFunctionRef function ) { + if (function) __objc_release_cf(function); +} + diff --git a/infer/models/objc/src/CoreGraphics/CGGradient.c b/infer/models/objc/src/CoreGraphics/CGGradient.c new file mode 100644 index 000000000..c2b45ccd3 --- /dev/null +++ b/infer/models/objc/src/CoreGraphics/CGGradient.c @@ -0,0 +1,24 @@ +#import + +void __objc_release_cf(CGGradientRef); + +void CGGradientRelease ( CGGradientRef gradient ) { + if (gradient) __objc_release_cf(gradient); +} + +CGGradientRef __cf_non_null_alloc(CGGradientRef); + +CGGradientRef CGGradientCreateWithColors ( CGColorSpaceRef space, + CFArrayRef colors, + const CGFloat locations[]) { + CGGradientRef c; + return __cf_non_null_alloc(c); +} + +CGGradientRef CGGradientCreateWithColorComponents ( CGColorSpaceRef space, + const CGFloat components[], + const CGFloat locations[], + size_t count ) { + CGGradientRef c; + return __cf_non_null_alloc(c); +} diff --git a/infer/models/objc/src/CoreGraphics/CGImage.c b/infer/models/objc/src/CoreGraphics/CGImage.c new file mode 100644 index 000000000..b0444d86a --- /dev/null +++ b/infer/models/objc/src/CoreGraphics/CGImage.c @@ -0,0 +1,76 @@ +#import +#import + +CGImageRef __cf_non_null_alloc(CGImageRef); +CGImageRef __cf_alloc(CGImageRef); +void __objc_release_cf(CGImageRef); + +CGImageRef CGImageSourceCreateImageAtIndex ( CGImageSourceRef isrc, size_t index, CFDictionaryRef options ) +{ + CGImageRef c; + return __cf_non_null_alloc(c); +} + +CGImageRef CGImageSourceCreateThumbnailAtIndex ( CGImageSourceRef isrc, size_t index, CFDictionaryRef options ) +{ + CGImageRef c; + return __cf_non_null_alloc(c); +} + + +void CGImageRelease ( CGImageRef image ) +{ + if (image) __objc_release_cf(image); +} + +CGImageRef CGBitmapContextCreateImage ( CGContextRef context ) +{ + CGImageRef c; + return __cf_alloc(c); +} + +CGImageRef CGImageCreateCopy ( CGImageRef image ) { + CGImageRef c; + return __cf_non_null_alloc(c); +} + +CGImageRef CGImageCreateWithImageInRect ( CGImageRef image, CGRect rect ) { + CGImageRef c; + return __cf_alloc(c); +} + +CGImageRef CGImageCreateWithJPEGDataProvider ( CGDataProviderRef source, + const CGFloat decode[], + bool shouldInterpolate, + CGColorRenderingIntent intent) { + CGImageRef c; + return __cf_non_null_alloc(c); +} + +CGImageRef CGImageCreateWithPNGDataProvider ( CGDataProviderRef source, + const CGFloat decode[], + bool shouldInterpolate, + CGColorRenderingIntent intent ) { + CGImageRef c; + return __cf_non_null_alloc(c); +} + +CGImageRef CGImageCreate ( size_t width, + size_t height, + size_t bitsPerComponent, + size_t bitsPerPixel, + size_t bytesPerRow, + CGColorSpaceRef space, + CGBitmapInfo bitmapInfo, + CGDataProviderRef provider, + const CGFloat decode[], + bool shouldInterpolate, + CGColorRenderingIntent intent) { + CGImageRef c; + return __cf_non_null_alloc(c); +} + +CGImageRef CGImageCreateWithMask ( CGImageRef image, CGImageRef mask ) { + CGImageRef c; + return __cf_non_null_alloc(c); +} diff --git a/infer/models/objc/src/CoreGraphics/CGImageDestination.c b/infer/models/objc/src/CoreGraphics/CGImageDestination.c new file mode 100644 index 000000000..f83504ed3 --- /dev/null +++ b/infer/models/objc/src/CoreGraphics/CGImageDestination.c @@ -0,0 +1,21 @@ +#import +#import + +CGImageDestinationRef __cf_non_null_alloc(CGImageDestinationRef); +CGImageDestinationRef __cf_alloc(CGImageDestinationRef); + +CGImageDestinationRef CGImageDestinationCreateWithURL (CFURLRef url, + CFStringRef type, + size_t count, + CFDictionaryRef options) { + CGImageDestinationRef c; + return __cf_non_null_alloc(c); +} + +CGImageDestinationRef CGImageDestinationCreateWithData ( CFMutableDataRef data, + CFStringRef type, + size_t count, + CFDictionaryRef options) { + CGImageDestinationRef c; + return __cf_non_null_alloc(c); +} diff --git a/infer/models/objc/src/CoreGraphics/CGImageSource.c b/infer/models/objc/src/CoreGraphics/CGImageSource.c new file mode 100644 index 000000000..5051e6ee8 --- /dev/null +++ b/infer/models/objc/src/CoreGraphics/CGImageSource.c @@ -0,0 +1,37 @@ +#import +#import + +CGImageSourceRef __cf_non_null_alloc(CGImageSourceRef); +CGImageSourceRef __cf_alloc(CGImageSourceRef); +void __objc_release_cf(CGImageSourceRef); + +CGImageSourceRef CGImageSourceCreateWithData ( CFDataRef data, CFDictionaryRef options ) +{ + CGImageSourceRef c; + return __cf_non_null_alloc(c); +} + +CGImageSourceRef CGImageSourceCreateWithDataProvider ( CGDataProviderRef provider, CFDictionaryRef options ) +{ + CGImageSourceRef c; + return __cf_non_null_alloc(c); +} + +CGImageSourceRef CGImageSourceCreateWithURL ( CFURLRef url, CFDictionaryRef options ) +{ + CGImageSourceRef c; + return __cf_alloc(c); +} + +CGImageSourceRef CGImageSourceCreateIncremental ( CFDictionaryRef options ) +{ + CGImageSourceRef c; + return __cf_non_null_alloc(c); +} + +void CGImageSourceRelease ( CGImageSourceRef image ) +{ + if (image) __objc_release_cf(image); +} + + diff --git a/infer/models/objc/src/CoreGraphics/CGLayer.c b/infer/models/objc/src/CoreGraphics/CGLayer.c new file mode 100644 index 000000000..7ac546313 --- /dev/null +++ b/infer/models/objc/src/CoreGraphics/CGLayer.c @@ -0,0 +1,8 @@ +#import + +void __objc_release_cf(CGLayerRef); + +void CGLayerRelease ( CGLayerRef layer ){ + if (layer) __objc_release_cf(layer); +} + diff --git a/infer/models/objc/src/CoreGraphics/CGPDFContentStream.c b/infer/models/objc/src/CoreGraphics/CGPDFContentStream.c new file mode 100644 index 000000000..9b38f9953 --- /dev/null +++ b/infer/models/objc/src/CoreGraphics/CGPDFContentStream.c @@ -0,0 +1,8 @@ +#import + +void __objc_release_cf(CGPDFContentStreamRef); + +void CGPDFContentStreamRelease ( CGPDFContentStreamRef cs ) { + if (cs) __objc_release_cf(cs); +} + diff --git a/infer/models/objc/src/CoreGraphics/CGPDFDocument.c b/infer/models/objc/src/CoreGraphics/CGPDFDocument.c new file mode 100644 index 000000000..bd9353a4f --- /dev/null +++ b/infer/models/objc/src/CoreGraphics/CGPDFDocument.c @@ -0,0 +1,8 @@ +#import + +void __objc_release_cf(CGPDFDocumentRef); + +void CGPDFDocumentRelease ( CGPDFDocumentRef document ) { + if (document) __objc_release_cf(document); +} + diff --git a/infer/models/objc/src/CoreGraphics/CGPDFOperatorTable.c b/infer/models/objc/src/CoreGraphics/CGPDFOperatorTable.c new file mode 100644 index 000000000..5bdd01d21 --- /dev/null +++ b/infer/models/objc/src/CoreGraphics/CGPDFOperatorTable.c @@ -0,0 +1,8 @@ +#import + +void __objc_release_cf(CGPDFOperatorTableRef); + +void CGPDFOperatorTableRelease ( CGPDFOperatorTableRef table ) { + if (table) __objc_release_cf(table); +} + diff --git a/infer/models/objc/src/CoreGraphics/CGPDFPage.c b/infer/models/objc/src/CoreGraphics/CGPDFPage.c new file mode 100644 index 000000000..0cafb8d28 --- /dev/null +++ b/infer/models/objc/src/CoreGraphics/CGPDFPage.c @@ -0,0 +1,8 @@ +#import + +void __objc_release_cf(CGPDFPageRef); + +void CGPDFPageRelease ( CGPDFPageRef page ) { + if (page) __objc_release_cf(page); +} + diff --git a/infer/models/objc/src/CoreGraphics/CGPDFScanner.c b/infer/models/objc/src/CoreGraphics/CGPDFScanner.c new file mode 100644 index 000000000..d85dd4e13 --- /dev/null +++ b/infer/models/objc/src/CoreGraphics/CGPDFScanner.c @@ -0,0 +1,8 @@ +#import + +void __objc_release_cf(CGPDFScannerRef); + +void CGPDFScannerRelease ( CGPDFScannerRef scanner ) { + if (scanner) __objc_release_cf(scanner); +} + diff --git a/infer/models/objc/src/CoreGraphics/CGPath.c b/infer/models/objc/src/CoreGraphics/CGPath.c new file mode 100644 index 000000000..f16d3598c --- /dev/null +++ b/infer/models/objc/src/CoreGraphics/CGPath.c @@ -0,0 +1,45 @@ +#import +#import + +CGPathRef __cf_non_null_alloc(CGPathRef); +void __objc_release_cf(CGPathRef); + +CGMutablePathRef CGPathCreateMutable () { + CGMutablePathRef c; + return (CGMutablePathRef) __cf_non_null_alloc(c); +} + +CGPathRef CGPathCreateWithRect ( CGRect rect, const CGAffineTransform *transform ) { + CGPathRef c; + return __cf_non_null_alloc(c); +} + +void CGPathRelease ( CGPathRef path ) { + if (path) __objc_release_cf(path); +} + + +CGPathRef CGPathCreateWithEllipseInRect ( CGRect rect, + const CGAffineTransform *transform ){ + CGPathRef c; + return __cf_non_null_alloc(c); +} + +CGPathRef CGPathCreateCopy ( CGPathRef path ) { + CGPathRef c; + return __cf_non_null_alloc(c); +} + +CGPathRef CGPathCreateCopyByTransformingPath ( CGPathRef path, + const CGAffineTransform *transform ) { + CGPathRef c; + return __cf_non_null_alloc(c); +} + +CGPathRef CGPathCreateWithRoundedRect ( CGRect rect, + CGFloat cornerWidth, + CGFloat cornerHeight, + const CGAffineTransform *transform) { + CGPathRef c; + return __cf_non_null_alloc(c); +} diff --git a/infer/models/objc/src/CoreGraphics/CGPattern.c b/infer/models/objc/src/CoreGraphics/CGPattern.c new file mode 100644 index 000000000..8af2b4a20 --- /dev/null +++ b/infer/models/objc/src/CoreGraphics/CGPattern.c @@ -0,0 +1,8 @@ +#import + +void __objc_release_cf(CGPatternRef); + +void CGPatternRelease ( CGPatternRef pattern ) { + if (pattern) __objc_release_cf(pattern); +} + diff --git a/infer/models/objc/src/CoreGraphics/CGShading.c b/infer/models/objc/src/CoreGraphics/CGShading.c new file mode 100644 index 000000000..aa659711a --- /dev/null +++ b/infer/models/objc/src/CoreGraphics/CGShading.c @@ -0,0 +1,8 @@ +#import + +void __objc_release_cf(CGShadingRef); + +void CGShadingRelease ( CGShadingRef shading ) { + if (shading) __objc_release_cf(shading); +} + diff --git a/infer/models/objc/src/Makefile b/infer/models/objc/src/Makefile new file mode 100644 index 000000000..6c8d5510b --- /dev/null +++ b/infer/models/objc/src/Makefile @@ -0,0 +1,30 @@ +M_SOURCES=$(shell find . -name "*.m") +C_SOURCES=$(shell find . -name "*.c") +M_OBJECTS=$(M_SOURCES:.m=.o) +C_OBJECTS=$(C_SOURCES:.c=.o) +CC=clang +XCODE_PATH=$(shell xcode-select -p) +IPHONE_SIMULATOR_PATH=$(XCODE_PATH)/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk +FLAGS=-x objective-c -c -mios-simulator-version-min=8.2 -isysroot $(IPHONE_SIMULATOR_PATH) + +default: test + +all: test + +.PHONY: test +test: $(C_OBJECTS) $(M_OBJECTS) + echo "test called\n" + +.PHONY: configure +configure: + echo "no configure required\n" + +clean: + rm -rf $(M_OBJECTS) + rm -rf $(C_OBJECTS) + +.m.o: + $(CC) $(FLAGS) $< -o $@ + +.c.o: + $(CC) $(FLAGS) $< -o $@ diff --git a/infer/models/objc/src/NSArray.h b/infer/models/objc/src/NSArray.h new file mode 100644 index 000000000..8f9b974c9 --- /dev/null +++ b/infer/models/objc/src/NSArray.h @@ -0,0 +1,5 @@ +#import + +@interface NSArray : NSObject + +@end diff --git a/infer/models/objc/src/NSArray.m b/infer/models/objc/src/NSArray.m new file mode 100644 index 000000000..c4a479c19 --- /dev/null +++ b/infer/models/objc/src/NSArray.m @@ -0,0 +1,9 @@ +#import "NSArray.h" + +@implementation NSArray + ++ (instancetype)array { + return [NSArray alloc]; +} + +@end diff --git a/infer/models/objc/src/NSAutoreleasePool.m b/infer/models/objc/src/NSAutoreleasePool.m new file mode 100644 index 000000000..750e264ee --- /dev/null +++ b/infer/models/objc/src/NSAutoreleasePool.m @@ -0,0 +1,20 @@ + /* + * Copyright (c) 2014- Facebook. + * All rights reserved. + */ + + #pragma clang diagnostic ignored "-Wprotocol" + + #pragma clang diagnostic ignored "-Wincomplete-implementation" + + #pragma clang diagnostic ignored "-Wimplicit-function-declaration" + + #import + + @implementation NSAutoreleasePool + + - (id)init { + return (id)[self autorelease]; + } + + @end diff --git a/infer/models/objc/src/NSData.m b/infer/models/objc/src/NSData.m new file mode 100644 index 000000000..52a5de0a5 --- /dev/null +++ b/infer/models/objc/src/NSData.m @@ -0,0 +1,34 @@ +#import +#include + +NSData* __objc_alloc(NSData*); + +@interface NSData : NSObject + +@property (readonly) const void *bytes; + ++ (instancetype)dataWithBytesNoCopy:(void *)bytes length:(NSUInteger)length; ++ (instancetype)dataWithBytesNoCopy:(void *)bytes length:(NSUInteger)length freeWhenDone:(BOOL)b; + +@end + +@implementation NSData + ++ (instancetype)dataWithBytesNoCopy:(void *)bytes length:(NSUInteger)length { + return [NSData dataWithBytesNoCopy:bytes length:length freeWhenDone:YES]; +} + ++ (instancetype)dataWithBytesNoCopy:(void *)bytes length:(NSUInteger)length freeWhenDone:(BOOL)b { + NSData* data = __objc_alloc(self); + if (data) { + data->_bytes = bytes; + return data; + } + else return nil; +} + +- (void) dealloc { + if (self) + free(self->_bytes); +} +@end diff --git a/infer/models/objc/src/NSDictionary.h b/infer/models/objc/src/NSDictionary.h new file mode 100644 index 000000000..7d0e80749 --- /dev/null +++ b/infer/models/objc/src/NSDictionary.h @@ -0,0 +1,5 @@ +#import + +@interface NSDictionary : NSObject + +@end diff --git a/infer/models/objc/src/NSDictionary.m b/infer/models/objc/src/NSDictionary.m new file mode 100644 index 000000000..6a57a399e --- /dev/null +++ b/infer/models/objc/src/NSDictionary.m @@ -0,0 +1,13 @@ +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + +#pragma clang diagnostic ignored "-Wdeprecated-objc-isa-usage" + +#import "NSDictionary.h" + +@implementation NSDictionary + ++ (instancetype)dictionary { + return [NSDictionary alloc]; +} + +@end diff --git a/infer/models/objc/src/NSFileHandle.m b/infer/models/objc/src/NSFileHandle.m new file mode 100644 index 000000000..8785b47da --- /dev/null +++ b/infer/models/objc/src/NSFileHandle.m @@ -0,0 +1,39 @@ +#import +#include + +@class NSString, NSData, NSError; + +@interface NSFileHandle : NSObject + +- (void)closeFile; + +- (instancetype)initWithFileDescriptor:(int)fd closeOnDealloc:(BOOL)closeopt; + +- (instancetype)initWithFileDescriptor:(int)fd; + +@property (readonly) int fileDescriptor; + +@end + +@implementation NSFileHandle + +- (instancetype)initWithFileDescriptor:(int)fd closeOnDealloc:(BOOL)closeopt { + if (self) { + self->_fileDescriptor = fd; + return self; + } +} + +- (instancetype)initWithFileDescriptor:(int)fd { + [self initWithFileDescriptor:fd closeOnDealloc:NO]; +} + +- (void)closeFile { + close(self->_fileDescriptor); +} + +- (void)dealloc { + [self closeFile]; +} + +@end diff --git a/infer/models/objc/src/NSMutableArray.h b/infer/models/objc/src/NSMutableArray.h new file mode 100644 index 000000000..f48ea3ea1 --- /dev/null +++ b/infer/models/objc/src/NSMutableArray.h @@ -0,0 +1,5 @@ +#import + +@interface NSMutableArray : NSObject + +@end diff --git a/infer/models/objc/src/NSMutableArray.m b/infer/models/objc/src/NSMutableArray.m new file mode 100644 index 000000000..45ff146d4 --- /dev/null +++ b/infer/models/objc/src/NSMutableArray.m @@ -0,0 +1,29 @@ +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + +#pragma clang diagnostic ignored "-Wdeprecated-objc-isa-usage" + +#import "NSMutableArray.h" + +@implementation NSMutableArray + +- (void)setObject:(id)anObject atIndexedSubscript:(NSUInteger)index { + NSObject *obj = (NSObject*)anObject; + id isa = obj->isa; +} + +- (void)addObject:(id)anObject { + NSObject *obj = (NSObject*)anObject; + id isa = obj->isa; +} + +- (void)insertObject:(id)anObject atIndex:(NSUInteger)index { + NSObject *obj = (NSObject*)anObject; + id isa = obj->isa; +} + ++ (instancetype)array { + return [NSMutableArray alloc]; +} + + +@end diff --git a/infer/models/objc/src/NSMutableDictionary.h b/infer/models/objc/src/NSMutableDictionary.h new file mode 100644 index 000000000..8ea4914f0 --- /dev/null +++ b/infer/models/objc/src/NSMutableDictionary.h @@ -0,0 +1,5 @@ +#import + +@interface NSMutableDictionary : NSObject + +@end diff --git a/infer/models/objc/src/NSMutableDictionary.m b/infer/models/objc/src/NSMutableDictionary.m new file mode 100644 index 000000000..e24e932d0 --- /dev/null +++ b/infer/models/objc/src/NSMutableDictionary.m @@ -0,0 +1,27 @@ +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + +#pragma clang diagnostic ignored "-Wdeprecated-objc-isa-usage" + +#import "NSMutableDictionary.h" + +@implementation NSMutableDictionary + +- (void)setObject:(id)object forKeyedSubscript:(id)aKey { + NSObject *obj = (NSObject*)object; + id isa = obj->isa; + NSObject *key = (NSObject*)aKey; + id isa2 = key->isa; +} + +- (void)setObject:(id)anObject forKey:(id)aKey { + NSObject *obj = (NSObject*)anObject; + id isa = obj->isa; + NSObject *key = (NSObject*)aKey; + id isa2 = key->isa; +} + ++ (instancetype)dictionary { + return [NSMutableDictionary alloc]; +} + +@end diff --git a/infer/models/objc/src/NSNumber.h b/infer/models/objc/src/NSNumber.h new file mode 100644 index 000000000..f4c449dac --- /dev/null +++ b/infer/models/objc/src/NSNumber.h @@ -0,0 +1,7 @@ +#import + +@interface NSNumber : NSObject { + double value; +} + +@end diff --git a/infer/models/objc/src/NSNumber.m b/infer/models/objc/src/NSNumber.m new file mode 100644 index 000000000..63895b881 --- /dev/null +++ b/infer/models/objc/src/NSNumber.m @@ -0,0 +1,50 @@ +#import "NSNumber.h" + + +@implementation NSNumber + ++(NSNumber *)numberWithInt:(int)value { + // using alloc as the documentation doesn't say it may return nil + NSNumber *number = [self alloc]; + return [number initWithInt:value]; +} + +- (id)initWithInt:(int)v { + self->value = (double)v; + return self; +} + ++(NSNumber *)numberWithFloat:(float)value { + // using alloc as the documentation doesn't say it may return nil + NSNumber *number = [NSNumber alloc]; + return [number initWithInt:value]; +} + +- (id)initWithFloat:(float)v { + self->value = (double)v; + return self; +} + ++(NSNumber *)numberWithDouble:(double)value { + // using alloc as the documentation doesn't say it may return nil + NSNumber *number = [self alloc]; + return [number initWithInt:value]; +} + +- (id)initWithDouble:(double)v { + self->value = v; + return self; +} + ++(NSNumber *)numberWithBool:(BOOL)value { + // using alloc as the documentation doesn't say it may return nil + NSNumber *number = [self alloc]; + return [number initWithBool:value]; +} + +- (id)initWithBool:(BOOL)v { + self->value = (double)v; + return self; +} + +@end diff --git a/infer/models/objc/src/NSObject.m b/infer/models/objc/src/NSObject.m new file mode 100644 index 000000000..fa0fb3837 --- /dev/null +++ b/infer/models/objc/src/NSObject.m @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2014- Facebook. + * All rights reserved. + */ + +#pragma clang diagnostic ignored "-Wprotocol" + +#pragma clang diagnostic ignored "-Wincomplete-implementation" + +#pragma clang diagnostic ignored "-Wimplicit-function-declaration" + +#import + +@implementation NSObject + +- (id)init { + return (id)self; +} +@end diff --git a/infer/models/objc/src/NSString.h b/infer/models/objc/src/NSString.h new file mode 100644 index 000000000..41f9c95cc --- /dev/null +++ b/infer/models/objc/src/NSString.h @@ -0,0 +1,9 @@ +#import + +@interface NSString : NSObject { + const char * value; +} + ++ (instancetype)stringWithUTF8String:(const char *)bytes; + +@end diff --git a/infer/models/objc/src/NSString.m b/infer/models/objc/src/NSString.m new file mode 100644 index 000000000..744070e56 --- /dev/null +++ b/infer/models/objc/src/NSString.m @@ -0,0 +1,29 @@ +#import "NSString.h" +#import + +void __get_array_size(const UInt8); + +@implementation NSString + ++ (instancetype)stringWithUTF8String:(const char *)bytes { + // using alloc as the documentation doesn't say it may return nil + NSString *s = [NSString alloc]; + s->value = bytes; + return s; +} + +- (instancetype)initWithBytesNoCopy:(char*)bytes + length:(NSUInteger)length + encoding:(id)encoding + freeWhenDone:(BOOL)flag { + if (flag == YES) { + if (bytes) { + __get_array_size(bytes); + free(bytes); + } + } + return self; + +} +@end + diff --git a/infer/src/Makefile b/infer/src/Makefile new file mode 100644 index 000000000..65bc34479 --- /dev/null +++ b/infer/src/Makefile @@ -0,0 +1,199 @@ +# Copyright 2013 - present Facebook. +# All Rights Reserved. + +REMOVE = rm -vf +REMOVE_DIR = rm -rvf +COPY = cp -f -p +COPY_DIR = cp -rf +MKDIR = mkdir -p +LINK = ln -sf + +OCAML_INCLUDE_DIR = $(shell ocamlc -where) + +#### ATDGEN declarations #### + +ATDGEN_SUFFIXES = _t.ml _t.mli _j.ml _j.mli + +ATDGEN_INCLUDE_DIR = $(shell ocamlfind query atdgen) +BINIOU_INCLUDE_DIR = $(shell ocamlfind query biniou) +YOJSON_INCLUDE_DIR = $(shell ocamlfind query yojson) +EASYFORMAT_INCLUDE_DIR = $(shell ocamlfind query easy-format) + +ATDGEN_INCLUDES = -I,$(EASYFORMAT_INCLUDE_DIR),-I,$(BINIOU_INCLUDE_DIR),-I,$(YOJSON_INCLUDE_DIR),-I,$(ATDGEN_INCLUDE_DIR) +ATDGEN_LIBS = biniou atdgen +ATDGEN_MODS = easy_format yojson ag_oj_run ag_util +ATDGEN_OPTIONS = -cflags -annot -lflags $(ATDGEN_INCLUDES) -cflags $(ATDGEN_INCLUDES) $(addprefix -lib ,$(ATDGEN_LIBS)) $(addprefix -mod ,$(ATDGEN_MODS)) + +#### Global declarations #### + +ROOT = $(shell cd ../.. && pwd) + +GLOBAL_LIBS = unix str + +BUILDDIR = ../_build-infer +ANNOTDIR = $(ROOT)/infer/src/_build +BINDIR = $(ROOT)/infer/bin + +ifneq ($(wildcard $(BUILDDIR)/sanitize.sh),) + SANITIZE_SCRIPT = $(BUILDDIR)/sanitize.sh +endif + +GLOBAL_LFLAGS = -annot +GLOBAL_CFLAGS = -bin-annot +GLOBAL_OPTIONS = -lflags $(GLOBAL_LFLAGS) -cflags $(GLOBAL_CFLAGS) $(addprefix -lib ,$(GLOBAL_LIBS)) + +#### Backend declarations #### + +BACKEND_SOURCES = backend + +INFERANALYZE_MAIN = $(BACKEND_SOURCES)/inferanalyze +INFERANALYZE_BINARY = $(BINDIR)/InferAnalyze + +#### Typeprop declarations #### + +TYPEPROP_MAIN = $(BACKEND_SOURCES)/type_prop +TYPEPROP_BINARY = $(BINDIR)/Typeprop + +#### InferPrint declarations #### + +INFERPRINT_ATDGEN_STUB_BASE = $(BACKEND_SOURCES)/jsonbug +INFERPRINT_ATDGEN_STUB_ATD = $(INFERPRINT_ATDGEN_STUB_BASE).atd +INFERPRINT_ATDGEN_STUBS = $(addprefix $(INFERPRINT_ATDGEN_STUB_BASE), $(ATDGEN_SUFFIXES)) + +INFERPRINT_MAIN = $(BACKEND_SOURCES)/inferprint +INFERPRINT_BINARY = $(BINDIR)/InferPrint + +#### Java declarations #### + +EXTLIB_INCLUDE_DIR = $(shell ocamlfind query extlib) +PTREES_INCLUDE_DIR = $(shell ocamlfind query ptrees) +JAVALIB_INCLUDE_DIR = $(shell ocamlfind query javalib) +SAWJA_INCLUDE_DIR = $(shell ocamlfind query sawja) +ZIP_INCLUDE_DIR = $(shell ocamlfind query camlzip) + +JAVA_INCLUDES = -I,$(EXTLIB_INCLUDE_DIR),-I,$(ZIP_INCLUDE_DIR),-I,$(PTREES_INCLUDE_DIR),-I,$(JAVALIB_INCLUDE_DIR),-I,$(SAWJA_INCLUDE_DIR) +JAVA_LIBS = zip extLib ptrees javalib sawja +JAVA_OPTIONS = -cflags -annot -lflags $(JAVA_INCLUDES) -cflags $(JAVA_INCLUDES) $(addprefix -lib ,$(JAVA_LIBS)) + +JAVA_SOURCES = java + +INFERJAVA_MAIN = $(JAVA_SOURCES)/jMain +INFERJAVA_BINARY = $(BINDIR)/InferJava + +#### Clang declarations #### + +CLANG_SOURCES = clang +INFERCLANG_MAIN = $(CLANG_SOURCES)/cMain +INFERCLANG_BINARY = $(BINDIR)/InferClang + +CLANG_PLUGIN_ROOT ?= $(shell cd $(ROOT)/../facebook-clang-plugin && pwd) +CLANG_PLUGIN_BUILD = $(CLANG_PLUGIN_ROOT)/build +CLANG_PLUGIN_BINARIES = $(addprefix $(CLANG_PLUGIN_ROOT)/clang-ocaml/build/, clang_ast_converter clang_ast_named_decl_printer) + +CLANG_OCAML_ROOT = $(CLANG_PLUGIN_ROOT)/clang-ocaml +CLANG_OCAML_BUILD = $(CLANG_OCAML_ROOT)/build + +CLANG_AST_BASE_NAME = clang_ast +CLANG_ATDGEN_STUB_BASE = $(CLANG_SOURCES)/$(CLANG_AST_BASE_NAME) +CLANG_ATDGEN_STUB_ATD = $(CLANG_OCAML_BUILD)/$(CLANG_AST_BASE_NAME).atd +CLANG_ATDGEN_STUBS = $(addprefix $(CLANG_SOURCES)/$(CLANG_AST_BASE_NAME), $(ATDGEN_SUFFIXES)) + +INFER_CLANG_AST_PROJ = $(addprefix $(CLANG_SOURCES)/, clang_ast_proj.ml clang_ast_proj.mli) +FCP_CLANG_AST_PROJ = $(addprefix $(CLANG_OCAML_BUILD)/, clang_ast_proj.ml clang_ast_proj.mli) + +#### End of declarations #### + +# Check whether .facebook file exists in a root directory. +# Based on that determine which code should be loaded +ifeq ($(wildcard $(ROOT)/.facebook),) + EXTRA_DEPS = opensource +else + EXTRA_DEPS = facebook +endif + +DEPENDENCIES = $(BACKEND_SOURCES) checkers facebook/checkers facebook/checkers/graphql facebook/scripts harness $(EXTRA_DEPS) + +OCAMLBUILD = ocamlbuild -build-dir $(BUILDDIR) -j 0 $(addprefix -I , $(DEPENDENCIES)) $(GLOBAL_OPTIONS) $(ATDGEN_OPTIONS) $(JAVA_OPTIONS) + +.PHONY: all java clang build_java build_clang annotations init sanitize version clean + +all: java clang + +java: build_java annotations $(INFERANALYZE_BINARY) $(INFERPRINT_BINARY) $(INFERJAVA_BINARY) + +clang: build_clang annotations $(INFERANALYZE_BINARY) $(INFERPRINT_BINARY) $(INFERCLANG_BINARY) + +build_java: init $(INFERPRINT_ATDGEN_STUBS) + $(OCAMLBUILD) $(TYPEPROP_MAIN).native $(INFERANALYZE_MAIN).native $(INFERPRINT_MAIN).native $(INFERJAVA_MAIN).native + +build_clang: init $(INFERPRINT_ATDGEN_STUBS) check_clang_plugin $(CLANG_ATDGEN_STUBS) + $(OCAMLBUILD) $(INFERANALYZE_MAIN).native $(INFERPRINT_MAIN).native $(INFERCLANG_MAIN).native + +annotations: + rsync -r --delete --exclude=*.ml* --exclude=*.o --exclude=*.cm* --exclude=*.native $(BUILDDIR)/* $(ANNOTDIR) + +check_clang_plugin: + $(ROOT)/scripts/check_clang_plugin_version.sh $(CLANG_PLUGIN_ROOT) + +$(INFERPRINT_ATDGEN_STUBS): $(INFERPRINT_ATDGEN_STUB_ATD) + atdgen -t $(INFERPRINT_ATDGEN_STUB_ATD) -o $(INFERPRINT_ATDGEN_STUB_BASE) + atdgen -j $(INFERPRINT_ATDGEN_STUB_ATD) -o $(INFERPRINT_ATDGEN_STUB_BASE) + +$(CLANG_ATDGEN_STUBS) $(INFER_CLANG_AST_PROJ): $(CLANG_ATDGEN_STUB_ATD) $(CLANG_PLUGIN_BINARIES) $(FCP_CLANG_AST_PROJ) + # rebuild the artifacts of the AST files whenever they're upated in FCP + # also copy the ast_proj files whenever they are updated + atdgen -rec -t $(CLANG_ATDGEN_STUB_ATD) -o $(CLANG_ATDGEN_STUB_BASE) + atdgen -rec -j $(CLANG_ATDGEN_STUB_ATD) -o $(CLANG_ATDGEN_STUB_BASE) + $(COPY) $(CLANG_OCAML_BUILD)/clang_ast_proj.ml $(CLANG_SOURCES) + $(COPY) $(CLANG_OCAML_BUILD)/clang_ast_proj.mli $(CLANG_SOURCES) + + +init: sanitize version $(BUILDDIR) + +sanitize: + $(SANITIZE_SCRIPT) + +ifneq ($(shell which git),) +MAJOR = 0 +MINOR = 1 +PATCH = 0 + +GIT_COMMIT = $(shell git rev-parse HEAD | awk '{print $0; exit}' | xargs echo -n | sed 's/[\/&]/\\&/g') +GIT_BRANCH = $(shell git rev-parse --abbrev-ref HEAD | awk '{print $0; exit}' | xargs echo -n | sed 's/[\/&]/\\&/g') +GIT_TAG = $(shell git tag --points-at $(GIT_COMMIT)) + +version: + sed -e 's/@MAJOR@/$(MAJOR)/g' \ + -e 's/@MINOR@/$(MINOR)/g' \ + -e 's/@PATCH@/$(PATCH)/g' \ + -e 's/@GIT_COMMIT@/$(GIT_COMMIT)/g' \ + -e 's/@GIT_BRANCH@/$(GIT_BRANCH)/g' \ + -e 's/@GIT_TAG@/$(GIT_TAG)/g' \ + $(BACKEND_SOURCES)/version.ml.in > $(BACKEND_SOURCES)/version.ml +else +version: + $(info Infer is not tracked, not updating $(BACKEND_SOURCES)/version.ml) +endif + +$(BUILDDIR): + $(MKDIR) $(BUILDDIR) + +$(INFERANALYZE_BINARY): $(BUILDDIR)/$(INFERANALYZE_MAIN).native + $(COPY) $(BUILDDIR)/$(INFERANALYZE_MAIN).native $(INFERANALYZE_BINARY) + +$(INFERPRINT_BINARY): $(BUILDDIR)/$(INFERPRINT_MAIN).native + $(COPY) $(BUILDDIR)/$(INFERPRINT_MAIN).native $(INFERPRINT_BINARY) + +$(INFERJAVA_BINARY): $(BUILDDIR)/$(INFERJAVA_MAIN).native + $(COPY) $(BUILDDIR)/$(INFERJAVA_MAIN).native $(INFERJAVA_BINARY) + +$(INFERCLANG_BINARY): $(BUILDDIR)/$(INFERCLANG_MAIN).native + $(COPY) $(BUILDDIR)/$(INFERCLANG_MAIN).native $(INFERCLANG_BINARY) + +$(TYPEPROP_BINARY): $(BUILDDIR)/$(TYPEPROP_MAIN).native + $(COPY) $(BUILDDIR)/$(TYPEPROP_MAIN).native $(TYPEPROP_BINARY) + +clean: $(BUILDDIR) + $(OCAMLBUILD) -clean + $(REMOVE_DIR) $(ANNOTDIR) + $(REMOVE) $(TYPEPROP_BINARY) $(INFERANALYZE_BINARY) $(INFERPRINT_BINARY) $(INFERJAVA_BINARY) $(INFERCLANG_BINARY) diff --git a/infer/src/backend/.project b/infer/src/backend/.project new file mode 100644 index 000000000..c256b8e3d --- /dev/null +++ b/infer/src/backend/.project @@ -0,0 +1,17 @@ + + + Infer Sil + + + + + + Ocaml.ocamlMakefileBuilder + + + + + + ocaml.ocamlnatureMakefile + + diff --git a/infer/src/backend/.settings/org.eclipse.core.resources.prefs b/infer/src/backend/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 000000000..c3f2dbf31 --- /dev/null +++ b/infer/src/backend/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,3 @@ +#Mon Jun 15 09:06:26 CEST 2009 +eclipse.preferences.version=1 +encoding/=ISO-8859-1 diff --git a/infer/src/backend/CRC.ml b/infer/src/backend/CRC.ml new file mode 100644 index 000000000..2146c6599 --- /dev/null +++ b/infer/src/backend/CRC.ml @@ -0,0 +1,57 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +let vecH = [| + 0x00; 0xC1; 0x81; 0x40; 0x01; 0xC0; 0x80; 0x41; 0x01; 0xC0; 0x80; 0x41; 0x00; 0xC1; 0x81; + 0x40; 0x01; 0xC0; 0x80; 0x41; 0x00; 0xC1; 0x81; 0x40; 0x00; 0xC1; 0x81; 0x40; 0x01; 0xC0; + 0x80; 0x41; 0x01; 0xC0; 0x80; 0x41; 0x00; 0xC1; 0x81; 0x40; 0x00; 0xC1; 0x81; 0x40; 0x01; + 0xC0; 0x80; 0x41; 0x00; 0xC1; 0x81; 0x40; 0x01; 0xC0; 0x80; 0x41; 0x01; 0xC0; 0x80; 0x41; + 0x00; 0xC1; 0x81; 0x40; 0x01; 0xC0; 0x80; 0x41; 0x00; 0xC1; 0x81; 0x40; 0x00; 0xC1; 0x81; + 0x40; 0x01; 0xC0; 0x80; 0x41; 0x00; 0xC1; 0x81; 0x40; 0x01; 0xC0; 0x80; 0x41; 0x01; 0xC0; + 0x80; 0x41; 0x00; 0xC1; 0x81; 0x40; 0x00; 0xC1; 0x81; 0x40; 0x01; 0xC0; 0x80; 0x41; 0x01; + 0xC0; 0x80; 0x41; 0x00; 0xC1; 0x81; 0x40; 0x01; 0xC0; 0x80; 0x41; 0x00; 0xC1; 0x81; 0x40; + 0x00; 0xC1; 0x81; 0x40; 0x01; 0xC0; 0x80; 0x41; 0x01; 0xC0; 0x80; 0x41; 0x00; 0xC1; 0x81; + 0x40; 0x00; 0xC1; 0x81; 0x40; 0x01; 0xC0; 0x80; 0x41; 0x00; 0xC1; 0x81; 0x40; 0x01; 0xC0; + 0x80; 0x41; 0x01; 0xC0; 0x80; 0x41; 0x00; 0xC1; 0x81; 0x40; 0x00; 0xC1; 0x81; 0x40; 0x01; + 0xC0; 0x80; 0x41; 0x01; 0xC0; 0x80; 0x41; 0x00; 0xC1; 0x81; 0x40; 0x01; 0xC0; 0x80; 0x41; + 0x00; 0xC1; 0x81; 0x40; 0x00; 0xC1; 0x81; 0x40; 0x01; 0xC0; 0x80; 0x41; 0x00; 0xC1; 0x81; + 0x40; 0x01; 0xC0; 0x80; 0x41; 0x01; 0xC0; 0x80; 0x41; 0x00; 0xC1; 0x81; 0x40; 0x01; 0xC0; + 0x80; 0x41; 0x00; 0xC1; 0x81; 0x40; 0x00; 0xC1; 0x81; 0x40; 0x01; 0xC0; 0x80; 0x41; 0x01; + 0xC0; 0x80; 0x41; 0x00; 0xC1; 0x81; 0x40; 0x00; 0xC1; 0x81; 0x40; 0x01; 0xC0; 0x80; 0x41; + 0x00; 0xC1; 0x81; 0x40; 0x01; 0xC0; 0x80; 0x41; 0x01; 0xC0; 0x80; 0x41; 0x00; 0xC1; 0x81; + 0x40 |] + +let vecL = [| + 0x00; 0xC0; 0xC1; 0x01; 0xC3; 0x03; 0x02; 0xC2; 0xC6; 0x06; 0x07; 0xC7; 0x05; 0xC5; 0xC4; + 0x04; 0xCC; 0x0C; 0x0D; 0xCD; 0x0F; 0xCF; 0xCE; 0x0E; 0x0A; 0xCA; 0xCB; 0x0B; 0xC9; 0x09; + 0x08; 0xC8; 0xD8; 0x18; 0x19; 0xD9; 0x1B; 0xDB; 0xDA; 0x1A; 0x1E; 0xDE; 0xDF; 0x1F; 0xDD; + 0x1D; 0x1C; 0xDC; 0x14; 0xD4; 0xD5; 0x15; 0xD7; 0x17; 0x16; 0xD6; 0xD2; 0x12; 0x13; 0xD3; + 0x11; 0xD1; 0xD0; 0x10; 0xF0; 0x30; 0x31; 0xF1; 0x33; 0xF3; 0xF2; 0x32; 0x36; 0xF6; 0xF7; + 0x37; 0xF5; 0x35; 0x34; 0xF4; 0x3C; 0xFC; 0xFD; 0x3D; 0xFF; 0x3F; 0x3E; 0xFE; 0xFA; 0x3A; + 0x3B; 0xFB; 0x39; 0xF9; 0xF8; 0x38; 0x28; 0xE8; 0xE9; 0x29; 0xEB; 0x2B; 0x2A; 0xEA; 0xEE; + 0x2E; 0x2F; 0xEF; 0x2D; 0xED; 0xEC; 0x2C; 0xE4; 0x24; 0x25; 0xE5; 0x27; 0xE7; 0xE6; 0x26; + 0x22; 0xE2; 0xE3; 0x23; 0xE1; 0x21; 0x20; 0xE0; 0xA0; 0x60; 0x61; 0xA1; 0x63; 0xA3; 0xA2; + 0x62; 0x66; 0xA6; 0xA7; 0x67; 0xA5; 0x65; 0x64; 0xA4; 0x6C; 0xAC; 0xAD; 0x6D; 0xAF; 0x6F; + 0x6E; 0xAE; 0xAA; 0x6A; 0x6B; 0xAB; 0x69; 0xA9; 0xA8; 0x68; 0x78; 0xB8; 0xB9; 0x79; 0xBB; + 0x7B; 0x7A; 0xBA; 0xBE; 0x7E; 0x7F; 0xBF; 0x7D; 0xBD; 0xBC; 0x7C; 0xB4; 0x74; 0x75; 0xB5; + 0x77; 0xB7; 0xB6; 0x76; 0x72; 0xB2; 0xB3; 0x73; 0xB1; 0x71; 0x70; 0xB0; 0x50; 0x90; 0x91; + 0x51; 0x93; 0x53; 0x52; 0x92; 0x96; 0x56; 0x57; 0x97; 0x55; 0x95; 0x94; 0x54; 0x9C; 0x5C; + 0x5D; 0x9D; 0x5F; 0x9F; 0x9E; 0x5E; 0x5A; 0x9A; 0x9B; 0x5B; 0x99; 0x59; 0x58; 0x98; 0x88; + 0x48; 0x49; 0x89; 0x4B; 0x8B; 0x8A; 0x4A; 0x4E; 0x8E; 0x8F; 0x4F; 0x8D; 0x4D; 0x4C; 0x8C; + 0x44; 0x84; 0x85; 0x45; 0x87; 0x47; 0x46; 0x86; 0x82; 0x42; 0x43; 0x83; 0x41; 0x81; 0x80; + 0x40 |] + +let compute_crc16 s cc = + let h = ref 0xFF in + let l = ref 0xFF in + for n = 0 to cc - 1 do + let i = !h lxor Char.code (s.[n]) in + h := !l lxor vecH.(i); + l := vecL.(i) + done; + Printf.sprintf "%02X%02X" !h !l + +let crc16 s = + compute_crc16 s (String.length s) diff --git a/infer/src/backend/CRC.mli b/infer/src/backend/CRC.mli new file mode 100644 index 000000000..674ec5f96 --- /dev/null +++ b/infer/src/backend/CRC.mli @@ -0,0 +1,7 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +val crc16 : string -> string diff --git a/infer/src/backend/DB.ml b/infer/src/backend/DB.ml new file mode 100644 index 000000000..d6432bc02 --- /dev/null +++ b/infer/src/backend/DB.ml @@ -0,0 +1,321 @@ +(** Database of analysis results *) + +open Utils +module F = Format +module L = Logging + +(** {2 Source Files} *) + +exception Path_not_prefix_root + +type source_file = + | Absolute of string + | Relative of string + +let source_file_compare sf1 sf2 = + match sf1, sf2 with + | Absolute p1, Absolute p2 -> string_compare p1 p2 + | Absolute _, _ -> -1 + | _, Absolute _ -> 1 + | Relative p1, Relative p2 -> string_compare p1 p2 + +let source_file_equal sf1 sf2 = + (source_file_compare sf1 sf2) = 0 + +module OrderedSourceFile = +struct + type t = source_file + let compare = source_file_compare +end + +module SourceFileMap = Map.Make(OrderedSourceFile) + +let source_file_from_string path = + if Filename.is_relative path then + Relative path + else + Absolute path + +(** convert a project root directory and a full path to a rooted source file *) +let rel_source_file_from_abs_path root fname = + if Utils.string_is_prefix root fname then + let relative_fname = filename_to_relative root fname in + Relative relative_fname + else raise Path_not_prefix_root + +(** convert a path to a source file, turning it into an absolute path if necessary *) +let abs_source_file_from_path fname = + Absolute (filename_to_absolute fname) + +type encoding_type = + Enc_base | Enc_path_with_underscores | Enc_crc + +let curr_encoding = Enc_crc + +let source_file_to_string fname = + match fname with + | Relative path + | Absolute path -> path + +exception No_project_root + +let project_root () = + match !Config.project_root with + | None -> L.err "No -project_root option passed@."; raise No_project_root + | Some path -> path + +(* Checking if the path exists may be needed only in some cases, hence the flag check_exists *) +let source_file_to_abs_path fname = + match fname with + | Relative path -> Filename.concat (project_root()) path + | Absolute path -> path + +let source_file_to_rel_path fname = + match fname with + | Relative path -> path + | Absolute path -> filename_to_relative (project_root ()) path + +(** string encoding of a source file (including path) as a single filename *) +let source_file_encoding source_file = + let source_file_s = source_file_to_string source_file in + match curr_encoding with + | Enc_base -> + Filename.basename source_file_s + | Enc_path_with_underscores -> + Escape.escape_path source_file_s + | Enc_crc -> + let base = Filename.basename source_file_s in + let dir = Filename.dirname source_file_s in + let crc = CRC.crc16 dir in + base ^ "." ^ crc + +let source_file_empty = Absolute "" + +(** current source file *) +let current_source = ref source_file_empty + +(** {2 Source Dirs} *) + +(** source directory: the directory inside the results dir corresponding to a source file *) +type source_dir = string + +let source_dir_compare = string_compare + +(** expose the source dir as a string *) +let source_dir_to_string source_dir = source_dir + +(** get the path to an internal file with the given extention (.cfg, .cg, .tenv) *) +let source_dir_get_internal_file source_dir extension = + let source_dir_name = Filename.chop_extension (Filename.basename source_dir) in + let fname = source_dir_name ^ extension in + Filename.concat source_dir fname + +let captured_dir () = + Filename.concat !Config.results_dir Config.captured_dir_name + +let sources_dir () = + Filename.concat !Config.results_dir Config.sources_dir_name + +(** get the source directory corresponding to a source file *) +let source_dir_from_source_file source_file = + Filename.concat (captured_dir ()) (source_file_encoding source_file) + +(** get the path to the copy of the source file to be stored in the results directory *) +let source_file_in_resdir source_file = + Filename.concat (sources_dir ()) (source_file_encoding source_file) + +(** Find the source directories in the results dir *) +let find_source_dirs () = + let source_dirs = ref [] in + let capt_dir = captured_dir () in + let files_in_results_dir = Array.to_list (Sys.readdir capt_dir) in + let add_cg_files_from_dir dir = + let files = Array.to_list (Sys.readdir dir) in + list_iter (fun fname -> + let path = Filename.concat dir fname in + if Filename.check_suffix path ".cg" then source_dirs := dir :: !source_dirs) + files in + list_iter (fun fname -> + let dir = Filename.concat capt_dir fname in + if Sys.is_directory dir then add_cg_files_from_dir dir) + files_in_results_dir; + list_rev !source_dirs + +(** {2 Filename} *) + +type filename = string + +let filename_concat = Filename.concat + +let filename_to_string s = s + +let filename_from_string s = s + +let filename_compare = Pervasives.compare + +let filename_add_suffix fn s = fn ^ s + +let chop_extension = Filename.chop_extension + +let file_exists = Sys.file_exists + +let file_remove = Sys.remove + +module FilenameSet = Set.Make( + struct + type t = filename + let compare = filename_compare + end) + +module FilenameMap = Map.Make( + struct + type t = filename + let compare = filename_compare + end) + +(** Return the time when a file was last modified. The file must exist. *) +let file_modified_time fname = + let stat = Unix.stat fname in + stat.Unix.st_mtime + +(** Create a directory if it does not exist already. *) +let create_dir dir = + try + if (Unix.stat dir).Unix.st_kind != Unix.S_DIR then + (L.err "@.ERROR: file %s exists and is not a directory@." dir; + assert false) + with Unix.Unix_error _ -> + (try Unix.mkdir dir 0o700 with + Unix.Unix_error _ -> + let created_concurrently = (* check if another process created it meanwhile *) + (Unix.stat dir).Unix.st_kind = Unix.S_DIR in + if not created_concurrently then + (L.err "@.ERROR: cannot create directory %s@." dir; + assert false)) + +let read_whole_file fd = + let stats = Unix.fstat fd in + let size = stats.Unix.st_size in + let buf = String.create size in + let nread = Unix.read fd buf 0 size in + if nread != size then + begin + L.stderr "Error nread:%d size:%d@." nread size; + assert false + end; + buf + +(** Update the file contents with the update function provided. +If the directory does not exist, it is created. +If the file does not exist, it is created, and update is given the empty string. +A lock is used to allow write attempts in parallel. *) +let update_file_with_lock dir fname update = + let reset_file fd = + let n = Unix.lseek fd 0 Unix.SEEK_SET in + if n <> 0 then + begin + L.stderr "reset_file: lseek fail@."; + assert false + end in + create_dir dir; + let path = Filename.concat dir fname in + let fd = Unix.openfile path [Unix.O_CREAT; Unix.O_SYNC; Unix.O_RDWR] 0o640 in + Unix.lockf fd Unix.F_LOCK 0; + let buf = read_whole_file fd in + reset_file fd; + let str = update buf in + let i = Unix.write fd str 0 (String.length str) in + if (i = (String.length str)) + then (Unix.lockf fd Unix.F_ULOCK 0; Unix.close fd) + else (L.err "@.save_with_lock: fail on path: %s@." path; + assert false) + +(** Read a file using a lock to allow write attempts in parallel. *) +let read_file_with_lock dir fname = + let path = Filename.concat dir fname in + try + let fd = Unix.openfile path [Unix.O_RSYNC; Unix.O_RDONLY] 0o646 in + try + Unix.lockf fd Unix.F_RLOCK 0; + let buf = read_whole_file fd in + Unix.lockf fd Unix.F_ULOCK 0; + Unix.close fd; + Some buf + with Unix.Unix_error _ -> + L.stderr "read_file_with_lock: Unix error"; + assert false + with Unix.Unix_error _ -> None + +(** {2 Results Directory} *) + +module Results_dir = struct + (** path expressed as a list of strings *) + type path = string list + + (** kind of path: specifies how to interpret a path *) + type path_kind = + | Abs_root (** absolute path implicitly rooted at the root of the results dir *) + | Abs_source_dir (** absolute path implicitly rooted at the source directory for the current file *) + | Rel (** relative path *) + + let filename_from_base base path = + let rec f = function + | [] -> base + | name:: names -> + Filename.concat (f names) (if name ==".." then Filename.parent_dir_name else name) in + f (list_rev path) + + (** convert a path to a filename *) + let path_to_filename pk path = + let base = match pk with + | Abs_root -> !Config.results_dir + | Abs_source_dir -> + let dir = source_dir_from_source_file !current_source in + source_dir_to_string dir + | Rel -> Filename.current_dir_name in + filename_from_base base path + + (** directory of spec files *) + let specs_dir () = path_to_filename Abs_root [Config.specs_dir_name] + + (** initialize the results directory *) + let init () = + create_dir !Config.results_dir; + create_dir (specs_dir ()); + create_dir (path_to_filename Abs_root [Config.sources_dir_name]); + create_dir (path_to_filename Abs_root [Config.captured_dir_name]); + if not (source_file_equal !current_source source_file_empty) then + create_dir (path_to_filename Abs_source_dir []) + + let clean_specs_dir () = + let specs_dir_path = specs_dir () in + create_dir specs_dir_path; (* create dir just in case it doesn't exist to avoid errors *) + let files_to_remove = Array.map (Filename.concat specs_dir_path) (Sys.readdir specs_dir_path) in + Array.iter Sys.remove files_to_remove + + (** create a file at the given path, creating any missing directories *) + let create_file pk path = + let rec create = function + | [] -> + let fname = path_to_filename pk [] in + create_dir fname; + fname + | name:: names -> + let new_path = Filename.concat (create names) name in + create_dir new_path; + new_path in + let filename, dir_path = match list_rev path with + | filename:: dir_path -> filename, dir_path + | [] -> raise (Failure "create_path") in + let full_fname = Filename.concat (create dir_path) filename in + Unix.openfile full_fname [Unix.O_WRONLY; Unix.O_CREAT; Unix.O_TRUNC] 0o777 +end + +let global_tenv_fname () = + let basename = Config.global_tenv_filename in + filename_concat (captured_dir ()) basename + +let is_source_file path = + list_exists + (fun ext -> Filename.check_suffix path ext) + Config.source_file_extentions diff --git a/infer/src/backend/DB.mli b/infer/src/backend/DB.mli new file mode 100644 index 000000000..3f9f413b1 --- /dev/null +++ b/infer/src/backend/DB.mli @@ -0,0 +1,140 @@ +(** Database of analysis results *) + +(** {2 Filename} *) + +(** generic file name *) +type filename + +module FilenameSet : Set.S with type elt = filename +module FilenameMap : Map.S with type key = filename + +val filename_from_string : string -> filename +val filename_to_string : filename -> string +val filename_compare : filename -> filename -> int +val chop_extension : filename -> filename +val filename_concat : filename -> string -> filename +val filename_add_suffix : filename -> string -> filename +val file_exists : filename -> bool +val file_remove : filename -> unit +val file_modified_time : filename -> float (** Return the time when a file was last modified. The file must exist. *) + +(** {2 Results Directory} *) + +module Results_dir : sig +(** path expressed as a list of strings *) + type path = string list + + (** kind of path: specifies how to interpret a path *) + type path_kind = + | Abs_root (** absolute path implicitly rooted at the root of the results dir *) + | Abs_source_dir (** absolute path implicitly rooted at the source directory for the current file *) + | Rel (** relative path *) + + (** convert a path to a filename *) + val path_to_filename : path_kind -> path -> filename + + (** directory of spec files *) + val specs_dir : unit -> filename + + (** Initialize the results directory *) + val init : unit -> unit + + (** Clean up specs directory *) + val clean_specs_dir : unit -> unit + + (** create a file at the given path, creating any missing directories *) + val create_file : path_kind -> path -> Unix.file_descr +end + +(** {2 Source Files} *) + +type source_file + +exception Path_not_prefix_root + +(** Maps from source_file *) +module SourceFileMap : Map.S with type key = source_file + +(** current source file *) +val current_source : source_file ref + +(** comparison of source files *) +val source_file_compare : source_file -> source_file -> int + +(** equality of source files *) +val source_file_equal : source_file -> source_file -> bool + +(** empty source file *) +val source_file_empty : source_file + +(** convert a path to a source file, turning it into an absolute path if necessary *) +val abs_source_file_from_path : string -> source_file + +(** convert a project root directory and an absolute path to a source file *) +val rel_source_file_from_abs_path : string -> string -> source_file + +(** string encoding of a source file (including path) as a single filename *) +val source_file_encoding : source_file -> string + +(** convert a source file to a string *) +val source_file_to_string : source_file -> string + +(** convert a string obtained by source_file_to_string to a source file *) +val source_file_from_string : string -> source_file + +exception No_project_root + +(** get the project root when it exists or raise No_project_root otherwise *) +val project_root : unit -> string + +(** get the full path of a source file, raise No_project_root exception when used with a relative source file and no project root specified *) +val source_file_to_abs_path : source_file -> string + +(** get the relative path of a source file *) +val source_file_to_rel_path : source_file -> string + +(** {2 Source Dirs} *) + +(** source directory: the directory inside the results dir corresponding to a source file *) +type source_dir + +val source_dir_compare : source_dir -> source_dir -> int + +(** get the absolute path to the sources dir *) +val sources_dir : unit -> string + +(** expose the source dir as a string *) +val source_dir_to_string : source_dir -> string + +(** get the path to an internal file with the given extention (.cfg, .cg, .tenv) *) +val source_dir_get_internal_file : source_dir -> string -> filename + +(** get the source directory corresponding to a source file *) +val source_dir_from_source_file : source_file -> source_dir + +(** get the path to the copy of the source file to be stored in the results directory *) +val source_file_in_resdir : source_file -> filename + +(** directory where the results of the capture phase are stored *) +val captured_dir : unit -> filename + +(** Find the source directories in the current results dir *) +val find_source_dirs : unit -> source_dir list + +(** create a directory if it does not exist already *) +val create_dir : string -> unit + +(** Read a file using a lock to allow write attempts in parallel. *) +val read_file_with_lock : string -> string -> string option + +(** Update the file contents with the update function provided. +If the directory does not exist, it is created. +If the file does not exist, it is created, and update is given the empty string. +A lock is used to allow write attempts in parallel. *) +val update_file_with_lock : string -> string -> (string -> string) -> unit + +(** get the path of the global type environment (only used in Java) *) +val global_tenv_fname : unit -> filename + +(** Check if a path is a Java, C, C++ or Objectve C source file according to the file extention *) +val is_source_file: string -> bool diff --git a/infer/src/backend/OcamlMakefile b/infer/src/backend/OcamlMakefile new file mode 100644 index 000000000..4b3d858af --- /dev/null +++ b/infer/src/backend/OcamlMakefile @@ -0,0 +1,535 @@ +########################################################################### +# OcamlMakefile +# Copyright (C) 1999, 2000 Markus Mottl +# Free to copy and modify! USE AT YOUR OWN RISK! +# +# For updates see: +# http://miss.wu-wien.ac.at/~mottl/ocaml_sources +# +# $Id: OcamlMakefile,v 1.2.4.1 2008/01/13 17:39:45 ccris Exp $ +# +########################################################################### + + +# Set these variables to the names of the sources to be processed and +# the result variable. Order matters during linkage! + +ifndef SOURCES + SOURCES := foo.ml +endif +export SOURCES + +ifndef RESULT + RESULT := foo +endif +export RESULT + +export BCSUFFIX +export NCSUFFIX + +ifndef TOPSUFFIX + TOPSUFFIX := .top +endif + +export TOPSUFFIX + +# Eventually set include- and library-paths, libraries to link, +# additional compilation-, link- and ocamlyacc-flags +# Path- and library information needs not be written with "-I" and such... +# Define THREADS if you need it, otherwise leave it unset! + +export THREADS + +export INCDIRS +export LIBDIRS +export OCAML_DEFAULT_DIRS +export OCAML_LIB_INSTALL + +export LIBS +export CLIBS + +export EXTRA_CMO +export EXTRA_CMX +export EXTRA_CMX_APPEND + +export OCAMLFLAGS +export OCAMLNCFLAGS +export OCAMLBCFLAGS + +export OCAMLLDFLAGS +export OCAMLNLDFLAGS +export OCAMLBLDFLAGS + +ifndef OCAMLCPFLAGS + OCAMLCPFLAGS := a +endif + +export OCAMLCPFLAGS + +export YFLAGS +export IDLFLAGS + +export CC +export CFLAGS +export LDFLAGS + +# Add a list of optional trash files that should be deleted by "make clean" +export TRASH + +#################### variables depending on your Ocaml-installation + +BCRESULT := $(addsuffix $(BCSUFFIX), $(RESULT)) +NCRESULT := $(addsuffix $(NCSUFFIX), $(RESULT)) +TOPRESULT := $(addsuffix $(TOPSUFFIX), $(RESULT)) + +ifndef OCAMLC + OCAMLC := ocamlc.opt +endif + +export OCAMLC + +ifndef OCAMLOPT + OCAMLOPT := ocamlopt.opt +endif + +export OCAMLOPT + +ifndef OCAMLMKTOP + OCAMLMKTOP := ocamlmktop +endif + +export OCAMLMKTOP + +ifndef OCAMLCP + OCAMLCP := ocamlcp +endif + +export OCAMLCP + +ifndef OCAMLDEP + OCAMLDEP := ocamldep +endif + +export OCAMLDEP + +ifndef OCAMLLEX + OCAMLLEX := ocamllex +endif + +export OCAMLLEX + +ifndef OCAMLYACC + OCAMLYACC := ocamlyacc +endif + +export OCAMLYACC + +ifndef OCAMLIDL + CAMLIDL := camlidl +endif + +export OCAMLIDL + +ifndef NOIDLHEADER + MAYBE_IDL_HEADER := -header +endif + +export NOIDLHEADER + +ifndef OCAMLMAKEFILE + OCAMLMAKEFILE := OcamlMakefile +endif + +export OCAMLMAKEFILE + +ifndef OCAMLLIBPATH + OCAMLLIBPATH := \ + $(shell $(OCAMLC) 2>/dev/null -where || echo /usr/local/lib/ocaml) +endif + +export OCAMLLIBPATH + +ifndef OCAML_LIB_INSTALL + OCAML_LIB_INSTALL := $(OCAMLLIBPATH)/contrib +endif + +export OCAML_LIB_INSTALL + + +########################################################################### + +#################### change following sections only if +#################### you know what you are doing! + +# for pedants using "--warn-undefined-variables" +export MAYBE_IDL +export REAL_RESULT +export REAL_RESULT_NOLINK +export CAMLIDLFLAGS +export THREAD_FLAG + +SHELL := /bin/sh + +MLDEPDIR := ._d +BCDIDIR := ._bcdi +NCDIDIR := ._ncdi + +FILTERED := $(filter %.mli %.ml %.mll %.mly %.idl %.c, $(SOURCES)) +SOURCE_DIRS := $(filter-out ./, $(sort $(dir $(FILTERED)))) + +FILTERED_ML := $(filter %.ml, $(FILTERED)) +DEP_ML := $(FILTERED_ML:%.ml=$(MLDEPDIR)/%.d) + +FILTERED_MLI := $(filter %.mli, $(FILTERED)) +DEP_MLI := $(FILTERED_MLI:.mli=.di) + +FILTERED_MLL := $(filter %.mll, $(FILTERED)) +DEP_MLL := $(FILTERED_MLL:%.mll=$(MLDEPDIR)/%.d) +AUTO_MLL := $(FILTERED_MLL:.mll=.ml) + +FILTERED_MLY := $(filter %.mly, $(FILTERED)) +DEP_MLY := $(FILTERED_MLY:%.mly=$(MLDEPDIR)/%.d) $(FILTERED_MLY:.mly=.di) +AUTO_MLY := $(FILTERED_MLY:.mly=.mli) $(FILTERED_MLY:.mly=.ml) + +FILTERED_IDL := $(filter %.idl, $(FILTERED)) +DEP_IDL := $(FILTERED_IDL:%.idl=$(MLDEPDIR)/%.d) $(FILTERED_IDL:.idl=.di) +C_IDL := $(FILTERED_IDL:%.idl=%_idl.c) $(FILTERED_IDL:.idl=.h) +OBJ_C_IDL := $(FILTERED_IDL:%.idl=%_idl.o) +AUTO_IDL := $(FILTERED_IDL:.idl=.mli) $(FILTERED_IDL:.idl=.ml) $(C_IDL) + +FILTERED_C := $(filter %.c, $(FILTERED)) +OBJ_C := $(FILTERED_C:.c=.o) + +AUTO_TARGETS := $(AUTO_MLL) $(AUTO_MLY) $(AUTO_IDL) + +ALL_DEPS := $(DEP_ML) $(DEP_MLI) $(DEP_MLL) $(DEP_MLY) $(DEP_IDL) +MLDEPS := $(filter %.d, $(ALL_DEPS)) +MLIDEPS := $(filter %.di, $(ALL_DEPS)) +BCDEPIS := $(MLIDEPS:%.di=$(BCDIDIR)/%.di) +NCDEPIS := $(MLIDEPS:%.di=$(NCDIDIR)/%.di) + +ALLML := $(filter %.mli %.ml %.mll %.mly %.idl, $(FILTERED)) + +IMPLO_INTF := $(ALLML:%.mli=%.mli.__) +IMPLO_INTF := $(foreach file, $(IMPLO_INTF), \ + $(basename $(file)).cmi $(basename $(file)).cmo) +IMPLO_INTF := $(filter-out %.mli.cmo, $(IMPLO_INTF)) +IMPLO_INTF := $(IMPLO_INTF:%.mli.cmi=%.cmi) + +IMPLX_INTF := $(IMPLO_INTF:.cmo=.cmx) + +INTF := $(filter %.cmi, $(IMPLO_INTF)) +IMPL_CMO := $(filter %.cmo, $(IMPLO_INTF)) +IMPL_CMX := $(IMPL_CMO:.cmo=.cmx) + +OBJ_LINK := $(OBJ_C_IDL) $(OBJ_C) +OBJ_FILES := $(IMPL_CMO:.cmo=.o) $(OBJ_LINK) + +TARGETS := $(sort $(TOPRESULT) $(BCRESULT) $(NCRESULT) $(INTF) \ + $(IMPL_CMO) $(IMPL_CMX) $(OBJ_FILES) $(AUTO_TARGETS) \ + $(BCRESULT).cma $(NCRESULT).cmxa $(NCRESULT).a) + +# If there are IDL-files +ifneq "$(strip $(FILTERED_IDL))" "" + MAYBE_IDL := -cclib -lcamlidl +endif + +INCFLAGS := $(SOURCE_DIRS:%=-I %) $(INCDIRS:%=-I %) $(OCAML_DEFAULT_DIRS:%=-I %) +CINCFLAGS := $(SOURCE_DIRS:%=-I%) $(INCDIRS:%=-I%) $(OCAML_DEFAULT_DIRS:%=-I%) + +ifndef PROFILING + INTF_OCAMLC := $(OCAMLC) +else + ifndef THREADS + INTF_OCAMLC := $(OCAMLCP) -p $(OCAMLCPFLAGS) + else + # OCaml does not support profiling byte code + # with threads (yet), therefore we force an error. + ifndef REAL_OCAMLC + error + endif + endif +endif + +COMMON_LDFLAGS := $(LDFLAGS:%=-ccopt %) $(SOURCE_DIRS:%=-ccopt -L%) \ + $(LIBDIRS:%=-ccopt -L%) $(OCAML_DEFAULT_DIRS:%=-ccopt -L%) + + +OBJS_LIBS := $(OBJ_LINK) $(CLIBS:%=-cclib -l%) $(MAYBE_IDL) + +# If we have to make byte-code +ifndef REAL_OCAMLC + SPECIAL_OCAMLFLAGS := $(OCAMLBCFLAGS) + + REAL_OCAMLC := $(INTF_OCAMLC) + + REAL_IMPL := $(EXTRA_CMO) $(IMPL_CMO) + REAL_IMPL_INTF := $(IMPLO_INTF) + IMPL_SUF := .cmo + + DEPFLAGS := + MAKE_DEPS := $(MLDEPS) $(BCDEPIS) + + ifndef THREADS + ifneq "$(strip $(OBJ_LINK))" "" + ALL_LDFLAGS := -custom + endif + ifdef CLIBS + ALL_LDFLAGS := -custom + endif + endif + + ALL_LDFLAGS += $(INCFLAGS) $(OCAMLLDFLAGS) $(OCAMLBLDFLAGS) \ + $(COMMON_LDFLAGS) $(LIBS:%=%.cma) + + ifdef THREADS + ALL_LDFLAGS += -thread unix.cma threads.cma + THREAD_FLAG := -thread + endif + +# we have to make native-code +else + ifndef PROFILING + SPECIAL_OCAMLFLAGS := $(OCAMLNCFLAGS) + PLDFLAGS := + else + SPECIAL_OCAMLFLAGS := -p $(OCAMLNCFLAGS) + PLDFLAGS := -p + endif + + REAL_IMPL := $(EXTRA_CMX) $(IMPL_CMX) $(EXTRA_CMX_APPEND) + REAL_IMPL_INTF := $(IMPLX_INTF) + IMPL_SUF := .cmx + + CFLAGS := -DNATIVE_CODE $(CFLAGS) + + DEPFLAGS := -native + MAKE_DEPS := $(MLDEPS) $(NCDEPIS) + + ALL_LDFLAGS := $(PLDFLAGS) $(INCFLAGS) $(OCAMLLDFLAGS) \ + $(OCAMLNLDFLAGS) $(COMMON_LDFLAGS) + + ifndef CREATE_LIB + ALL_LDFLAGS += $(LIBS:%=%.cmxa) + endif + + ifdef THREADS + ALL_LDFLAGS := -thread $(ALL_LDFLAGS) + ifndef CREATE_LIB + ALL_LDFLAGS += unix.cmxa threads.cmxa + endif + THREAD_FLAG := -thread + endif +endif + +ALL_OCAMLCFLAGS := $(THREAD_FLAG) $(OCAMLFLAGS) \ + $(INCFLAGS) $(SPECIAL_OCAMLFLAGS) + +ifdef make_deps + -include $(MAKE_DEPS) + AUTO_TARGETS := +endif + +########################################################################### +# USER RULES + +# generates byte-code (default) +byte-code: $(AUTO_TARGETS) + @$(MAKE) -r -f $(OCAMLMAKEFILE) $(BCRESULT) \ + REAL_RESULT="$(BCRESULT)" make_deps=yes +bc: byte-code + +top: $(AUTO_TARGETS) + @$(MAKE) -r -f $(OCAMLMAKEFILE) $(TOPRESULT) \ + REAL_RESULT="$(BCRESULT)" make_deps=yes + +# generates native-code + +native-code: $(AUTO_TARGETS) + @$(MAKE) -r -f $(OCAMLMAKEFILE) $(NCRESULT) \ + REAL_RESULT="$(NCRESULT)" \ + REAL_OCAMLC="$(OCAMLOPT)" \ + make_deps=yes +nc: native-code + +# generates byte-code libraries +byte-code-library: $(AUTO_TARGETS) + @$(MAKE) -r -f $(OCAMLMAKEFILE) $(BCRESULT).cma \ + REAL_RESULT="$(BCRESULT)" \ + CREATE_LIB=yes \ + make_deps=yes +bcl: byte-code-library + +# generates native-code libraries +native-code-library: $(AUTO_TARGETS) + @$(MAKE) -r -f $(OCAMLMAKEFILE) $(NCRESULT).cmxa \ + REAL_RESULT="$(NCRESULT)" \ + REAL_OCAMLC="$(OCAMLOPT)" \ + CREATE_LIB=yes \ + make_deps=yes +ncl: native-code-library + +# generates byte-code with debugging information +debug-code: $(AUTO_TARGETS) + @$(MAKE) -r -f $(OCAMLMAKEFILE) $(BCRESULT) \ + REAL_RESULT="$(BCRESULT)" make_deps=yes \ + OCAMLFLAGS="-g $(OCAMLFLAGS)" \ + OCAMLLDFLAGS="-g $(OCAMLLDFLAGS)" +dc: debug-code + + +# generates byte-code with debugging information +debug-code-nolink: $(AUTO_TARGETS) + @$(MAKE) -r -f $(OCAMLMAKEFILE) $(BCRESULT) \ + REAL_RESULT_NOLINK="$(BCRESULT)" make_deps=yes \ + OCAMLFLAGS="-g $(OCAMLFLAGS)" \ + OCAMLLDFLAGS="-g $(OCAMLLDFLAGS)" +dc: debug-code + +# generates byte-code libraries with debugging information +debug-code-library: $(AUTO_TARGETS) + @$(MAKE) -r -f $(OCAMLMAKEFILE) $(BCRESULT).cma \ + REAL_RESULT="$(BCRESULT)" make_deps=yes \ + CREATE_LIB=yes \ + OCAMLFLAGS="-g $(OCAMLFLAGS)" \ + OCAMLLDFLAGS="-g $(OCAMLLDFLAGS)" +dcl: debug-code-library + +# generates byte-code for profiling +profiling-byte-code: $(AUTO_TARGETS) + @$(MAKE) -r -f $(OCAMLMAKEFILE) $(BCRESULT) \ + REAL_RESULT="$(BCRESULT)" PROFILING="y" \ + make_deps=yes +pbc: profiling-byte-code + +# generates native-code + +profiling-native-code: $(AUTO_TARGETS) + @$(MAKE) -r -f $(OCAMLMAKEFILE) $(NCRESULT) \ + REAL_RESULT="$(NCRESULT)" \ + REAL_OCAMLC="$(OCAMLOPT)" \ + PROFILING="y" \ + make_deps=yes +pnc: profiling-native-code + +# generates byte-code libraries +profiling-byte-code-library: $(AUTO_TARGETS) + @$(MAKE) -r -f $(OCAMLMAKEFILE) $(BCRESULT).cma \ + REAL_RESULT="$(BCRESULT)" PROFILING="y" \ + CREATE_LIB=yes \ + make_deps=yes +pbcl: profiling-byte-code-library + +# generates native-code libraries +profiling-native-code-library: $(AUTO_TARGETS) + @$(MAKE) -r -f $(OCAMLMAKEFILE) $(NCRESULT).cmxa \ + REAL_RESULT="$(NCRESULT)" PROFILING="y" \ + REAL_OCAMLC="$(OCAMLOPT)" \ + CREATE_LIB=yes \ + make_deps=yes +pncl: profiling-native-code-library + +########################################################################### +# LOW LEVEL RULES + +$(REAL_RESULT): $(REAL_IMPL_INTF) $(OBJ_LINK) + $(REAL_OCAMLC) $(ALL_LDFLAGS) $(OBJS_LIBS) -o $@ \ + $(REAL_IMPL) + +$(REAL_RESULT_NOLINK): $(REAL_IMPL_INTF) $(OBJ_LINK) +# $(REAL_OCAMLC) $(ALL_LDFLAGS) $(OBJS_LIBS) -o $@ \ +# $(REAL_IMPL) + +%$(TOPSUFFIX): $(REAL_IMPL_INTF) $(OBJ_LINK) + $(OCAMLMKTOP) $(ALL_LDFLAGS) $(OBJS_LIBS) -o $@ \ + $(REAL_IMPL) + +.SUFFIXES: .mli .ml .cmi .cmo .cmx .cma .cmxa .o \ + .mly .di .d .a .idl .c .h + +%.cma: $(REAL_IMPL_INTF) $(OBJ_LINK) + $(REAL_OCAMLC) -a $(ALL_LDFLAGS) $(OBJS_LIBS) -o $@ \ + $(OCAMLBLDFLAGS) $(REAL_IMPL) + +%.cmxa %.a: $(REAL_IMPL_INTF) $(OBJ_LINK) + $(OCAMLOPT) -a $(ALL_LDFLAGS) $(OBJS_LIBS) \ + $(OCAMLNLDFLAGS) -o $@ $(REAL_IMPL) + +.mli.cmi: + $(INTF_OCAMLC) -c $(THREAD_FLAG) $(OCAMLFLAGS) \ + $(INCFLAGS) $< + +.ml.cmi .ml.o .ml.cmx .ml.cmo: + $(REAL_OCAMLC) -c $(ALL_OCAMLCFLAGS) $< + +.PRECIOUS: %.ml +%.ml: %.mll + $(OCAMLLEX) $< + +.PRECIOUS: %.ml %.mli +%.ml %.mli: %.mly + $(OCAMLYACC) $(YFLAGS) $< + +.PRECIOUS: %.ml %.mli %_idl.c %.h +%.ml %.mli %_idl.c %.h: %.idl + $(CAMLIDL) $(MAYBE_IDL_HEADER) $(IDLFLAGS) \ + $(CAMLIDLFLAGS) $< + @if [ $(NOIDLHEADER) ]; then touch $*.h; fi; + mv $*.c $*_idl.c + +.c.o: + $(CC) -c $(CFLAGS) $(CINCFLAGS) -I"$(OCAMLLIBPATH)" \ + $< -o $@ + +$(MLDEPDIR)/%.d: %.ml +# @echo making $@ from $< + @d=`dirname $@`; \ + if [ ! -d $$d ]; then mkdir -p $$d; fi + @$(OCAMLDEP) $(INCFLAGS) $< > $@ + +$(BCDIDIR)/%.di $(NCDIDIR)/%.di: %.mli +# @echo making $@ from $< + @d=`dirname $@`; \ + if [ ! -d $$d ]; then mkdir -p $$d; fi + @$(OCAMLDEP) $(DEPFLAGS) $(INCFLAGS) $< > $@ + +########################################################################### +# (UN)INSTALL RULES FOR LIBRARIES + +.PHONY: libinstall +libinstall: all + @printf "\nInstalling library to: $(OCAML_LIB_INSTALL)\n" + @printf "Is this ok? (y/[n]) - " + @\ + read ans; \ + if [ "$$ans" != "y" ]; then \ + echo Installation aborted.; exit 1; fi + @echo + -install -d $(OCAML_LIB_INSTALL) + for i in $(LIBINSTALL_FILES); \ + do install -m 0644 $$i $(OCAML_LIB_INSTALL); done + @printf "\nInstallation successful.\n" + +.PHONY: libuninstall +libuninstall: + @printf "\nUninstalling library from: $(OCAML_LIB_INSTALL)\n" + @printf "Is this ok? (y/[n]) - " + @\ + read ans; \ + if [ "$$ans" != "y" ]; then \ + echo Uninstallation aborted.; exit 1; fi + @echo + cd $(OCAML_LIB_INSTALL); rm $(LIBINSTALL_FILES) + @printf "\nUninstallation successful.\n" + +########################################################################### +# MAINTAINANCE RULES + +.PHONY: clean +clean: + rm -f $(TARGETS) $(TRASH) + rm -rf $(BCDIDIR) $(NCDIDIR) $(MLDEPDIR) + +.PHONY: nobackup +nobackup: + rm -f *.bak *~ *.dup diff --git a/infer/src/backend/PROFILE_HOWTO b/infer/src/backend/PROFILE_HOWTO new file mode 100644 index 000000000..05447f640 --- /dev/null +++ b/infer/src/backend/PROFILE_HOWTO @@ -0,0 +1,12 @@ +[Based on a linux machine] + +1) comment out the line + # PROFILE := true #To activate profiler +in cil/Makefile.in +2) configure +3) make +4) run test, e.g. + ./bin/cilly -c --doanalysis --test ../test/creation.c +5) display the profile info: + gprof ./obj/x86_LINUX/cilly.asm.exe + diff --git a/infer/src/backend/abs.ml b/infer/src/backend/abs.ml new file mode 100644 index 000000000..528c5cc6a --- /dev/null +++ b/infer/src/backend/abs.ml @@ -0,0 +1,1460 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Implementation of Abstraction Functions *) + +module L = Logging +module F = Format +open Utils + +(** {2 Abstraction} *) + +type rule = + { r_vars: Ident.t list; + r_root: Match.hpred_pat; + r_sigma: Match.hpred_pat list; (* sigma should be in a specific order *) + r_new_sigma: Sil.hpred list; + r_new_pi: Prop.normal Prop.t -> Prop.normal Prop.t -> Sil.subst -> Sil.atom list; + r_condition: Prop.normal Prop.t -> Sil.subst -> bool } + +let sigma_rewrite p r : Prop.normal Prop.t option = + match (Match.prop_match_with_impl p r.r_condition r.r_vars r.r_root r.r_sigma) with + | None -> None + | Some(sub, p_leftover) -> + if not (r.r_condition p_leftover sub) then None + else + let res_pi = r.r_new_pi p p_leftover sub in + let res_sigma = Prop.sigma_sub sub r.r_new_sigma in + let p_with_res_pi = list_fold_left Prop.prop_atom_and p_leftover res_pi in + let p_new = Prop.prop_sigma_star p_with_res_pi res_sigma in + Some (Prop.normalize p_new) + +let sigma_fav_list sigma = + Sil.fav_to_list (Prop.sigma_fav sigma) + +let sigma_fav_in_pvars = + Sil.fav_imperative_to_functional Prop.sigma_fav_in_pvars_add + +let sigma_fav_in_pvars_list sigma = + Sil.fav_to_list (sigma_fav_in_pvars sigma) + +(******************** Start of SLL abstraction rules *****************) +let create_fresh_primeds_ls para = + let id_base = Ident.create_fresh Ident.kprimed in + let id_next = Ident.create_fresh Ident.kprimed in + let id_end = Ident.create_fresh Ident.kprimed in + let ids_shared = + let svars = para.Sil.svars in + let f id = Ident.create_fresh Ident.kprimed in + list_map f svars in + let ids_tuple = (id_base, id_next, id_end, ids_shared) in + let exp_base = Sil.Var id_base in + let exp_next = Sil.Var id_next in + let exp_end = Sil.Var id_end in + let exps_shared = list_map (fun id -> Sil.Var id) ids_shared in + let exps_tuple = (exp_base, exp_next, exp_end, exps_shared) in + (ids_tuple, exps_tuple) + +let create_condition_ls ids_private id_base p_leftover (inst: Sil.subst) = + let (insts_of_private_ids, insts_of_public_ids, inst_of_base) = + let f id' = list_exists (fun id'' -> Ident.equal id' id'') ids_private in + let (inst_private, inst_public) = Sil.sub_domain_partition f inst in + let insts_of_public_ids = Sil.sub_range inst_public in + let inst_of_base = try Sil.sub_find (Ident.equal id_base) inst_public with Not_found -> assert false in + let insts_of_private_ids = Sil.sub_range inst_private in + (insts_of_private_ids, insts_of_public_ids, inst_of_base) in + let fav_insts_of_public_ids = list_flatten (list_map Sil.exp_fav_list insts_of_public_ids) in + let fav_insts_of_private_ids = list_flatten (list_map Sil.exp_fav_list insts_of_private_ids) in + let (fav_p_leftover, fav_in_pvars) = + let sigma = Prop.get_sigma p_leftover in + (sigma_fav_list sigma, sigma_fav_in_pvars_list sigma) in + let fpv_inst_of_base = Sil.exp_fpv inst_of_base in + let fpv_insts_of_private_ids = list_flatten (list_map Sil.exp_fpv insts_of_private_ids) in + (* + let fav_inst_of_base = Sil.exp_fav_list inst_of_base in + L.out "@[.... application of condition ....@\n@."; + L.out "@[<4> private ids : %a@\n@." pp_exp_list insts_of_private_ids; + L.out "@[<4> public ids : %a@\n@." pp_exp_list insts_of_public_ids; + *) + (* (not (list_intersect compare fav_inst_of_base fav_in_pvars)) && *) + (fpv_inst_of_base = []) && + (fpv_insts_of_private_ids = []) && + (not (list_exists Ident.is_normal fav_insts_of_private_ids)) && + (not (Utils.list_intersect Ident.compare fav_insts_of_private_ids fav_p_leftover)) && + (not (Utils.list_intersect Ident.compare fav_insts_of_private_ids fav_insts_of_public_ids)) + +let mk_rule_ptspts_ls impl_ok1 impl_ok2 (para: Sil.hpara) = + let (ids_tuple, exps_tuple) = create_fresh_primeds_ls para in + let (id_base, id_next, id_end, ids_shared) = ids_tuple in + let (exp_base, exp_next, exp_end, exps_shared) = exps_tuple in + let (ids_exist_fst, para_fst) = Sil.hpara_instantiate para exp_base exp_next exps_shared in + let (para_fst_start, para_fst_rest) = + let mark_impl_flag hpred = { Match.hpred = hpred; Match.flag = impl_ok1 } in + match para_fst with + | [] -> L.out "@.@.ERROR (Empty Para): %a @.@." (Sil.pp_hpara pe_text) para; assert false + | hpred :: hpreds -> + let hpat = mark_impl_flag hpred in + let hpats = list_map mark_impl_flag hpreds in + (hpat, hpats) in + let (ids_exist_snd, para_snd) = + let mark_impl_flag hpred = { Match.hpred = hpred; Match.flag = impl_ok2 } in + let (ids, para_body) = Sil.hpara_instantiate para exp_next exp_end exps_shared in + let para_body_hpats = list_map mark_impl_flag para_body in + (ids, para_body_hpats) in + let lseg_res = Prop.mk_lseg Sil.Lseg_NE para exp_base exp_end exps_shared in + let gen_pi_res p_start p_leftover (inst: Sil.subst) = [] in + let condition = + let ids_private = id_next :: (ids_exist_fst @ ids_exist_snd) in + create_condition_ls ids_private id_base in + { r_vars = id_base :: id_next :: id_end :: ids_shared @ ids_exist_fst @ ids_exist_snd; + r_root = para_fst_start; + r_sigma = para_fst_rest @ para_snd; + r_new_sigma = [lseg_res]; + r_new_pi = gen_pi_res; + r_condition = condition } + +let mk_rule_ptsls_ls k2 impl_ok1 impl_ok2 para = + let (ids_tuple, exps_tuple) = create_fresh_primeds_ls para in + let (id_base, id_next, id_end, ids_shared) = ids_tuple in + let (exp_base, exp_next, exp_end, exps_shared) = exps_tuple in + let (ids_exist, para_inst) = Sil.hpara_instantiate para exp_base exp_next exps_shared in + let (para_inst_start, para_inst_rest) = + match para_inst with + | [] -> L.out "@.@.ERROR (Empty Para): %a @.@." (Sil.pp_hpara pe_text) para; assert false + | hpred :: hpreds -> + let allow_impl hpred = { Match.hpred = hpred; Match.flag = impl_ok1 } in + (allow_impl hpred, list_map allow_impl hpreds) in + let lseg_pat = { Match.hpred = Prop.mk_lseg k2 para exp_next exp_end exps_shared; Match.flag = impl_ok2 } in + let lseg_res = Prop.mk_lseg Sil.Lseg_NE para exp_base exp_end exps_shared in + let gen_pi_res p_start p_leftover (inst: Sil.subst) = [] in + let condition = + let ids_private = id_next :: ids_exist in + create_condition_ls ids_private id_base in + { r_vars = id_base :: id_next :: id_end :: ids_shared @ ids_exist; + r_root = para_inst_start; + r_sigma = para_inst_rest @ [lseg_pat]; + r_new_pi = gen_pi_res; + r_new_sigma = [lseg_res]; + r_condition = condition } + +let mk_rule_lspts_ls k1 impl_ok1 impl_ok2 para = + let (ids_tuple, exps_tuple) = create_fresh_primeds_ls para in + let (id_base, id_next, id_end, ids_shared) = ids_tuple in + let (exp_base, exp_next, exp_end, exps_shared) = exps_tuple in + let lseg_pat = { Match.hpred = Prop.mk_lseg k1 para exp_base exp_next exps_shared; Match.flag = impl_ok1 } in + let (ids_exist, para_inst_pat) = + let (ids, para_body) = Sil.hpara_instantiate para exp_next exp_end exps_shared in + let allow_impl hpred = { Match.hpred = hpred; Match.flag = impl_ok2 } in + let para_body_pat = list_map allow_impl para_body in + (ids, para_body_pat) in + let lseg_res = Prop.mk_lseg Sil.Lseg_NE para exp_base exp_end exps_shared in + let gen_pi_res p_start p_leftover (inst: Sil.subst) = [] in + let condition = + let ids_private = id_next :: ids_exist in + create_condition_ls ids_private id_base in + { r_vars = id_base :: id_next :: id_end :: ids_shared @ ids_exist; + r_root = lseg_pat; + r_sigma = para_inst_pat; + r_new_sigma = [lseg_res]; + r_new_pi = gen_pi_res; + r_condition = condition } + +let lseg_kind_add k1 k2 = match k1, k2 with + | Sil.Lseg_NE, Sil.Lseg_NE | Sil.Lseg_NE, Sil.Lseg_PE | Sil.Lseg_PE, Sil.Lseg_NE -> Sil.Lseg_NE + | Sil.Lseg_PE, Sil.Lseg_PE -> Sil.Lseg_PE + +let mk_rule_lsls_ls k1 k2 impl_ok1 impl_ok2 para = + let (ids_tuple, exps_tuple) = create_fresh_primeds_ls para in + let (id_base, id_next, id_end, ids_shared) = ids_tuple in + let (exp_base, exp_next, exp_end, exps_shared) = exps_tuple in + let lseg_fst_pat = + { Match.hpred = Prop.mk_lseg k1 para exp_base exp_next exps_shared; Match.flag = impl_ok1 } in + let lseg_snd_pat = + { Match.hpred = Prop.mk_lseg k2 para exp_next exp_end exps_shared; Match.flag = impl_ok2 } in + let k_res = lseg_kind_add k1 k2 in + let lseg_res = Prop.mk_lseg k_res para exp_base exp_end exps_shared in + let gen_pi_res p_start p_leftover (inst: Sil.subst) = [] + (* + let inst_base, inst_next, inst_end = + let find x = sub_find (equal x) inst in + try + (find id_base, find id_next, find id_end) + with Not_found -> assert false in + let spooky_case _ = + (lseg_kind_equal Sil.Lseg_PE k_res) + && (check_allocatedness p_leftover inst_end) + && ((check_disequal p_start inst_base inst_next) + || (check_disequal p_start inst_next inst_end)) in + let obvious_case _ = + check_disequal p_start inst_base inst_end && + not (check_disequal p_leftover inst_base inst_end) in + if not (spooky_case () || obvious_case ()) then [] + else [Aneq(inst_base, inst_end)] + *) + in + let condition = + let ids_private = [id_next] in + create_condition_ls ids_private id_base in + { r_vars = id_base :: id_next :: id_end :: ids_shared ; + r_root = lseg_fst_pat; + r_sigma = [lseg_snd_pat]; + r_new_sigma = [lseg_res]; + r_new_pi = gen_pi_res; + r_condition = condition } + +let mk_rules_for_sll (para : Sil.hpara) : rule list = + if not !Config.nelseg then + begin + let pts_pts = mk_rule_ptspts_ls true true para in + let pts_pels = mk_rule_ptsls_ls Sil.Lseg_PE true false para in + let pels_pts = mk_rule_lspts_ls Sil.Lseg_PE false true para in + let pels_nels = mk_rule_lsls_ls Sil.Lseg_PE Sil.Lseg_NE false false para in + let nels_pels = mk_rule_lsls_ls Sil.Lseg_NE Sil.Lseg_PE false false para in + let pels_pels = mk_rule_lsls_ls Sil.Lseg_PE Sil.Lseg_PE false false para in + [pts_pts; pts_pels; pels_pts; pels_nels; nels_pels; pels_pels] + end + else + begin + let pts_pts = mk_rule_ptspts_ls true true para in + let pts_nels = mk_rule_ptsls_ls Sil.Lseg_NE true false para in + let nels_pts = mk_rule_lspts_ls Sil.Lseg_NE false true para in + let nels_nels = mk_rule_lsls_ls Sil.Lseg_NE Sil.Lseg_NE false false para in + [pts_pts; pts_nels; nels_pts; nels_nels] + end +(****************** End of SLL abstraction rules ******************) + +(****************** Start of DLL abstraction rules ******************) +let create_condition_dll = create_condition_ls + +let mk_rule_ptspts_dll impl_ok1 impl_ok2 para = + let id_iF = Ident.create_fresh Ident.kprimed in + let id_iF' = Ident.create_fresh Ident.kprimed in + let id_oB = Ident.create_fresh Ident.kprimed in + let id_oF = Ident.create_fresh Ident.kprimed in + let ids_shared = + let svars = para.Sil.svars_dll in + let f id = Ident.create_fresh Ident.kprimed in + list_map f svars in + let exp_iF = Sil.Var id_iF in + let exp_iF' = Sil.Var id_iF' in + let exp_oB = Sil.Var id_oB in + let exp_oF = Sil.Var id_oF in + let exps_shared = list_map (fun id -> Sil.Var id) ids_shared in + let (ids_exist_fst, para_fst) = Sil.hpara_dll_instantiate para exp_iF exp_oB exp_iF' exps_shared in + let (para_fst_start, para_fst_rest) = + let mark_impl_flag hpred = { Match.hpred = hpred; Match.flag = impl_ok1 } in + match para_fst with + | [] -> L.out "@.@.ERROR (Empty DLL para): %a@.@." (Sil.pp_hpara_dll pe_text) para; assert false + | hpred :: hpreds -> + let hpat = mark_impl_flag hpred in + let hpats = list_map mark_impl_flag hpreds in + (hpat, hpats) in + let (ids_exist_snd, para_snd) = + let mark_impl_flag hpred = { Match.hpred = hpred; Match.flag = impl_ok2 } in + let (ids, para_body) = Sil.hpara_dll_instantiate para exp_iF' exp_iF exp_oF exps_shared in + let para_body_hpats = list_map mark_impl_flag para_body in + (ids, para_body_hpats) in + let dllseg_res = Prop.mk_dllseg Sil.Lseg_NE para exp_iF exp_oB exp_oF exp_iF' exps_shared in + let gen_pi_res p_start p_leftover (inst: Sil.subst) = [] in + let condition = + (* for the case of ptspts since iF'=iB therefore iF' cannot be private*) + let ids_private = ids_exist_fst @ ids_exist_snd in + create_condition_dll ids_private id_iF in + (* + L.out "r_root/para_fst_start=%a @.@." pp_hpat para_fst_start; + L.out "para_fst_rest=%a @.@." pp_hpat_list para_fst_rest; + L.out "para_snd=%a @.@." pp_hpat_list para_snd; + L.out "dllseg_res=%a @.@." pp_hpred dllseg_res; + *) + { r_vars = id_iF :: id_oB :: id_iF':: id_oF :: ids_shared @ ids_exist_fst @ ids_exist_snd; + r_root = para_fst_start; + r_sigma = para_fst_rest @ para_snd; + r_new_sigma = [dllseg_res]; + r_new_pi = gen_pi_res; + r_condition = condition } + +let mk_rule_ptsdll_dll k2 impl_ok1 impl_ok2 para = + let id_iF = Ident.create_fresh Ident.kprimed in + let id_iF' = Ident.create_fresh Ident.kprimed in + let id_oB = Ident.create_fresh Ident.kprimed in + let id_oF = Ident.create_fresh Ident.kprimed in + let id_iB = Ident.create_fresh Ident.kprimed in + let ids_shared = + let svars = para.Sil.svars_dll in + let f id = Ident.create_fresh Ident.kprimed in + list_map f svars in + let exp_iF = Sil.Var id_iF in + let exp_iF' = Sil.Var id_iF' in + let exp_oB = Sil.Var id_oB in + let exp_oF = Sil.Var id_oF in + let exp_iB = Sil.Var id_iB in + let exps_shared = list_map (fun id -> Sil.Var id) ids_shared in + let (ids_exist, para_inst) = Sil.hpara_dll_instantiate para exp_iF exp_oB exp_iF' exps_shared in + let (para_inst_start, para_inst_rest) = + match para_inst with + | [] -> assert false + | hpred :: hpreds -> + let allow_impl hpred = { Match.hpred = hpred; Match.flag = impl_ok1 } in + (allow_impl hpred, list_map allow_impl hpreds) in + let dllseg_pat = { Match.hpred = Prop.mk_dllseg k2 para exp_iF' exp_iF exp_oF exp_iB exps_shared; Match.flag = impl_ok2 } in + let dllseg_res = Prop.mk_dllseg Sil.Lseg_NE para exp_iF exp_oB exp_oF exp_iB exps_shared in + let gen_pi_res p_start p_leftover (inst: Sil.subst) = [] in + let condition = + let ids_private = id_iF':: ids_exist in + create_condition_dll ids_private id_iF in + { r_vars = id_iF :: id_oB :: id_iF':: id_oF:: id_iB:: ids_shared @ ids_exist; + r_root = para_inst_start; + r_sigma = para_inst_rest @ [dllseg_pat]; + r_new_pi = gen_pi_res; + r_new_sigma = [dllseg_res]; + r_condition = condition } + +let mk_rule_dllpts_dll k1 impl_ok1 impl_ok2 para = + let id_iF = Ident.create_fresh Ident.kprimed in + let id_iF' = Ident.create_fresh Ident.kprimed in + let id_oB = Ident.create_fresh Ident.kprimed in + let id_oB' = Ident.create_fresh Ident.kprimed in + let id_oF = Ident.create_fresh Ident.kprimed in + let ids_shared = + let svars = para.Sil.svars_dll in + let f id = Ident.create_fresh Ident.kprimed in + list_map f svars in + let exp_iF = Sil.Var id_iF in + let exp_iF' = Sil.Var id_iF' in + let exp_oB = Sil.Var id_oB in + let exp_oB' = Sil.Var id_oB' in + let exp_oF = Sil.Var id_oF in + let exps_shared = list_map (fun id -> Sil.Var id) ids_shared in + let (ids_exist, para_inst) = Sil.hpara_dll_instantiate para exp_iF' exp_oB' exp_oF exps_shared in + let para_inst_pat = + let allow_impl hpred = { Match.hpred = hpred; Match.flag = impl_ok2 } in + list_map allow_impl para_inst in + let dllseg_pat = { Match.hpred = Prop.mk_dllseg k1 para exp_iF exp_oB exp_iF' exp_oB' exps_shared; Match.flag = impl_ok1 } in + let dllseg_res = Prop.mk_dllseg Sil.Lseg_NE para exp_iF exp_oB exp_oF exp_iF' exps_shared in + let gen_pi_res p_start p_leftover (inst: Sil.subst) = [] in + let condition = + let ids_private = id_oB':: ids_exist in + create_condition_dll ids_private id_iF in + { r_vars = id_iF :: id_oB :: id_iF':: id_oB':: id_oF:: ids_shared @ ids_exist; + r_root = dllseg_pat; + r_sigma = para_inst_pat; + r_new_pi = gen_pi_res; + r_new_sigma = [dllseg_res]; + r_condition = condition } + +let mk_rule_dlldll_dll k1 k2 impl_ok1 impl_ok2 para = + let id_iF = Ident.create_fresh Ident.kprimed in + let id_iF' = Ident.create_fresh Ident.kprimed in + let id_oB = Ident.create_fresh Ident.kprimed in + let id_oB' = Ident.create_fresh Ident.kprimed in + let id_oF = Ident.create_fresh Ident.kprimed in + let id_iB = Ident.create_fresh Ident.kprimed in + let ids_shared = + let svars = para.Sil.svars_dll in + let f id = Ident.create_fresh Ident.kprimed in + list_map f svars in + let exp_iF = Sil.Var id_iF in + let exp_iF' = Sil.Var id_iF' in + let exp_oB = Sil.Var id_oB in + let exp_oB' = Sil.Var id_oB' in + let exp_oF = Sil.Var id_oF in + let exp_iB = Sil.Var id_iB in + let exps_shared = list_map (fun id -> Sil.Var id) ids_shared in + let lseg_fst_pat = { Match.hpred = Prop.mk_dllseg k1 para exp_iF exp_oB exp_iF' exp_oB' exps_shared; Match.flag = impl_ok1 } in + let lseg_snd_pat = { Match.hpred = Prop.mk_dllseg k2 para exp_iF' exp_oB' exp_oF exp_iB exps_shared; Match.flag = impl_ok2 } in + let k_res = lseg_kind_add k1 k2 in + let lseg_res = Prop.mk_dllseg k_res para exp_iF exp_oB exp_oF exp_iB exps_shared in + let gen_pi_res p_start p_leftover (inst: Sil.subst) = [] in + let condition = + let ids_private = [id_iF'; id_oB'] in + create_condition_dll ids_private id_iF in + { r_vars = id_iF :: id_iF' :: id_oB:: id_oB' :: id_oF:: id_iB:: ids_shared ; + r_root = lseg_fst_pat; + r_sigma = [lseg_snd_pat]; + r_new_sigma = [lseg_res]; + r_new_pi = gen_pi_res; + r_condition = condition } + +let mk_rules_for_dll (para : Sil.hpara_dll) : rule list = + if not !Config.nelseg then + begin + let pts_pts = mk_rule_ptspts_dll true true para in + let pts_pedll = mk_rule_ptsdll_dll Sil.Lseg_PE true false para in + let pedll_pts = mk_rule_dllpts_dll Sil.Lseg_PE false true para in + let pedll_nedll = mk_rule_dlldll_dll Sil.Lseg_PE Sil.Lseg_NE false false para in + let nedll_pedll = mk_rule_dlldll_dll Sil.Lseg_NE Sil.Lseg_PE false false para in + let pedll_pedll = mk_rule_dlldll_dll Sil.Lseg_PE Sil.Lseg_PE false false para in + [pts_pts; pts_pedll; pedll_pts; pedll_nedll; nedll_pedll; pedll_pedll] + end + else + begin + let ptspts_dll = mk_rule_ptspts_dll true true para in + let ptsdll_dll = mk_rule_ptsdll_dll Sil.Lseg_NE true false para in + let dllpts_dll = mk_rule_dllpts_dll Sil.Lseg_NE false true para in + let dlldll_dll = mk_rule_dlldll_dll Sil.Lseg_NE Sil.Lseg_NE false false para in + [ptspts_dll; ptsdll_dll; dllpts_dll; dlldll_dll] + end +(****************** End of DLL abstraction rules ******************) + +(****************** Start of Predicate Discovery ******************) +let typ_get_recursive_flds tenv te = + let filter (_, t, _) = + match t with + | Sil.Tvar _ | Sil.Tint _ | Sil.Tfloat _ | Sil.Tvoid | Sil.Tfun _ -> false + | Sil.Tptr (Sil.Tvar tname', _) -> + let typ' = match Sil.tenv_lookup tenv tname' with + | None -> + L.err "@.typ_get_recursive: Undefined type %s@." (Sil.typename_to_string tname'); + t + | Some typ' -> typ' in + Sil.exp_equal te (Sil.Sizeof (typ', Sil.Subtype.exact)) + | Sil.Tptr _ | Sil.Tstruct _ | Sil.Tarray _ | Sil.Tenum _ -> + false + in + match te with + | Sil.Sizeof (typ, _) -> + (match typ with + | Sil.Tvar _ -> assert false (* there should be no indirection *) + | Sil.Tint _ | Sil.Tvoid | Sil.Tfun _ | Sil.Tptr _ | Sil.Tfloat _ | Sil.Tenum _ -> [] + | Sil.Tstruct (fld_typ_ann_list, _, _, _, _, _, _) -> list_map (fun (x, y, z) -> x) (list_filter filter fld_typ_ann_list) + | Sil.Tarray _ -> []) + | Sil.Var _ -> [] (* type of |-> not known yet *) + | Sil.Const _ -> [] + | _ -> + L.err "@.typ_get_recursive: unexpected type expr: %a@." (Sil.pp_exp pe_text) te; + assert false + +let discover_para_roots p root1 next1 root2 next2 : Sil.hpara option = + let eq_arg1 = Sil.exp_equal root1 next1 in + let eq_arg2 = Sil.exp_equal root2 next2 in + let precondition_check = (not eq_arg1 && not eq_arg2) in + if not precondition_check then None + else + let corres = [(next1, next2)] in + let todos = [(root1, root2)] in + let sigma = Prop.get_sigma p in + match Match.find_partial_iso (Prover.check_equal p) corres todos sigma with + | None -> None + | Some (new_corres, new_sigma1, _, _) -> + let hpara, _ = Match.hpara_create new_corres new_sigma1 root1 next1 in + Some hpara + +let discover_para_dll_roots p root1 blink1 flink1 root2 blink2 flink2 : Sil.hpara_dll option = + let eq_arg1 = Sil.exp_equal root1 blink1 in + let eq_arg1' = Sil.exp_equal root1 flink1 in + let eq_arg2 = Sil.exp_equal root2 blink2 in + let eq_arg2' = Sil.exp_equal root2 flink2 in + let precondition_check = not (eq_arg1 || eq_arg1' || eq_arg2 || eq_arg2') in + if not precondition_check then None + else + let corres = [(blink1, blink2); (flink1, flink2)] in + let todos = [(root1, root2)] in + let sigma = Prop.get_sigma p in + match Match.find_partial_iso (Prover.check_equal p) corres todos sigma with + | None -> None + | Some (new_corres, new_sigma1, _, _) -> + let hpara_dll, _ = Match.hpara_dll_create new_corres new_sigma1 root1 blink1 flink1 in + Some hpara_dll + +let discover_para_candidates tenv p = + let edges = ref [] in + let add_edge edg = edges := edg :: !edges in + let get_edges_strexp rec_flds root se = + let is_rec_fld fld = list_exists (Sil.fld_equal fld) rec_flds in + match se with + | Sil.Eexp _ | Sil.Earray _ -> () + | Sil.Estruct (fsel, _) -> + let fsel' = list_filter (fun (fld, _) -> is_rec_fld fld) fsel in + let process (_, nextse) = + match nextse with + | Sil.Eexp (next, inst) -> add_edge (root, next) + | _ -> assert false in + list_iter process fsel' in + let rec get_edges_sigma = function + | [] -> () + | Sil.Hlseg _ :: sigma_rest | Sil.Hdllseg _ :: sigma_rest -> + get_edges_sigma sigma_rest + | Sil.Hpointsto (root, se, te) :: sigma_rest -> + let rec_flds = typ_get_recursive_flds tenv te in + get_edges_strexp rec_flds root se; + get_edges_sigma sigma_rest in + let rec find_all_consecutive_edges found edges_seen = function + | [] -> list_rev found + | (e1, e2) :: edges_notseen -> + let edges_others = (list_rev edges_seen) @ edges_notseen in + let edges_matched = list_filter (fun (e1', _) -> Sil.exp_equal e2 e1') edges_others in + let new_found = + let f found_acc (_, e3) = (e1, e2, e3) :: found_acc in + list_fold_left f found edges_matched in + let new_edges_seen = (e1, e2) :: edges_seen in + find_all_consecutive_edges new_found new_edges_seen edges_notseen in + let sigma = Prop.get_sigma p in + get_edges_sigma sigma; + find_all_consecutive_edges [] [] !edges + +let discover_para_dll_candidates tenv p = + let edges = ref [] in + let add_edge edg = (edges := edg :: !edges) in + let get_edges_strexp rec_flds root se = + let is_rec_fld fld = list_exists (Sil.fld_equal fld) rec_flds in + match se with + | Sil.Eexp _ | Sil.Earray _ -> () + | Sil.Estruct (fsel, _) -> + let fsel' = list_filter (fun (fld, _) -> is_rec_fld fld) fsel in + let convert_to_exp acc (_, se) = + match se with + | Sil.Eexp (e, inst) -> e:: acc + | _ -> assert false in + let links = list_rev (list_fold_left convert_to_exp [] fsel') in + let rec iter_pairs = function + | [] -> () + | x:: l -> (list_iter (fun y -> add_edge (root, x, y)) l; iter_pairs l) in + iter_pairs links in + let rec get_edges_sigma = function + | [] -> () + | Sil.Hlseg _ :: sigma_rest | Sil.Hdllseg _ :: sigma_rest -> + get_edges_sigma sigma_rest + | Sil.Hpointsto (root, se, te) :: sigma_rest -> + let rec_flds = typ_get_recursive_flds tenv te in + get_edges_strexp rec_flds root se; + get_edges_sigma sigma_rest in + let rec find_all_consecutive_edges found edges_seen = function + | [] -> list_rev found + | (iF, blink, flink) :: edges_notseen -> + let edges_others = (list_rev edges_seen) @ edges_notseen in + let edges_matched = list_filter (fun (e1', _, _) -> Sil.exp_equal flink e1') edges_others in + let new_found = + let f found_acc (_, _, flink2) = (iF, blink, flink, flink2) :: found_acc in + list_fold_left f found edges_matched in + let new_edges_seen = (iF, blink, flink) :: edges_seen in + find_all_consecutive_edges new_found new_edges_seen edges_notseen in + let sigma = Prop.get_sigma p in + get_edges_sigma sigma; + find_all_consecutive_edges [] [] !edges + +let discover_para tenv p = + let candidates = discover_para_candidates tenv p in + let already_defined para paras = + list_exists (fun para' -> Match.hpara_iso para para') paras in + let f paras (root, next, out) = + match (discover_para_roots p root next next out) with + | None -> paras + | Some para -> if already_defined para paras then paras else para :: paras in + list_fold_left f [] candidates + +let discover_para_dll tenv p = + (* + L.out "@[.... Called discover_dll para ...@."; + L.out "@[<4> PROP : %a@\n@." pp_prop p; + *) + let candidates = discover_para_dll_candidates tenv p in + let already_defined para paras = + list_exists (fun para' -> Match.hpara_dll_iso para para') paras in + let f paras (iF, oB, iF', oF) = + match (discover_para_dll_roots p iF oB iF' iF' iF oF) with + | None -> paras + | Some para -> if already_defined para paras then paras else para :: paras in + list_fold_left f [] candidates +(****************** Start of Predicate Discovery ******************) + +(****************** Start of the ADT abs_rules ******************) +type para_ty = SLL of Sil.hpara | DLL of Sil.hpara_dll + +type rule_set = para_ty * rule list + +type abs_rules = { mutable ar_default : rule_set list } + +let eqs_sub subst eqs = + list_map (fun (e1, e2) -> (Sil.exp_sub subst e1, Sil.exp_sub subst e2)) eqs + +let eqs_solve ids_in eqs_in = + let rec solve (sub: Sil.subst) (eqs: (Sil.exp * Sil.exp) list) : Sil.subst option = + let do_default id e eqs_rest = + if not (list_exists (fun id' -> Ident.equal id id') ids_in) then None + else + let sub' = match Sil.extend_sub sub id e with + | None -> L.out "@.@.ERROR : Buggy Implementation.@.@."; assert false + | Some sub' -> sub' in + let eqs_rest' = eqs_sub sub' eqs_rest in + solve sub' eqs_rest' in + match eqs with + | [] -> Some sub + | (e1, e2) :: eqs_rest when Sil.exp_equal e1 e2 -> + solve sub eqs_rest + | (Sil.Var id1, (Sil.Const _ as e2)) :: eqs_rest -> + do_default id1 e2 eqs_rest + | ((Sil.Const _ as e1), (Sil.Var _ as e2)) :: eqs_rest -> + solve sub ((e2, e1):: eqs_rest) + | ((Sil.Var id1 as e1), (Sil.Var id2 as e2)) :: eqs_rest -> + let n = Ident.compare id1 id2 in + begin + if n = 0 then solve sub eqs_rest + else if n > 0 then solve sub ((e2, e1):: eqs_rest) + else do_default id1 e2 eqs_rest + end + | _ :: _ -> None in + let compute_ids sub = + let sub_list = Sil.sub_to_list sub in + let sub_dom = list_map fst sub_list in + let filter id = + not (list_exists (fun id' -> Ident.equal id id') sub_dom) in + list_filter filter ids_in in + match solve Sil.sub_empty eqs_in with + | None -> None + | Some sub -> Some (compute_ids sub, sub) + +let sigma_special_cases_eqs sigma = + let rec f ids_acc eqs_acc sigma_acc = function + | [] -> + [(list_rev ids_acc, list_rev eqs_acc, list_rev sigma_acc)] + | Sil.Hpointsto _ as hpred :: sigma_rest -> + f ids_acc eqs_acc (hpred:: sigma_acc) sigma_rest + | Sil.Hlseg(k, para, e1, e2, es) as hpred :: sigma_rest -> + let empty_case = + f ids_acc ((e1, e2):: eqs_acc) sigma_acc sigma_rest in + let pointsto_case = + let (eids, para_inst) = Sil.hpara_instantiate para e1 e2 es in + f (eids@ids_acc) eqs_acc sigma_acc (para_inst@sigma_rest) in + let general_case = + f ids_acc eqs_acc (hpred:: sigma_acc) sigma_rest in + empty_case @ pointsto_case @ general_case + | Sil.Hdllseg(k, para, e1, e2, e3, e4, es) as hpred :: sigma_rest -> + let empty_case = + f ids_acc ((e1, e3):: (e2, e4):: eqs_acc) sigma_acc sigma_rest in + let pointsto_case = + let (eids, para_inst) = Sil.hpara_dll_instantiate para e1 e2 e3 es in + f (eids@ids_acc) eqs_acc sigma_acc (para_inst@sigma_rest) in + let general_case = + f ids_acc eqs_acc (hpred:: sigma_acc) sigma_rest in + empty_case @ pointsto_case @ general_case in + f [] [] [] sigma + +let sigma_special_cases ids sigma : (Ident.t list * Sil.hpred list) list = + let special_cases_eqs = sigma_special_cases_eqs sigma in + let special_cases_rev = + let f acc (eids_cur, eqs_cur, sigma_cur) = + let ids_all = ids @ eids_cur in + match (eqs_solve ids_all eqs_cur) with + | None -> acc + | Some (ids_res, sub) -> + (ids_res, list_map (Sil.hpred_sub sub) sigma_cur) :: acc in + list_fold_left f [] special_cases_eqs in + list_rev special_cases_rev + +let rec hpara_special_cases hpara : Sil.hpara list = + let update_para (evars', body') = { hpara with Sil.evars = evars'; Sil.body = body'} in + let special_cases = sigma_special_cases hpara.Sil.evars hpara.Sil.body in + list_map update_para special_cases + +let rec hpara_special_cases_dll hpara : Sil.hpara_dll list = + let update_para (evars', body') = { hpara with Sil.evars_dll = evars'; Sil.body_dll = body'} in + let special_cases = sigma_special_cases hpara.Sil.evars_dll hpara.Sil.body_dll in + list_map update_para special_cases + +let abs_rules : abs_rules = { ar_default = [] } + +let abs_rules_reset () = + abs_rules.ar_default <- [] + +let abs_rules_add rule_set : unit = + (* + let _ = match (fst rule_set) with + | SLL hpara -> L.out "@.@....Added Para: %a@.@." pp_hpara hpara + | DLL _ -> () + in + *) + abs_rules.ar_default <- abs_rules.ar_default@[rule_set] + +let abs_rules_add_sll (para: Sil.hpara) : unit = + let rules = mk_rules_for_sll para in + let rule_set = (SLL para, rules) in + abs_rules_add rule_set + +let abs_rules_add_dll (para: Sil.hpara_dll) : unit = + let rules = mk_rules_for_dll para in + let rule_set = (DLL para, rules) in + abs_rules_add rule_set + +let abs_rules_apply_rsets (rsets: rule_set list) (p_in: Prop.normal Prop.t) : Prop.normal Prop.t = + let apply_rule (changed, p) r = + match (sigma_rewrite p r) with + | None -> (changed, p) + | Some p' -> + (* + L.out "@[.... abstraction (rewritten in abs_rules) ....@."; + L.out "@[<4> PROP:%a@\n@." pp_prop p'; + *) + (true, p') in + let rec apply_rule_set p rset = + let (_, rules) = rset in + let (changed, p') = list_fold_left apply_rule (false, p) rules in + if changed then apply_rule_set p' rset else p' in + list_fold_left apply_rule_set p_in rsets + +let abs_rules_apply_lists tenv (p_in: Prop.normal Prop.t) : Prop.normal Prop.t = + let new_rsets = ref [] in + let def_rsets = abs_rules.ar_default in + let rec discover_then_abstract p = + let (closed_paras_sll, closed_paras_dll) = + let paras_sll = discover_para tenv p in + let paras_dll = discover_para_dll tenv p in + let closed_paras_sll = list_flatten (list_map hpara_special_cases paras_sll) in + let closed_paras_dll = list_flatten (list_map hpara_special_cases_dll paras_dll) in + begin + (* + if list_length closed_paras_sll >= 1 then + begin + L.out "@.... discovered predicates ....@."; + L.out "@[<4> pred : %a@\n@." pp_hpara_list closed_paras_sll; + end + if list_length closed_paras_dll >= 1 then + begin + L.out "@.... discovered predicates ....@."; + L.out "@[<4> pred : %a@\n@." pp_hpara_dll_list closed_paras_dll; + end + *) + (closed_paras_sll, closed_paras_dll) + end in + let (todo_paras_sll, todo_paras_dll) = + let eq_sll para = function (SLL para', _) -> Match.hpara_iso para para' | _ -> false in + let eq_dll para = function (DLL para', _) -> Match.hpara_dll_iso para para' | _ -> false in + let filter_sll para = + not (list_exists (eq_sll para) def_rsets) && not (list_exists (eq_sll para) !new_rsets) in + let filter_dll para = + not (list_exists (eq_dll para) def_rsets) && not (list_exists (eq_dll para) !new_rsets) in + let todo_paras_sll = list_filter filter_sll closed_paras_sll in + let todo_paras_dll = list_filter filter_dll closed_paras_dll in + (todo_paras_sll, todo_paras_dll) in + let f_recurse () = + let todo_rsets_sll = list_map (fun para -> (SLL para, mk_rules_for_sll para)) todo_paras_sll in + let todo_rsets_dll = list_map (fun para -> (DLL para, mk_rules_for_dll para)) todo_paras_dll in + new_rsets := !new_rsets @ todo_rsets_sll @ todo_rsets_dll; + let p' = abs_rules_apply_rsets todo_rsets_sll p in + let p'' = abs_rules_apply_rsets todo_rsets_dll p' in + discover_then_abstract p'' in + match todo_paras_sll, todo_paras_dll with + | [], [] -> p + | _ -> f_recurse () in + let p1 = abs_rules_apply_rsets def_rsets p_in in + let p2 = if !Config.on_the_fly then discover_then_abstract p1 else p1 + in + abs_rules.ar_default <- (def_rsets@(!new_rsets)); + p2 + +let abs_rules_apply tenv (p_in: Prop.normal Prop.t) : Prop.normal Prop.t = + abs_rules_apply_lists tenv p_in +(****************** End of the ADT abs_rules ******************) + +(****************** Start of fns that add rules during preprocessing ******************) +let is_simply_recursive tenv tname = + let typ = match Sil.tenv_lookup tenv tname with + | None -> assert false + | Some typ -> typ in + let filter (_, t, _) = match t with + | Sil.Tvar _ | Sil.Tint _ | Sil.Tfloat _ | Sil.Tvoid | Sil.Tfun _ | Sil.Tenum _ -> + false + | Sil.Tptr (Sil.Tvar tname', _) -> + Sil.typename_equal tname tname' + | Sil.Tptr _ | Sil.Tstruct _ | Sil.Tarray _ -> + false in + match typ with + | Sil.Tvar _ -> + assert false (* there should be no indirection *) + | Sil.Tint _ | Sil.Tfloat _ | Sil.Tvoid | Sil.Tfun _ | Sil.Tptr _ | Sil.Tenum _ -> + None + | Sil.Tstruct (fld_typ_ann_list, _, _, _, _, _, _) -> + begin + match (list_filter filter fld_typ_ann_list) with + | [(fld, _, _)] -> Some fld + | _ -> None + end + | Sil.Tarray _ -> + None + +let create_hpara_from_tname_flds tenv tname nfld sflds eflds inst = + let typ = match Sil.tenv_lookup tenv tname with + | Some typ -> typ + | None -> assert false in + let id_base = Ident.create_fresh Ident.kprimed in + let id_next = Ident.create_fresh Ident.kprimed in + let ids_shared = list_map (fun _ -> Ident.create_fresh Ident.kprimed) sflds in + let ids_exist = list_map (fun _ -> Ident.create_fresh Ident.kprimed) eflds in + let exp_base = Sil.Var id_base in + let fld_sexps = + let ids = id_next :: (ids_shared @ ids_exist) in + let flds = nfld :: (sflds @ eflds) in + let f fld id = (fld, Sil.Eexp (Sil.Var id, inst)) in + try list_map2 f flds ids with Invalid_argument _ -> assert false in + let strexp_para = Sil.Estruct (fld_sexps, inst) in + let ptsto_para = Prop.mk_ptsto exp_base strexp_para (Sil.Sizeof (typ, Sil.Subtype.exact)) in + Prop.mk_hpara id_base id_next ids_shared ids_exist [ptsto_para] + +let create_dll_hpara_from_tname_flds tenv tname flink blink sflds eflds inst = + let typ = match Sil.tenv_lookup tenv tname with + | Some typ -> typ + | None -> assert false in + let id_iF = Ident.create_fresh Ident.kprimed in + let id_oB = Ident.create_fresh Ident.kprimed in + let id_oF = Ident.create_fresh Ident.kprimed in + let ids_shared = list_map (fun _ -> Ident.create_fresh Ident.kprimed) sflds in + let ids_exist = list_map (fun _ -> Ident.create_fresh Ident.kprimed) eflds in + let exp_iF = Sil.Var id_iF in + let fld_sexps = + let ids = id_oF:: id_oB :: (ids_shared @ ids_exist) in + let flds = flink:: blink:: (sflds @ eflds) in + let f fld id = (fld, Sil.Eexp (Sil.Var id, inst)) in + try list_map2 f flds ids with Invalid_argument _ -> assert false in + let strexp_para = Sil.Estruct (fld_sexps, inst) in + let ptsto_para = Prop.mk_ptsto exp_iF strexp_para (Sil.Sizeof (typ, Sil.Subtype.exact)) in + Prop.mk_dll_hpara id_iF id_oB id_oF ids_shared ids_exist [ptsto_para] + +let create_hpara_two_ptsto tname1 tenv nfld1 dfld tname2 nfld2 inst = + let typ1 = match Sil.tenv_lookup tenv tname1 with + | Some typ -> typ + | None -> assert false in + let typ2 = match Sil.tenv_lookup tenv tname2 with + | Some typ -> typ + | None -> assert false in + let id_base = Ident.create_fresh Ident.kprimed in + let id_next = Ident.create_fresh Ident.kprimed in + let id_exist = Ident.create_fresh Ident.kprimed in + let exp_base = Sil.Var id_base in + let exp_exist = Sil.Var id_exist in + let fld_sexps1 = + let ids = [id_next; id_exist] in + let flds = [nfld1; dfld] in + let f fld id = (fld, Sil.Eexp (Sil.Var id, inst)) in + try list_map2 f flds ids with Invalid_argument _ -> assert false in + let fld_sexps2 = + [(nfld2, Sil.Eexp (Sil.exp_zero, inst))] in + let strexp_para1 = Sil.Estruct (fld_sexps1, inst) in + let strexp_para2 = Sil.Estruct (fld_sexps2, inst) in + let ptsto_para1 = Prop.mk_ptsto exp_base strexp_para1 (Sil.Sizeof (typ1, Sil.Subtype.exact)) in + let ptsto_para2 = Prop.mk_ptsto exp_exist strexp_para2 (Sil.Sizeof (typ2, Sil.Subtype.exact)) in + Prop.mk_hpara id_base id_next [] [id_exist] [ptsto_para1; ptsto_para2] + +let create_hpara_dll_two_ptsto tenv tname1 flink_fld1 blink_fld1 dfld tname2 nfld2 inst = + let typ1 = match Sil.tenv_lookup tenv tname1 with + | Some typ -> typ + | None -> assert false in + let typ2 = match Sil.tenv_lookup tenv tname2 with + | Some typ -> typ + | None -> assert false in + let id_cell = Ident.create_fresh Ident.kprimed in + let id_blink = Ident.create_fresh Ident.kprimed in + let id_flink = Ident.create_fresh Ident.kprimed in + let id_exist = Ident.create_fresh Ident.kprimed in + let exp_cell = Sil.Var id_cell in + let exp_exist = Sil.Var id_exist in + let fld_sexps1 = + let ids = [ id_blink; id_flink; id_exist] in + let flds = [ blink_fld1; flink_fld1; dfld] in + let f fld id = (fld, Sil.Eexp (Sil.Var id, inst)) in + try list_map2 f flds ids with Invalid_argument _ -> assert false in + let fld_sexps2 = + [(nfld2, Sil.Eexp (Sil.exp_zero, inst))] in + let strexp_para1 = Sil.Estruct (fld_sexps1, inst) in + let strexp_para2 = Sil.Estruct (fld_sexps2, inst) in + let ptsto_para1 = Prop.mk_ptsto exp_cell strexp_para1 (Sil.Sizeof (typ1, Sil.Subtype.exact)) in + let ptsto_para2 = Prop.mk_ptsto exp_exist strexp_para2 (Sil.Sizeof (typ2, Sil.Subtype.exact)) in + Prop.mk_dll_hpara id_cell id_blink id_flink [] [id_exist] [ptsto_para1; ptsto_para2] + +let create_hpara_from_tname_twoflds_hpara tenv tname fld_next fld_down para inst = + let typ = match Sil.tenv_lookup tenv tname with + | Some typ -> typ + | None -> assert false in + let id_base = Ident.create_fresh Ident.kprimed in + let id_next = Ident.create_fresh Ident.kprimed in + let id_down = Ident.create_fresh Ident.kprimed in + let exp_base = Sil.Var id_base in + let exp_next = Sil.Var id_next in + let exp_down = Sil.Var id_down in + let strexp = Sil.Estruct ([(fld_next, Sil.Eexp (exp_next, inst)); (fld_down, Sil.Eexp (exp_down, inst))], inst) in + let ptsto = Prop.mk_ptsto exp_base strexp (Sil.Sizeof (typ, Sil.Subtype.exact)) in + let lseg = Prop.mk_lseg Sil.Lseg_PE para exp_down Sil.exp_zero [] in + let body = [ptsto; lseg] in + Prop.mk_hpara id_base id_next [] [id_down] body + +let create_hpara_dll_from_tname_twoflds_hpara tenv tname fld_flink fld_blink fld_down para inst = + let typ = match Sil.tenv_lookup tenv tname with + | Some typ -> typ + | None -> assert false in + let id_cell = Ident.create_fresh Ident.kprimed in + let id_blink = Ident.create_fresh Ident.kprimed in + let id_flink = Ident.create_fresh Ident.kprimed in + let id_down = Ident.create_fresh Ident.kprimed in + let exp_cell = Sil.Var id_cell in + let exp_blink = Sil.Var id_blink in + let exp_flink = Sil.Var id_flink in + let exp_down = Sil.Var id_down in + let strexp = Sil.Estruct ([(fld_blink, Sil.Eexp (exp_blink, inst)); (fld_flink, Sil.Eexp (exp_flink, inst)); (fld_down, Sil.Eexp (exp_down, inst))], inst) in + let ptsto = Prop.mk_ptsto exp_cell strexp (Sil.Sizeof (typ, Sil.Subtype.exact)) in + let lseg = Prop.mk_lseg Sil.Lseg_PE para exp_down Sil.exp_zero [] in + let body = [ptsto; lseg] in + Prop.mk_dll_hpara id_cell id_blink id_flink [] [id_down] body + +let tname_list = Sil.TN_typedef (Mangled.from_string "list") +let name_down = Ident.create_fieldname (Mangled.from_string "down") 0 +let tname_HSlist2 = Sil.TN_typedef (Mangled.from_string "HSlist2") +let name_next = Ident.create_fieldname (Mangled.from_string "next") 0 + +let tname_dllist = Sil.TN_typedef (Mangled.from_string "dllist") +let name_Flink = Ident.create_fieldname (Mangled.from_string "Flink") 0 +let name_Blink = Ident.create_fieldname (Mangled.from_string "Blink") 0 +let tname_HOdllist = Sil.TN_typedef (Mangled.from_string "HOdllist") + +let create_absrules_from_tdecl tenv tname = + if (not (!Config.on_the_fly)) && Sil.typename_equal tname tname_HSlist2 then + (* L.out "@[.... Adding Abstraction Rules for Nested Lists ....@\n@."; *) + let para1 = create_hpara_from_tname_flds tenv tname_list name_down [] [] Sil.inst_abstraction in + let para2 = create_hpara_from_tname_flds tenv tname_HSlist2 name_next [name_down] [] Sil.inst_abstraction in + let para_nested = create_hpara_from_tname_twoflds_hpara tenv tname_HSlist2 name_next name_down para1 Sil.inst_abstraction in + let para_nested_base = create_hpara_two_ptsto tname_HSlist2 tenv name_next name_down tname_list name_down Sil.inst_abstraction in + list_iter abs_rules_add_sll [para_nested_base; para2; para_nested] + else if (not (!Config.on_the_fly)) && Sil.typename_equal tname tname_dllist then + (* L.out "@[.... Adding Abstraction Rules for Doubly-linked Lists ....@\n@."; *) + let para = create_dll_hpara_from_tname_flds tenv tname_dllist name_Flink name_Blink [] [] Sil.inst_abstraction in + abs_rules_add_dll para + else if (not (!Config.on_the_fly)) && Sil.typename_equal tname tname_HOdllist then + (* L.out "@[.... Adding Abstraction Rules for High-Order Doubly-linked Lists ....@\n@."; *) + let para1 = create_hpara_from_tname_flds tenv tname_list name_down [] [] Sil.inst_abstraction in + let para2 = create_dll_hpara_from_tname_flds tenv tname_HOdllist name_Flink name_Blink [name_down] [] Sil.inst_abstraction in + let para_nested = create_hpara_dll_from_tname_twoflds_hpara tenv tname_HOdllist name_Flink name_Blink name_down para1 Sil.inst_abstraction in + let para_nested_base = create_hpara_dll_two_ptsto tenv tname_HOdllist name_Flink name_Blink name_down tname_list name_down Sil.inst_abstraction in + list_iter abs_rules_add_dll [para_nested_base; para2; para_nested] + else if (not (!Config.on_the_fly)) then + match is_simply_recursive tenv tname with + | None -> () + | Some (fld) -> + (* L.out "@[.... Adding Abstraction Rules ....@\n@."; *) + let para = create_hpara_from_tname_flds tenv tname fld [] [] Sil.inst_abstraction in + abs_rules_add_sll para + else () +(****************** End of fns that add rules during preprocessing ******************) + +(****************** Start of Main Abstraction Functions ******************) +let abstract_pure_part p ~(from_abstract_footprint: bool) = + let do_pure pure = + let pi_filtered = + let sigma = Prop.get_sigma p in + let fav_sigma = Prop.sigma_fav sigma in + let fav_nonpure = Prop.prop_fav_nonpure p in (** vars in current and footprint sigma *) + let filter atom = + let fav' = Sil.atom_fav atom in + Sil.fav_for_all fav' (fun id -> + if Ident.is_primed id then Sil.fav_mem fav_sigma id + else if Ident.is_footprint id then Sil.fav_mem fav_nonpure id + else true) in + list_filter filter pure in + let new_pure = + list_fold_left + (fun pi a -> + match a with + | Sil.Aneq (Sil.Var name, _) -> a:: pi + (* we only use Lt and Le because Gt and Ge are inserted in terms of Lt and Le. *) + | Sil.Aeq (Sil.Const (Sil.Cint i), Sil.BinOp (Sil.Lt, _, _)) + | Sil.Aeq (Sil.BinOp (Sil.Lt, _, _), Sil.Const (Sil.Cint i)) + | Sil.Aeq (Sil.Const (Sil.Cint i), Sil.BinOp (Sil.Le, _, _)) + | Sil.Aeq (Sil.BinOp (Sil.Le, _, _), Sil.Const (Sil.Cint i)) when Sil.Int.isone i -> + a :: pi + | Sil.Aeq (Sil.Var name, e) when not (Ident.is_primed name) -> + (match e with + | Sil.Var _ + | Sil.Const _ -> a :: pi + | _ -> pi) + | _ -> pi) + [] pi_filtered in + list_rev new_pure in + + let new_pure = do_pure (Prop.get_pure p) in + let eprop' = Prop.replace_pi new_pure (Prop.replace_sub Sil.sub_empty p) in + let eprop'' = + if !Config.footprint && not from_abstract_footprint then + let new_pi_footprint = do_pure (Prop.get_pi_footprint p) in + Prop.replace_pi_footprint new_pi_footprint eprop' + else eprop' in + Prop.normalize eprop'' + +(* Collect symbolic garbage from pi and sigma *) +let abstract_gc p = + let pi = Prop.get_pi p in + let p_without_pi = Prop.normalize (Prop.replace_pi [] p) in + let fav_p_without_pi = Prop.prop_fav p_without_pi in + (* let weak_filter atom = + let fav_atom = atom_fav atom in + list_intersect compare fav_p_without_pi fav_atom in *) + let strong_filter = function + | Sil.Aeq(e1, e2) | Sil.Aneq(e1, e2) -> + let fav_e1 = Sil.exp_fav e1 in + let fav_e2 = Sil.exp_fav e2 in + let intersect_e1 _ = list_intersect Ident.compare (Sil.fav_to_list fav_e1) (Sil.fav_to_list fav_p_without_pi) in + let intersect_e2 _ = list_intersect Ident.compare (Sil.fav_to_list fav_e2) (Sil.fav_to_list fav_p_without_pi) in + let no_fav_e1 = Sil.fav_is_empty fav_e1 in + let no_fav_e2 = Sil.fav_is_empty fav_e2 in + (no_fav_e1 || intersect_e1 ()) && (no_fav_e2 || intersect_e2 ()) in + let new_pi = list_filter strong_filter pi in + let prop = Prop.normalize (Prop.replace_pi new_pi p) in + match Prop.prop_iter_create prop with + | None -> prop + | Some iter -> Prop.prop_iter_to_prop (Prop.prop_iter_gc_fields iter) + +module IdMap = Map.Make (Ident) (** maps from identifiers *) +module HpredSet = + Set.Make(struct + type t = Sil.hpred + let compare = Sil.hpred_compare + end) + +let hpred_entries hpred = match hpred with + | Sil.Hpointsto (e, _, _) -> [e] + | Sil.Hlseg (_, _, e, _, _) -> [e] + | Sil.Hdllseg (_, _, e1, _, _, e2, _) -> [e1; e2] + +(** find the id's in sigma reachable from the given roots *) +let sigma_reachable root_fav sigma = + let fav_to_set fav = Ident.idlist_to_idset (Sil.fav_to_list fav) in + let reach_set = ref (fav_to_set root_fav) in + let edges = ref [] in + let do_hpred hpred = + let hp_fav_set = fav_to_set (Sil.hpred_fav hpred) in + let add_entry e = edges := (e, hp_fav_set) :: !edges in + list_iter add_entry (hpred_entries hpred) in + list_iter do_hpred sigma; + let edge_fires (e, _) = match e with + | Sil.Var id -> + if (Ident.is_primed id || Ident.is_footprint id) then Ident.IdentSet.mem id !reach_set + else true + | _ -> true in + let rec apply_once edges_to_revisit edges_todo modified = match edges_todo with + | [] -> (edges_to_revisit, modified) + | edge:: edges_todo' -> + if edge_fires edge then + begin + reach_set := Ident.IdentSet.union (snd edge) !reach_set; + apply_once edges_to_revisit edges_todo' true + end + else apply_once (edge :: edges_to_revisit) edges_todo' modified in + let rec find_fixpoint edges_todo = + let edges_to_revisit, modified = apply_once [] edges_todo false in + if modified then find_fixpoint edges_to_revisit in + find_fixpoint !edges; + (* L.d_str "reachable: "; + Ident.IdentSet.iter (fun id -> Sil.d_exp (Sil.Var id); L.d_str " ") !reach_set; + L.d_ln (); *) + !reach_set + +let get_cycle root prop = + let sigma = Prop.get_sigma prop in + let get_points_to e = + match e with + | Sil.Eexp(e', _) -> + (try + Some(list_find (fun hpred -> match hpred with + | Sil.Hpointsto(e'', _, _) -> Sil.exp_equal e'' e' + | _ -> false) sigma) + with _ -> None) + | _ -> None in + let print_cycle cyc = + (L.d_str "Cycle= "; + list_iter (fun ((e, t), f, e') -> + match e, e' with + | Sil.Eexp (e, _), Sil.Eexp (e', _) -> + L.d_str ("("^(Sil.exp_to_string e)^": "^(Sil.typ_to_string t)^", "^(Ident.fieldname_to_string f)^", "^(Sil.exp_to_string e')^")") + | _ -> ()) cyc; + L.d_strln "") in + (* perform a dfs of a graph stopping when e_root is reached. *) + (* Returns a pair (path, bool) where path is a list of edges ((e1,type_e1),f,e2) *) + (* describing the path to e_root and bool is true if e_root is reached. *) + let rec dfs e_root et_src path el visited = + match el with + | [] -> path, false + | (f, e):: el' -> + if Sil.strexp_equal e e_root then + (et_src, f, e):: path, true + else if list_mem Sil.strexp_equal e visited then + path, false + else ( + let visited' = (fst et_src):: visited in + let res = (match get_points_to e with + | None -> path, false + | Some (Sil.Hpointsto(_, Sil.Estruct(fl, _), Sil.Sizeof(te, _))) -> + dfs e_root (e, te) ((et_src, f, e):: path) fl visited' + | _ -> path, false (* check for lists *)) in + if snd res then res + else dfs e_root et_src path el' visited') in + L.d_strln "Looking for cycle with root expression: "; Sil.d_hpred root; L.d_strln ""; + match root with + | Sil.Hpointsto(e_root, Sil.Estruct(fl, _), Sil.Sizeof(te, _)) -> + let se_root = Sil.Eexp(e_root, Sil.Inone) in + (* start dfs with empty path and expr pointing to root *) + let (pot_cycle, res) = dfs se_root (se_root, te) [] fl [] in + if res then ( + print_cycle pot_cycle; + pot_cycle + ) else ( + L.d_strln "NO cycle found from root"; + []) + | _ -> L.d_strln "Root exp is not an allocated object. No cycle found"; [] + +(** return a reachability function based on whether an id appears in several hpreds *) +let reachable_when_in_several_hpreds sigma : Ident.t -> bool = + let (id_hpred_map : HpredSet.t IdMap.t ref) = ref IdMap.empty (* map id to hpreds in which it occurs *) in + let add_id_hpred id hpred = + try + let hpred_set = IdMap.find id !id_hpred_map in + id_hpred_map := IdMap.add id (HpredSet.add hpred hpred_set) !id_hpred_map + with + | Not_found -> id_hpred_map := IdMap.add id (HpredSet.singleton hpred) !id_hpred_map in + let add_hpred hpred = + let fav = Sil.fav_new () in + Sil.hpred_fav_add fav hpred; + list_iter (fun id -> add_id_hpred id hpred) (Sil.fav_to_list fav) in + let id_in_several_hpreds id = + HpredSet.cardinal (IdMap.find id !id_hpred_map) > 1 in + list_iter add_hpred sigma; + id_in_several_hpreds + +let full_reachability_algorithm = true + +(* Check whether the hidden counter field of a struct representing an *) +(* objective-c object is positive, and whether the leak is part of the *) +(* specified buckets. In the positive case, it returns the bucket *) +let should_raise_objc_leak prop hpred = + match hpred with + | Sil.Hpointsto(e, Sil.Estruct((fn, Sil.Eexp( (Sil.Const (Sil.Cint i)), _)):: _, _), Sil.Sizeof (typ, _)) + when Ident.fieldname_is_hidden fn && Sil.Int.gt i Sil.Int.zero (* counter > 0 *) -> + Mleak_buckets.should_raise_leak typ + | _ -> None + +let print_retain_cycle _prop = + match _prop with + | None -> () + | Some (Some _prop) -> + let loc = State.get_loc () in + let source_file = DB.source_file_to_string loc.Sil.file in + let source_file'= Str.global_replace (Str.regexp_string "/") "_" source_file in + let dest_file_str = (DB.filename_to_string (DB.Results_dir.specs_dir ()))^"/"^source_file'^"_RETAIN_CYCLE_"^(Sil.loc_to_string loc)^".dot" in + L.d_strln ("Printing dotty proposition for retain cycle in :"^dest_file_str); + Prop.d_prop _prop; L.d_strln ""; + Dotty.dotty_prop_to_dotty_file dest_file_str _prop + | _ -> () + +let get_var_retain_cycle _prop = + let sigma = Prop.get_sigma _prop in + let is_pvar v h = + match h with + | Sil.Hpointsto (Sil.Lvar pv, v', _) when Sil.strexp_equal v v' -> true + | _ -> false in + let is_hpred_block v h = + match h, v with + | Sil.Hpointsto (e, _, Sil.Sizeof(typ, _)), Sil.Eexp (e', _) + when Sil.exp_equal e e' && Sil.is_block_type typ -> true + | _, _ -> false in + let find_pvar v = + try + let hp = list_find (is_pvar v) sigma in + Some (Sil.hpred_get_lhs hp) + with Not_found -> None in + let find_block v = + if (list_exists (is_hpred_block v) sigma) then + Some (Sil.Lvar Sil.block_pvar) + else None in + let sexp e = Sil.Eexp (e, Sil.Inone) in + let find_pvar_or_block ((e, t), f, e') = + match find_pvar e with + | Some pvar -> [((sexp pvar, t), f, e')] + | _ -> (match find_block e with + | Some blk -> [((sexp blk, t), f, e')] + | _ -> [((sexp (Sil.Sizeof(t, Sil.Subtype.exact)), t), f, e')]) in + (* returns the pvars of the first cycle we find in sigma. *) + (* This is an heuristic that works if there is one cycle. *) + (* In case there are more than one cycle we may return not necessarily*) + (* the one we are looking for. *) + let rec do_sigma sigma_todo = + match sigma_todo with + | [] -> [] + | hp:: sigma' -> + let cycle = get_cycle hp _prop in + L.d_strln "Filtering pvar in cycle "; + let cycle' = list_flatten (list_map find_pvar_or_block cycle) in + if cycle' = [] then do_sigma sigma' + else cycle' in + do_sigma sigma + +let remove_opt _prop = + match _prop with + | Some (Some p) -> p + | _ -> Prop.prop_emp + +(* Checks if cycle has fields with property attributes weak/unsafe_unretained *) +let cycle_has_weak_or_unretained_or_assign_field cycle = + (* returns items annotation for field fn in struct t *) + let get_item_annotation t fn = + match t with + | Sil.Tstruct(nsf, sf, _, _, _, _, _) -> + let ia = ref [] in + list_iter (fun (fn', t', ia') -> + if Ident.fieldname_equal fn fn' then ia := ia') (nsf@sf); + !ia + | _ -> [] in + let rec has_weak_or_unretained_or_assign params = + match params with + | [] -> false + | att::_ when Config.unsafe_unret = att || Config.weak = att || Config.assign = att -> true + | _:: params' -> has_weak_or_unretained_or_assign params' in + let do_annotation (a, _) = + (a.Sil.class_name = Config.property_attributes && has_weak_or_unretained_or_assign a.Sil.parameters) in + let rec do_cycle c = + match c with + | [] -> false + | ((e, t), fn, _):: c' -> + let ia = get_item_annotation t fn in + if (list_exists do_annotation ia) then true + else do_cycle c' in + do_cycle cycle + +let check_junk ?original_prop pname tenv prop = + let fav_sub_sigmafp = Sil.fav_new () in + Sil.sub_fav_add fav_sub_sigmafp (Prop.get_sub prop); + Prop.sigma_fav_add fav_sub_sigmafp (Prop.get_sigma_footprint prop); + let leaks_reported = ref [] in + + let remove_junk_once fp_part fav_root sigma = + let id_considered_reachable = (* reachability function *) + if full_reachability_algorithm then + let reach_set = sigma_reachable fav_root sigma in + fun id -> Ident.IdentSet.mem id reach_set + else + reachable_when_in_several_hpreds sigma in + let should_remove_hpred entries = + let predicate = function + | Sil.Var id -> + (Ident.is_primed id || Ident.is_footprint id) + && not (Sil.fav_mem fav_root id) && not (id_considered_reachable id) + | _ -> false in + list_for_all predicate entries in + let hpred_in_cycle hpred = (* check if the predicate belongs to a cycle in the heap *) + let id_in_cycle id = + let set1 = sigma_reachable (Sil.fav_from_list [id]) sigma in + let set2 = Ident.IdentSet.remove id set1 in + let fav2 = Sil.fav_from_list (Ident.IdentSet.elements set2) in + let set3 = sigma_reachable fav2 sigma in + Ident.IdentSet.mem id set3 in + let entries = hpred_entries hpred in + let predicate = function + | Sil.Var id -> id_in_cycle id + | _ -> false in + let hpred_is_loop = match hpred with (* true if hpred has a self loop, ie one field points to id *) + | Sil.Hpointsto (Sil.Var id, se, _) -> + let fav = Sil.fav_new () in + Sil.strexp_fav_add fav se; + Sil.fav_mem fav id + | _ -> false in + hpred_is_loop + || + list_exists predicate entries in + let rec remove_junk_recursive sigma_done sigma_todo = + match sigma_todo with + | [] -> list_rev sigma_done + | hpred :: sigma_todo' -> + let entries = hpred_entries hpred in + if should_remove_hpred entries + then begin + let part = if fp_part then "footprint" else "normal" in + L.d_strln (".... Prop with garbage in " ^ part ^ " part ...."); + L.d_increase_indent 1; + L.d_strln "PROP:"; + Prop.d_prop prop; L.d_ln (); + L.d_strln "PREDICATE:"; + Prop.d_sigma [hpred]; + L.d_ln (); + let alloc_attribute = (* find the alloc attribute of one of the roots of hpred, if it exists *) + let res = ref None in + let do_entry e = + match Prop.get_resource_undef_attribute prop e with + | Some (Sil.Aresource ({ Sil.ra_kind = Sil.Racquire }) as a) -> + L.d_str "ATTRIBUTE: "; Sil.d_exp (Sil.Const (Sil.Cattribute a)); L.d_ln (); + res := Some a + | Some (Sil.Aundef _ as a) -> + res := Some a + | _ -> () in + list_iter do_entry entries; + !res in + L.d_decrease_indent 1; + let is_undefined = match alloc_attribute with + | Some (Sil.Aundef _) -> true + | _ -> false in + let resource = match Errdesc.hpred_is_open_resource prop hpred with + | Some res -> res + | None -> Sil.Rmemory Sil.Mmalloc in + let objc_ml_bucket_opt = + match resource with + | Sil.Rmemory Sil.Mobjc -> should_raise_objc_leak prop hpred + | _ -> None in + let exn_retain_cycle cycle = + print_retain_cycle original_prop; + let desc = Errdesc.explain_retain_cycle (remove_opt original_prop) cycle (State.get_loc ()) in + Exceptions.Retain_cycle(remove_opt original_prop, hpred, desc, try assert false with Assert_failure x -> x) in + let exn_leak = + Exceptions.Leak (fp_part, prop, hpred, Errdesc.explain_leak tenv hpred prop alloc_attribute objc_ml_bucket_opt, !Absarray.array_abstraction_performed, resource, try assert false with Assert_failure x -> x) in + let ignore_resource, exn = + (match alloc_attribute, resource with + | Some _, Sil.Rmemory Sil.Mobjc when (hpred_in_cycle hpred) -> + (* When there is a cycle in objc we ignore it only if it has weak or unsafe_unretained fields *) + (* Otherwise we report a retain cycle*) + let cycle = get_var_retain_cycle (remove_opt original_prop) in + if cycle_has_weak_or_unretained_or_assign_field cycle then + true, exn_retain_cycle cycle + else false, exn_retain_cycle cycle + | Some _, Sil.Rmemory Sil.Mobjc -> + objc_ml_bucket_opt = None, exn_leak + | Some _, Sil.Rmemory _ -> !Sil.curr_language = Sil.Java, exn_leak + | Some _, Sil.Rignore -> true, exn_leak + | Some _, Sil.Rfile -> false, exn_leak + | Some _, Sil.Rlock -> false, exn_leak + | _ when hpred_in_cycle hpred && Sil.has_objc_ref_counter hpred -> + (* When its a cycle and the object has a ref counter then *) + (* we have a retain cycle. Objc object may not have the *) + (* Sil.Mobjc qualifier when added in footprint doing abduction *) + let cycle = get_var_retain_cycle (remove_opt original_prop) in + false, exn_retain_cycle cycle + | _ -> !Sil.curr_language = Sil.Java, exn_leak) in + let ignore_leak = !Config.allowleak || ignore_resource || is_undefined in + let report_and_continue = !Config.footprint in (* in footprint mode, report leak and continue *) + let already_reported () = + let attr_opt_equal ao1 ao2 = match ao1, ao2 with + | None, None -> true + | Some a1, Some a2 -> Sil.attribute_equal a1 a2 + | Some _, None + | None, Some _ -> false in + (alloc_attribute = None && !leaks_reported <> []) || (* None attribute only reported if it's the first one *) + list_mem attr_opt_equal alloc_attribute !leaks_reported in + let report_leak () = + if report_and_continue then + begin + if not (already_reported ()) (* report each leak only once *) + then begin + Reporting.log_error pname exn; + Exceptions.print_exception_html "Error: " exn; + end; + leaks_reported := alloc_attribute :: !leaks_reported; + remove_junk_recursive sigma_done sigma_todo' + end + else raise exn in + if ignore_leak then remove_junk_recursive sigma_done sigma_todo' + else report_leak () + end + else + remove_junk_recursive (hpred :: sigma_done) sigma_todo' in + remove_junk_recursive [] sigma in + let rec remove_junk fp_part fav_root sigma = (* call remove_junk_once until sigma stops shrinking *) + let sigma' = remove_junk_once fp_part fav_root sigma in + if list_length sigma' = list_length sigma then sigma' + else remove_junk fp_part fav_root sigma' in + let sigma_new = remove_junk false fav_sub_sigmafp (Prop.get_sigma prop) in + let sigma_fp_new = remove_junk true (Sil.fav_new ()) (Prop.get_sigma_footprint prop) in + if Prop.sigma_equal (Prop.get_sigma prop) sigma_new && Prop.sigma_equal (Prop.get_sigma_footprint prop) sigma_fp_new + then prop + else Prop.normalize (Prop.replace_sigma sigma_new (Prop.replace_sigma_footprint sigma_fp_new prop)) + +(** Check whether the prop contains junk. +If it does, and [Config.allowleak] is true, remove the junk, otherwise raise a Leak exception. *) +let abstract_junk ?original_prop pname tenv prop = + Absarray.array_abstraction_performed := false; + check_junk ~original_prop: original_prop pname tenv prop + +(** Remove redundant elements in an array, and check for junk afterwards *) +let remove_redundant_array_elements pname tenv prop = + Absarray.array_abstraction_performed := false; + let prop' = Absarray.remove_redundant_elements prop in + check_junk ~original_prop: (Some(prop)) pname tenv prop' + +let abstract_prop pname tenv ~(rename_primed: bool) ~(from_abstract_footprint: bool) p = + Absarray.array_abstraction_performed := false; + let pure_abs_p = abstract_pure_part ~from_abstract_footprint: true p in + let array_abs_p = + if from_abstract_footprint + then pure_abs_p + else abstract_pure_part ~from_abstract_footprint: from_abstract_footprint (Absarray.abstract_array_check pure_abs_p) in + let abs_p = abs_rules_apply tenv array_abs_p in + let abs_p = abstract_gc abs_p in (** abstraction might enable more gc *) + let abs_p = check_junk ~original_prop: (Some(p)) pname tenv abs_p in + let ren_abs_p = + if rename_primed + then Prop.prop_rename_primed_footprint_vars abs_p + else abs_p in + ren_abs_p + +let get_local_stack cur_sigma init_sigma = + let filter_stack = function + | Sil.Hpointsto (Sil.Lvar _, _, _) -> true + | Sil.Hpointsto _ | Sil.Hlseg _ | Sil.Hdllseg _ -> false in + let get_stack_var = function + | Sil.Hpointsto (Sil.Lvar pvar, _, _) -> pvar + | Sil.Hpointsto _ | Sil.Hlseg _ | Sil.Hdllseg _ -> assert false in + let filter_local_stack old_pvars = function + | Sil.Hpointsto (Sil.Lvar pvar, _, _) -> not (list_exists (Sil.pvar_equal pvar) old_pvars) + | Sil.Hpointsto _ | Sil.Hlseg _ | Sil.Hdllseg _ -> false in + let init_stack = list_filter filter_stack init_sigma in + let init_stack_pvars = list_map get_stack_var init_stack in + let cur_local_stack = list_filter (filter_local_stack init_stack_pvars) cur_sigma in + let cur_local_stack_pvars = list_map get_stack_var cur_local_stack in + (cur_local_stack, cur_local_stack_pvars) + +(** Extract the footprint, add a local stack and return it as a prop *) +let extract_footprint_for_abs (p : 'a Prop.t) : Prop.exposed Prop.t * Sil.pvar list = + let sigma = Prop.get_sigma p in + let foot_pi = Prop.get_pi_footprint p in + let foot_sigma = Prop.get_sigma_footprint p in + let (local_stack, local_stack_pvars) = get_local_stack sigma foot_sigma in + let p0 = Prop.from_sigma (local_stack @ foot_sigma) in + let p1 = Prop.replace_pi foot_pi p0 in + (p1, local_stack_pvars) + +let remove_local_stack sigma pvars = + let filter_non_stack = function + | Sil.Hpointsto (Sil.Lvar pvar, _, _) -> not (list_exists (Sil.pvar_equal pvar) pvars) + | Sil.Hpointsto _ | Sil.Hlseg _ | Sil.Hdllseg _ -> true in + list_filter filter_non_stack sigma + +(** [prop_set_fooprint p p_foot] removes a local stack from [p_foot], +and sets proposition [p_foot] as footprint of [p]. *) +let set_footprint_for_abs (p : 'a Prop.t) (p_foot : 'a Prop.t) local_stack_pvars : Prop.exposed Prop.t = + let p_foot_pure = Prop.get_pure p_foot in + let p_foot_sigma = Prop.get_sigma p_foot in + let pi = p_foot_pure in + let sigma = remove_local_stack p_foot_sigma local_stack_pvars in + Prop.replace_sigma_footprint sigma (Prop.replace_pi_footprint pi p) + +(** Abstract the footprint of prop *) +let abstract_footprint pname (tenv : Sil.tenv) (prop : Prop.normal Prop.t) : Prop.normal Prop.t = + let (p, added_local_vars) = extract_footprint_for_abs prop in + let p_abs = + abstract_prop + pname tenv ~rename_primed: false + ~from_abstract_footprint: true (Prop.normalize p) in + let prop' = set_footprint_for_abs prop p_abs added_local_vars in + Prop.normalize prop' + +let _abstract pname pay tenv p = + if pay then SymOp.pay(); (* pay one symop *) + let p' = if !Config.footprint then abstract_footprint pname tenv p else p in + abstract_prop pname tenv ~rename_primed: true ~from_abstract_footprint: false p' + +let abstract pname tenv p = + _abstract pname true tenv p + +let abstract_no_symop pname tenv p = + _abstract pname false tenv p + +let lifted_abstract pname tenv pset = + let f p = + if Prover.check_inconsistency p then None + else Some (abstract pname tenv p) in + let abstracted_pset = Propset.map_option f pset in + abstracted_pset + +(***************** End of Main Abstraction Functions *****************) diff --git a/infer/src/backend/abs.mli b/infer/src/backend/abs.mli new file mode 100644 index 000000000..65ca5c2c9 --- /dev/null +++ b/infer/src/backend/abs.mli @@ -0,0 +1,31 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Implementation of Abstraction Functions *) + +(** Create abstraction rules from the definition of a type *) +val create_absrules_from_tdecl : Sil.tenv -> Sil.typename -> unit + +(** Check whether the prop contains junk. +If it does, and [Config.allowleak] is true, remove the junk, otherwise raise a Leak exception. *) +val abstract_junk : ?original_prop:Prop.normal Prop.t -> Procname.t -> Sil.tenv -> Prop.normal Prop.t -> Prop.normal Prop.t + +(** Remove redundant elements in an array, and check for junk afterwards *) +val remove_redundant_array_elements : +Procname.t -> Sil.tenv -> Prop.normal Prop.t -> Prop.normal Prop.t + +(** Abstract a proposition. *) +val abstract : Procname.t -> Sil.tenv -> Prop.normal Prop.t -> Prop.normal Prop.t + +(** Abstract a proposition but don't pay a SymOp *) +val abstract_no_symop : Procname.t -> Sil.tenv -> Prop.normal Prop.t -> Prop.normal Prop.t + +(** Abstract each proposition in [propset] *) +val lifted_abstract : Procname.t -> Sil.tenv -> Propset.t -> Propset.t + +val abs_rules_reset : unit -> unit + +val get_cycle : Sil.hpred -> Prop.normal Prop.t -> ((Sil.strexp * Sil.typ) * Ident.fieldname * Sil.strexp) list diff --git a/infer/src/backend/absarray.ml b/infer/src/backend/absarray.ml new file mode 100644 index 000000000..dd8c4e272 --- /dev/null +++ b/infer/src/backend/absarray.ml @@ -0,0 +1,627 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Abstraction for Arrays *) + +module L = Logging +module F = Format +open Utils + +type sigma = Sil.hpred list + +(** Matcher for the sigma part specialized to strexps *) +module StrexpMatch : sig +(** path through a strexp *) + type path + + (** convert a path into a list of expressions *) + val path_to_exps : path -> Sil.exp list + + (** create a path from a root and a list of offsets *) + val path_from_exp_offsets : Sil.exp -> Sil.offset list -> path + + (** path to the root, size, elements and type of a new_array *) + type strexp_data = path * Sil.strexp * Sil.typ + + (** sigma with info about a current array *) + type t + + (** Find a strexp at the given path. Can raise [Not_found] *) + val find_path : sigma -> path -> t + + (** Find a strexp with the given property. *) + val find : sigma -> (sigma -> strexp_data -> bool) -> t list + + (** Get the array *) + val get_data : t -> strexp_data + + (** Get the partition of the sigma: the unmatched part of the sigma and the matched hpred *) + val get_sigma_partition : t -> sigma * Sil.hpred + + (** Replace the strexp at a given position by a new strexp *) + val replace_strexp : bool -> t -> Sil.strexp -> sigma + + (** Replace the strexp and the unmatched part of the sigma by the givn inputs *) + val replace_strexp_sigma : bool -> t -> Sil.strexp -> sigma -> sigma + + (** Replace the index in the array at a given position with the new index *) + val replace_index : bool -> t -> Sil.exp -> Sil.exp -> sigma + +end = struct + + (** syntactic offset *) + type syn_offset = Field of Ident.fieldname * Sil.typ | Index of Sil.exp + + (** path through an Estruct *) + type path = Sil.exp * (syn_offset list) + + (** Find a strexp and a type at the given syntactic offset list *) + let rec get_strexp_at_syn_offsets se t syn_offs = + match se, t, syn_offs with + | _, _, [] -> (se, t) + | Sil.Estruct (fsel, _), Sil.Tstruct (ftal, sftal, _, _, _, _, _), Field (fld, _) :: syn_offs' -> + let se' = snd (list_find (fun (f', se') -> Sil.fld_equal f' fld) fsel) in + let t' = (fun (x,y,z) -> y) (list_find (fun (f', t', a') -> Sil.fld_equal f' fld) ftal) in + get_strexp_at_syn_offsets se' t' syn_offs' + | Sil.Earray (size, esel, _), Sil.Tarray(t', _), Index ind :: syn_offs' -> + let se' = snd (list_find (fun (i', se') -> Sil.exp_equal i' ind) esel) in + get_strexp_at_syn_offsets se' t' syn_offs' + | _ -> + L.d_strln "Failure of get_strexp_at_syn_offsets"; + L.d_str "se: "; Sil.d_sexp se; L.d_ln (); + L.d_str "t: "; Sil.d_typ_full t; L.d_ln (); + assert false + + (** Replace a strexp at the given syntactic offset list *) + let rec replace_strexp_at_syn_offsets se t syn_offs update = + match se, t, syn_offs with + | _, _, [] -> + update se t + | Sil.Estruct (fsel, inst), Sil.Tstruct (ftal, sftal, _, _, _, _, _), Field (fld, _) :: syn_offs' -> + let se' = snd (list_find (fun (f', _) -> Sil.fld_equal f' fld) fsel) in + let t' = (fun (x,y,z) -> y) (list_find (fun (f', _, _) -> Sil.fld_equal f' fld) ftal) in + let se_mod = replace_strexp_at_syn_offsets se' t' syn_offs' update in + let fsel' = list_map (fun (f'', se'') -> if Sil.fld_equal f'' fld then (fld, se_mod) else (f'', se'')) fsel in + Sil.Estruct (fsel', inst) + | Sil.Earray (size, esel, inst), Sil.Tarray (t', _), Index idx :: syn_offs' -> + let se' = snd (list_find (fun (i', _) -> Sil.exp_equal i' idx) esel) in + let se_mod = replace_strexp_at_syn_offsets se' t' syn_offs' update in + let esel' = list_map (fun ese -> if Sil.exp_equal (fst ese) idx then (idx, se_mod) else ese) esel in + Sil.Earray (size, esel', inst) + | _ -> assert false + + (** convert a path into an expression *) + let path_to_exps (root, syn_offs_in) = + let rec convert acc = function + | [] -> acc + | Field (f, t) :: syn_offs' -> + let acc' = list_map (fun e -> Sil.Lfield (e, f, t)) acc in + convert acc' syn_offs' + | Index idx :: syn_offs' -> + let acc' = list_map (fun e -> Sil.Lindex (e, idx)) acc in + convert acc' syn_offs' in + begin + convert [root] syn_offs_in + end + + (** create a path from a root and a list of offsets *) + let path_from_exp_offsets root offs = + let offset_to_syn_offset = function + | Sil.Off_fld (fld, typ) -> Field (fld, typ) + | Sil.Off_index idx -> Index idx in + let syn_offs = list_map offset_to_syn_offset offs in + (root, syn_offs) + + (** path to the root, size, elements and type of a new_array *) + type strexp_data = path * Sil.strexp * Sil.typ + + (** Store hpred using physical equality, and offset list for an array *) + type t = sigma * Sil.hpred * (syn_offset list) + + (** Find an array at the given path. Can raise [Not_found] *) + let find_path sigma (root, syn_offs) : t = + let filter = function + | Sil.Hpointsto (e, _, _) -> Sil.exp_equal root e + | _ -> false in + let hpred = list_find filter sigma in + (sigma, hpred, syn_offs) + + (** Find a sub strexp with the given property. Can raise [Not_found] *) + let find (sigma : sigma) (pred : sigma -> strexp_data -> bool) : t list = + let found = ref [] in + let rec find_offset_sexp sigma_other hpred root offs se typ = + let offs' = list_rev offs in + let path = (root, offs') in + if pred sigma_other (path, se, typ) then found := (sigma, hpred, offs') :: !found + else begin + match se, typ with + | Sil.Estruct (fsel, _), Sil.Tstruct (ftal, sftal, _, _, _, _, _) -> + find_offset_fsel sigma_other hpred root offs fsel ftal typ + | Sil.Earray (size, esel, _), Sil.Tarray (t, _) -> + find_offset_esel sigma_other hpred root offs esel t + | _ -> () + end + and find_offset_fsel sigma_other hpred root offs fsel ftal typ = match fsel with + | [] -> () + | (f, se) :: fsel' -> + begin + try + let t = (fun (x,y,z) -> y) (list_find (fun (f', t, a) -> Sil.fld_equal f' f) ftal) in + find_offset_sexp sigma_other hpred root ((Field (f, typ)) :: offs) se t + with Not_found -> + L.d_strln ("Can't find field " ^ (Ident.fieldname_to_string f) ^ " in StrexpMatch.find") + end; + find_offset_fsel sigma_other hpred root offs fsel' ftal typ + and find_offset_esel sigma_other hpred root offs esel t = match esel with + | [] -> () + | (ind, se) :: esel' -> + begin + find_offset_sexp sigma_other hpred root ((Index ind):: offs) se t; + find_offset_esel sigma_other hpred root offs esel' t + end in + let rec iterate sigma_seen = function + | [] -> () + | hpred :: sigma_rest -> + begin + match hpred with + | Sil.Hpointsto (root, se, te) -> + let sigma_other = sigma_seen @ sigma_rest in + find_offset_sexp sigma_other hpred root [] se (Sil.texp_to_typ None te) + | _ -> () + end; + iterate (hpred:: sigma_seen) sigma_rest in + begin + iterate [] sigma; + !found + end + + (** Get the matched strexp *) + let get_data ((_ , hpred, syn_offs) : t) = match hpred with + | Sil.Hpointsto (root, se, te) -> + let t = Sil.texp_to_typ None te in + let se', t' = get_strexp_at_syn_offsets se t syn_offs in + let path' = (root, syn_offs) in + (path', se', t') + | _ -> assert false + + (** Get the partition of the sigma: the unmatched part of the sigma and the matched hpred *) + let get_sigma_partition (sigma, hpred, _) = + let sigma_unmatched = list_filter (fun hpred' -> not (hpred' == hpred)) sigma in + (sigma_unmatched, hpred) + + (** Replace the current hpred *) + let replace_hpred ((sigma, hpred, syn_offs) : t) hpred' = + list_map (fun hpred'' -> if hpred''== hpred then hpred' else hpred'') sigma + + (** Replace the strexp at the given offset in the given hpred *) + let hpred_replace_strexp footprint_part hpred syn_offs update = + let update se' t' = + let se_in = update se' t' in + match se', se_in with + | Sil.Earray (size, esel, inst1), Sil.Earray (_, esel_in, inst2) -> + let orig_indices = list_map fst esel in + let index_is_not_new idx = list_exists (Sil.exp_equal idx) orig_indices in + let process_index idx = + if index_is_not_new idx then idx else (Sil.array_clean_new_index footprint_part idx) in + let esel_in' = list_map (fun (idx, se) -> process_index idx, se) esel_in in + Sil.Earray (size, esel_in', inst2) + | _, _ -> se_in in + begin + match hpred with + | Sil.Hpointsto (root, se, te) -> + let t = Sil.texp_to_typ None te in + let se' = replace_strexp_at_syn_offsets se t syn_offs update in + Sil.Hpointsto (root, se', te) + | _ -> assert false + end + + (** Replace the strexp at a given position by a new strexp *) + let replace_strexp footprint_part ((sigma, hpred, syn_offs) : t) se_in = + let update se' t' = se_in in + let hpred' = hpred_replace_strexp footprint_part hpred syn_offs update in + replace_hpred (sigma, hpred, syn_offs) hpred' + + (** Replace the strexp and the unmatched part of the sigma by the given inputs *) + let replace_strexp_sigma footprint_part ((_, hpred, syn_offs) : t) se_in sigma_in = + let new_sigma = hpred :: sigma_in in + let sigma' = replace_strexp footprint_part (new_sigma, hpred, syn_offs) se_in in + list_sort Sil.hpred_compare sigma' + + (** Replace the index in the array at a given position with the new index *) + let replace_index footprint_part ((sigma, hpred, syn_offs) : t) (index: Sil.exp) (index': Sil.exp) = + let update se' t' = + match se' with + | Sil.Earray (size, esel, inst) -> + let esel' = list_map (fun (e', se') -> if Sil.exp_equal e' index then (index', se') else (e', se')) esel in + Sil.Earray (size, esel', inst) + | _ -> assert false in + let hpred' = hpred_replace_strexp footprint_part hpred syn_offs update in + replace_hpred (sigma, hpred, syn_offs) hpred' +end + +(** This function renames expressions in [p]. The renaming is, roughly +speaking, to replace [path.i] by [path.i'] for all (i, i') in [map]. *) +let prop_replace_path_index + (p: Prop.exposed Prop.t) + (path: StrexpMatch.path) + (map : (Sil.exp * Sil.exp) list) : Prop.exposed Prop.t += + let elist_path = StrexpMatch.path_to_exps path in + let expmap_list = + list_fold_left (fun acc_outer e_path -> + list_fold_left (fun acc_inner (old_index, new_index) -> + let old_e_path_index = Prop.exp_normalize_prop p (Sil.Lindex(e_path, old_index)) in + let new_e_path_index = Prop.exp_normalize_prop p (Sil.Lindex(e_path, new_index)) in + (old_e_path_index, new_e_path_index) :: acc_inner + ) acc_outer map + ) [] elist_path in + let expmap_fun e' = + try + let _, fresh_e = list_find (fun (e, _) -> Sil.exp_equal e e') expmap_list in + fresh_e + with Not_found -> e' in + Prop.prop_expmap expmap_fun p + +(** This function uses [update] and transforms the two sigma parts of [p], +the sigma of the current SH of [p] and that of the footprint of [p]. *) +let prop_update_sigma_and_fp_sigma + (p : Prop.normal Prop.t) + (update : bool -> sigma -> sigma * bool) : Prop.normal Prop.t * bool += + let sigma', changed = update false (Prop.get_sigma p) in + let ep1 = Prop.replace_sigma sigma' p in + let ep2, changed2 = + if !Config.footprint then + let sigma_fp', changed' = update true (Prop.get_sigma_footprint ep1) in + (Prop.replace_sigma_footprint sigma_fp' ep1, changed') + else (ep1, false) in + (Prop.normalize ep2, changed || changed2) + +(** This function uses [update] and transforms the sigma of the +current SH of [p] or that of the footprint of [p], depending on +[footprint_part]. *) +let prop_update_sigma_or_fp_sigma + (footprint_part : bool) + (p : Prop.normal Prop.t) + (update : bool -> sigma -> sigma * bool) : Prop.normal Prop.t * bool += + let ep1, changed1 = + if footprint_part then (Prop.expose p, false) + else + let sigma', changed = update false (Prop.get_sigma p) in + (Prop.replace_sigma sigma' p, changed) in + let ep2, changed2 = + if not footprint_part then (ep1, false) + else + begin + (if not !Config.footprint then assert false); (* always run in the footprint mode *) + let sigma_fp', changed = update true (Prop.get_sigma_footprint ep1) in + (Prop.replace_sigma_footprint sigma_fp' ep1, changed) + end in + (Prop.normalize ep2, changed1 || changed2) + +(** Remember whether array abstraction was performed (to be reset before calling Abs.abstract) *) +let array_abstraction_performed = ref false + +(** This function abstracts strexps. The parameter [can_abstract] spots strexps +where the abstraction might be applicable, and the parameter [do_abstract] does +the abstraction to those spotted strexps. *) +let generic_strexp_abstract + (abstraction_name : string) + (p_in : Prop.normal Prop.t) + (_can_abstract : sigma -> StrexpMatch.strexp_data -> bool) + (do_abstract : bool -> Prop.normal Prop.t -> StrexpMatch.strexp_data -> Prop.normal Prop.t * bool) +: Prop.normal Prop.t += + let can_abstract s data = + let r = _can_abstract s data in + if r then array_abstraction_performed := true; + r in + let find_strexp_to_abstract p0 = + let find sigma = StrexpMatch.find sigma can_abstract in + let matchings_cur = find (Prop.get_sigma p0) in + let matchings_fp = find (Prop.get_sigma_footprint p0) in + matchings_cur, matchings_fp in + let match_select_next (matchings_cur, matchings_fp) = + match matchings_cur, matchings_fp with + | [], [] -> raise Not_found + | matched :: cur', fp' -> matched, false, (cur', fp') + | [], matched :: fp' -> matched, true, ([], fp') in + let rec match_abstract p0 matchings_cur_fp = + try + let matched, footprint_part, matchings_cur_fp' = match_select_next matchings_cur_fp in + let n = list_length (snd matchings_cur_fp') + 1 in + if !Config.trace_absarray then (L.d_strln ("Num of fp candidates " ^ (string_of_int n))); + let strexp_data = StrexpMatch.get_data matched in + let p1, changed = do_abstract footprint_part p0 strexp_data in + if changed then (p1, true) + else match_abstract p0 matchings_cur_fp' + with + | Not_found -> (p0, false) in + let rec find_then_abstract bound p0 = + if bound = 0 then p0 + else begin + if !Config.trace_absarray then (L.d_strln ("Applying " ^ abstraction_name ^ " to"); Prop.d_prop p0; L.d_ln (); L.d_ln ()); + let matchings_cur_fp = find_strexp_to_abstract p0 in + let p1, changed = match_abstract p0 matchings_cur_fp in + if changed then find_then_abstract (bound - 1) p1 else p0 + end in + let matchings_cur, matchings_fp = find_strexp_to_abstract p_in in + let num_matches = (list_length matchings_cur) + (list_length matchings_fp) in + begin + find_then_abstract num_matches p_in + end + + +(** Return [true] if there's a pointer to the index *) +let index_is_pointed_to (p: Prop.normal Prop.t) (path: StrexpMatch.path) (index: Sil.exp) : bool = + let indices = + let index_plus_one = Sil.BinOp(Sil.PlusA, index, Sil.exp_one) in + [index; index_plus_one] in + let add_index_to_paths = + let elist_path = StrexpMatch.path_to_exps path in + let add_index i e = Prop.exp_normalize_prop p (Sil.Lindex(e, i)) in + fun i -> list_map (add_index i) elist_path in + let pointers = list_flatten (list_map add_index_to_paths indices) in + let filter = function + | Sil.Hpointsto (_, Sil.Eexp (e, inst), _) -> list_exists (Sil.exp_equal e) pointers + | _ -> false in + list_exists filter (Prop.get_sigma p) + + +(** Given [p] containing an array at [path], blur [index] in it *) +let blur_array_index + (footprint_part: bool) + (p: Prop.normal Prop.t) + (path: StrexpMatch.path) + (index: Sil.exp) : Prop.normal Prop.t += + try + let fresh_index = Sil.Var (Ident.create_fresh (if !Config.footprint then Ident.kfootprint else Ident.kprimed)) in + let p2 = + try + if !Config.footprint then + begin + let sigma_fp = Prop.get_sigma_footprint p in + let matched_fp = StrexpMatch.find_path sigma_fp path in + let sigma_fp' = StrexpMatch.replace_index true matched_fp index fresh_index in + Prop.replace_sigma_footprint sigma_fp' p + end + else Prop.expose p + with Not_found -> Prop.expose p in + let p3 = + let matched = StrexpMatch.find_path (Prop.get_sigma p) path in + let sigma' = StrexpMatch.replace_index false matched index fresh_index in + Prop.replace_sigma sigma' p2 in + let p4 = + let index_next = Sil.BinOp(Sil.PlusA, index, Sil.exp_one) in + let fresh_index_next = Sil.BinOp (Sil.PlusA, fresh_index, Sil.exp_one) in + let map = [(index, fresh_index); (index_next, fresh_index_next)] in + prop_replace_path_index p3 path map in + Prop.normalize p4 + with Not_found -> p + + +(** Given [p] containing an array at [root], blur [indices] in it *) +let blur_array_indices + (footprint_part : bool) + (p: Prop.normal Prop.t) + (root: StrexpMatch.path) + (indices: Sil.exp list) : Prop.normal Prop.t * bool += + let f prop index = blur_array_index footprint_part prop root index in + (list_fold_left f p indices, list_length indices > 0) + + +(** Given [p] containing an array at [root], only keep [indices] in it *) +let keep_only_indices + (footprint_part : bool) + (p: Prop.normal Prop.t) + (path: StrexpMatch.path) + (indices: Sil.exp list) : Prop.normal Prop.t * bool += + let prune_sigma footprint_part sigma = + try + let matched = StrexpMatch.find_path sigma path in + let (_, se, _) = StrexpMatch.get_data matched in + match se with + | Sil.Earray (size, esel, inst) -> + let esel', esel_leftover' = list_partition (fun (e, _) -> list_exists (Sil.exp_equal e) indices) esel in + if esel_leftover' == [] then (sigma, false) + else begin + let se' = Sil.Earray (size, esel', inst) in + let sigma' = StrexpMatch.replace_strexp footprint_part matched se' in + (sigma', true) + end + | _ -> (sigma, false) + with Not_found -> (sigma, false) in + prop_update_sigma_and_fp_sigma p prune_sigma + + +(** If the type is array, check whether we should do abstraction *) +let array_typ_can_abstract = function + | Sil.Tarray (Sil.Tptr (Sil.Tfun _, _), _) -> false (* don't abstract arrays of pointers *) + | _ -> true + +(** This function checks whether we can apply an abstraction to a strexp *) +let strexp_can_abstract sigma_rest ((_, se, typ) : StrexpMatch.strexp_data) : bool = + let can_abstract_se = match se with + | Sil.Earray (size, esel, _) -> + let len = list_length esel in + len > 1 + | _ -> false in + can_abstract_se && array_typ_can_abstract typ + + +(** This function abstracts a strexp *) +let strexp_do_abstract footprint_part p ((path, se_in, typ_in) : StrexpMatch.strexp_data) : Prop.normal Prop.t * bool = + if !Config.trace_absarray && footprint_part then (L.d_str "strexp_do_abstract (footprint)"; L.d_ln ()); + if !Config.trace_absarray && not footprint_part then (L.d_str "strexp_do_abstract (nonfootprint)"; L.d_ln ()); + let prune_and_blur d_keys keep blur path keep_keys blur_keys = + let p2, changed2 = + if !Config.trace_absarray then (L.d_str "keep "; d_keys keep_keys; L.d_ln ()); + keep p path keep_keys in + let p3, changed3 = + if blur_keys == [] then (p2, false) + else begin + if !Config.trace_absarray then (L.d_str "blur "; d_keys blur_keys; L.d_ln ()); + blur p2 path blur_keys + end in + if !Config.trace_absarray then (L.d_strln "Returns"; Prop.d_prop p3; L.d_ln (); L.d_ln ()); + (p3, changed2 || changed3) in + let prune_and_blur_indices = + prune_and_blur Sil.d_exp_list + (keep_only_indices footprint_part) + (blur_array_indices footprint_part) in + + let partition_abstract should_keep abstract ksel default_keys = + let keep_ksel, remove_ksel = list_partition should_keep ksel in + let keep_keys, remove_keys, keys = + list_map fst keep_ksel, list_map fst remove_ksel, list_map fst ksel in + let keep_keys' = if keep_keys == [] then default_keys else keep_keys in + abstract keep_keys' keep_keys' in + let do_array_footprint esel = + (* array case footprint: keep only the last index, and blur it *) + let should_keep (i0, _) = index_is_pointed_to p path i0 in + let abstract = prune_and_blur_indices path in + let default_indices = + match list_map fst esel with + | [] -> [] + | indices -> [list_hd (list_rev indices)] (* keep last key at least *) in + partition_abstract should_keep abstract esel default_indices in + let do_footprint () = + match se_in with + | Sil.Earray (_, esel, _) -> do_array_footprint esel + | _ -> assert false in + + let filter_abstract d_keys should_keep abstract ksel default_keys = + let keep_ksel = list_filter should_keep ksel in + let keep_keys = list_map fst keep_ksel in + let keep_keys' = if keep_keys == [] then default_keys else keep_keys in + if !Config.trace_absarray then (L.d_str "keep "; d_keys keep_keys'; L.d_ln ()); + abstract keep_keys' [] in + let do_array_reexecution esel = + (* array case re-execution: remove and blur constant and primed indices *) + let is_pointed index = index_is_pointed_to p path index in + let should_keep (index, _) = match index with + | Sil.Const _ -> is_pointed index + | Sil.Var id -> Ident.is_normal id || is_pointed index + | _ -> false in + let abstract = prune_and_blur_indices path in + filter_abstract Sil.d_exp_list should_keep abstract esel [] in + let do_reexecution () = + match se_in with + | Sil.Earray (_, esel, _) -> do_array_reexecution esel + | _ -> assert false in + + if !Config.footprint then do_footprint () + else do_reexecution () + +let strexp_abstract (p : Prop.normal Prop.t) : Prop.normal Prop.t = + generic_strexp_abstract "strexp_abstract" p strexp_can_abstract strexp_do_abstract + +let report_error prop = + L.d_strln "Check after array abstraction: FAIL"; + Prop.d_prop prop; L.d_ln (); + assert false + +let check_footprint_pure prop = + let fav_pure = Sil.fav_new () in + Prop.pi_fav_add fav_pure (Prop.get_pure prop @ Prop.get_pi_footprint prop); + let fav_sigma = Sil.fav_new () in + Prop.sigma_fav_add fav_sigma (Prop.get_sigma prop @ Prop.get_sigma_footprint prop); + Sil.fav_filter_ident fav_pure Ident.is_footprint; + Sil.fav_filter_ident fav_sigma Ident.is_footprint; + if not (Sil.fav_subset_ident fav_pure fav_sigma) + then (L.d_strln "footprint vars in pure and not in sigma"; report_error prop) + +(** Check performed after the array abstraction to see whether it was successful. Raise assert false in case of failure *) +let check_after_array_abstraction prop = + let check_index root offs (ind, _) = + if !Config.footprint then + let path = StrexpMatch.path_from_exp_offsets root offs in + index_is_pointed_to prop path ind + else not (Sil.fav_exists (Sil.exp_fav ind) Ident.is_primed) in + let rec check_se root offs typ = function + | Sil.Eexp _ -> () + | Sil.Earray (_, esel, _) -> (* check that no more than 2 elements are in the array *) + let typ_elem = Sil.array_typ_elem (Some Sil.Tvoid) typ in + if list_length esel > 2 && array_typ_can_abstract typ then + if list_for_all (check_index root offs) esel then () + else report_error prop + else list_iter (fun (ind, se) -> check_se root (offs @ [Sil.Off_index ind]) typ_elem se) esel + | Sil.Estruct (fsel, _) -> + list_iter (fun (f, se) -> + let typ_f = Sil.struct_typ_fld (Some Sil.Tvoid) f typ in + check_se root (offs @ [Sil.Off_fld (f, typ)]) typ_f se) fsel in + let check_hpred = function + | Sil.Hpointsto (root, se, texp) -> + let typ = Sil.texp_to_typ (Some Sil.Tvoid) texp in + check_se root [] typ se + | Sil.Hlseg _ | Sil.Hdllseg _ -> () in + let check_sigma sigma = list_iter check_hpred sigma in + (* check_footprint_pure prop; *) + check_sigma (Prop.get_sigma prop); + check_sigma (Prop.get_sigma_footprint prop) + +(** Apply array abstraction and check the result *) +let abstract_array_check p = + let p_res = strexp_abstract p in + check_after_array_abstraction p_res; + p_res + +(** remove redundant elements in an array *) +let remove_redundant_elements prop = + Prop.d_prop prop; L.d_ln (); + let occurs_at_most_once : Ident.t -> bool = (* the variable occurs at most once in the footprint or current part *) + let fav_curr = Sil.fav_new () in + let fav_foot = Sil.fav_new () in + Sil.fav_duplicates := true; + Sil.sub_fav_add fav_curr (Prop.get_sub prop); + Prop.pi_fav_add fav_curr (Prop.get_pi prop); + Prop.sigma_fav_add fav_curr (Prop.get_sigma prop); + Prop.pi_fav_add fav_foot (Prop.get_pi_footprint prop); + Prop.sigma_fav_add fav_foot (Prop.get_sigma_footprint prop); + let favl_curr = Sil.fav_to_list fav_curr in + let favl_foot = Sil.fav_to_list fav_foot in + Sil.fav_duplicates := false; + (* L.d_str "favl_curr "; list_iter (fun id -> Sil.d_exp (Sil.Var id)) favl_curr; L.d_ln(); + L.d_str "favl_foot "; list_iter (fun id -> Sil.d_exp (Sil.Var id)) favl_foot; L.d_ln(); *) + let num_occur l id = list_length (list_filter (fun id' -> Ident.equal id id') l) in + let at_most_once v = + num_occur favl_curr v <= 1 && num_occur favl_foot v <= 1 in + at_most_once in + let modified = ref false in + let filter_redundant_e_se fp_part (e, se) = + let remove () = + L.d_strln "kill_redundant: removing "; Sil.d_exp e; L.d_str " "; Sil.d_sexp se; L.d_ln(); + array_abstraction_performed := true; + modified := true; + false in + match e, se with + | Sil.Const (Sil.Cint i), Sil.Eexp (Sil.Var id, _) when (not fp_part || Sil.Int.iszero i) && Ident.is_normal id = false && occurs_at_most_once id -> + remove () (* unknown value can be removed in re-execution mode or if the index is zero *) + | Sil.Var id, Sil.Eexp _ when Ident.is_normal id = false && occurs_at_most_once id -> + remove () (* index unknown can be removed *) + | _ -> true in + let remove_redundant_se fp_part = function + | Sil.Earray (size, esel, inst) -> + let esel' = list_filter (filter_redundant_e_se fp_part) esel in + Sil.Earray (size, esel', inst) + | se -> se in + let remove_redundant_hpred fp_part = function + | Sil.Hpointsto (e, se, te) -> + let se' = remove_redundant_se fp_part se in + Sil.Hpointsto (e, se', te) + | hpred -> hpred in + let remove_redundant_sigma fp_part sigma = list_map (remove_redundant_hpred fp_part) sigma in + let sigma' = remove_redundant_sigma false (Prop.get_sigma prop) in + let foot_sigma' = remove_redundant_sigma true (Prop.get_sigma_footprint prop) in + if !modified then + let prop' = Prop.replace_sigma sigma' (Prop.replace_sigma_footprint foot_sigma' prop) in + Prop.normalize prop' + else prop + diff --git a/infer/src/backend/absarray.mli b/infer/src/backend/absarray.mli new file mode 100644 index 000000000..27a27664e --- /dev/null +++ b/infer/src/backend/absarray.mli @@ -0,0 +1,16 @@ +(* +* Copyright (c) 2009-2013 Monoidics ltd. +* Copyright (c) 2013- Facebook. +* All rights reserved. +*) + +(** Abstraction for Arrays *) + +(** Apply array abstraction and check the result *) +val abstract_array_check : Prop.normal Prop.t -> Prop.normal Prop.t + +(** Remember whether array abstraction was performed (to be reset before calling Abs.abstract) *) +val array_abstraction_performed : bool ref + +(** remove redundant elements in an array *) +val remove_redundant_elements : Prop.normal Prop.t -> Prop.normal Prop.t \ No newline at end of file diff --git a/infer/src/backend/autounit.ml b/infer/src/backend/autounit.ml new file mode 100644 index 000000000..5f6fa3e28 --- /dev/null +++ b/infer/src/backend/autounit.ml @@ -0,0 +1,641 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Generate unit tests automatically from specs *) + +open Utils +module L = Logging +module F = Format + +let (++) = Sil.Int.add +let (--) = Sil.Int.sub + +module IdMap = Map.Make (Ident) (** maps from identifiers *) + +(** Constraint solving module *) +module Constraint : sig +(** Collect constraints on [vars] from [pi], and return a satisfying instantiation *) + val solve_from_pure : Sil.atom list -> Ident.t list -> Sil.Int.t IdMap.t +end = struct + (** flag for debug mode of the module *) + let debug = false + + (** denote a range of values [bottom] <= val <= [top], except [excluded] *) + type range = + { mutable bottom: Sil.Int.t option; (** lower bound *) + mutable excluded: Sil.Int.t list; (** individual values not in range *) + mutable top: Sil.Int.t option; (** upper bound *) } + + type eval = range IdMap.t (** evaluation for variables *) + + (** create a new range *) + let new_range () = + { bottom = None; excluded = []; top = None } + + (** pretty print a range *) + let pp_range id fmt rng = + let pp_opt fmt = function + | None -> F.fprintf fmt "_" + | Some n -> Sil.Int.pp fmt n in + F.fprintf fmt "%a <= %a <= %a [%a]" pp_opt rng.bottom (Ident.pp pe_text) id pp_opt rng.top (pp_comma_seq Sil.Int.pp) rng.excluded + + (** pretty print an evaluation *) + let pp_eval fmt ev = + let do_id id rng = + F.fprintf fmt "%a@." (pp_range id) rng in + IdMap.iter do_id ev + + (** create a new evaluation *) + let new_eval vars = + let ev = ref IdMap.empty in + let add_var id = + ev := IdMap.add id (new_range ()) !ev in + list_iter add_var vars; + !ev + + let gt_bottom i r = + match r.bottom with + | None -> true + | Some j -> Sil.Int.gt i j + + let geq_bottom i r = + match r.bottom with + | None -> true + | Some j -> Sil.Int.geq i j + + let lt_top i r = + match r.top with + | None -> true + | Some j -> Sil.Int.lt i j + + let leq_top i r = + match r.top with + | None -> true + | Some j -> Sil.Int.leq i j + + (** normalize [r]: the excluded elements must be strictly between bottom and top *) + let normalize r = + r.excluded <- list_filter (fun i -> geq_bottom i r && leq_top i r) r.excluded; + let rec normalize_bottom () = match r.bottom with + | None -> () + | Some i -> + if list_mem Sil.Int.eq i r.excluded then begin + r.excluded <- list_filter (Sil.Int.neq i) r.excluded; + r.bottom <- Some (i ++ Sil.Int.one); + normalize_bottom () + end in + let rec normalize_top () = match r.top with + | None -> () + | Some i -> + if list_mem Sil.Int.eq i r.excluded then begin + r.excluded <- list_filter (Sil.Int.neq i) r.excluded; + r.top <- Some (i -- Sil.Int.one); + normalize_top () + end in + normalize_bottom (); + normalize_top () + + (** global variable set to true every time a range is changed *) + let changed = ref false + + let mark_changed id r = + changed := true; + if debug then F.fprintf F.std_formatter "changed: %a@." (pp_range id) r + + (** exclude one element from the range *) + let add_excluded r id i = + if geq_bottom i r && leq_top i r && not (list_mem Sil.Int.eq i r.excluded) + then begin + r.excluded <- i :: r.excluded; + normalize r; + mark_changed id r; + end + + (** make the bottom of the range >= [i] *) + let add_bottom r id i = + if gt_bottom i r then + begin + r.bottom <- Some i; + normalize r; + mark_changed id r + end + + (** make the top of the range <= [i] *) + let add_top r id i = + if lt_top i r then + begin + r.top <- Some i; + normalize r; + mark_changed id r + end + + (** choose an element in the range *) + let choose id rng = + if debug then F.fprintf F.std_formatter "choosing %a@." (pp_range id) rng; + let found = ref None in + let num_iter = list_length rng.excluded in + let try_candidate candidate = + if geq_bottom candidate rng && leq_top candidate rng && not (list_mem Sil.Int.eq candidate rng.excluded) + then (found := Some candidate; rng.bottom <- Some candidate; rng.top <- Some candidate; rng.excluded <- []) in + let search_up () = + let base = match rng.bottom with None -> Sil.Int.zero | Some n -> n in + for i = 0 to num_iter do + if !found = None then + let candidate = Sil.Int.add base (Sil.Int.of_int i) in + try_candidate candidate + done in + let search_down () = + let base = match rng.top with None -> Sil.Int.zero | Some n -> n in + for i = 0 to num_iter do + if !found = None then + let candidate = Sil.Int.sub base (Sil.Int.of_int i) in + try_candidate candidate + done in + search_up (); + if !found = None then search_down (); + if !found = None then + (L.err "Constraint Error: empty range %a@." (pp_range id) rng; + rng.top <- Some Sil.Int.zero; + rng.bottom <- Some Sil.Int.zero; + rng.excluded <- []) + + (** return the solution if the id is solved (has unique solution) *) + let solved ev id = + let rng = IdMap.find id ev in + match rng.bottom, rng.top with + | Some n1, Some n2 when Sil.Int.eq n1 n2 -> Some n1 + | _ -> None + + let rec pi_iter do_le do_lt do_neq pi = + let do_atom a = match a with + | Sil.Aeq (Sil.BinOp (Sil.Le, e1, e2), Sil.Const (Sil.Cint i)) when Sil.Int.isone i -> + do_le e1 e2 + | Sil.Aeq (Sil.BinOp (Sil.Lt, e1, e2), Sil.Const (Sil.Cint i)) when Sil.Int.isone i -> + do_lt e1 e2 + | Sil.Aeq _ -> () + | Sil.Aneq (e1, e2) -> + do_neq e1 e2 in + changed := false; + list_iter do_atom pi; + if !changed then pi_iter do_le do_lt do_neq pi + + (** Collect constraints on [vars] from [pi], and return a satisfying instantiation *) + let solve_from_pure pi vars = + if debug then F.fprintf F.std_formatter "solve_from_pure pi: %a@." (Prop.pp_pi pe_text) pi; + let vars_fav = Sil.fav_from_list vars in + let atom_is_relevant a = + let fav = Sil.atom_fav a in + Sil.fav_for_all fav (fun id -> Sil.fav_mem vars_fav id) in + let pi_relevant = list_filter atom_is_relevant pi in + let ev = new_eval vars in + let update_top rng id n_op = match rng.top, n_op with + | Some _, Some n -> add_top rng id n + | _ -> () in + let update_bottom rng id n_op = match rng.bottom, n_op with + | Some _, Some n -> add_bottom rng id n + | _ -> () in + let (+++) n1_op n2_op = match n1_op, n2_op with + | Some n1, Some n2 -> Some (Sil.Int.add n1 n2) + | _ -> None in + let (---) n1_op n2_op = match n1_op, n2_op with + | Some n1, Some n2 -> Some (Sil.Int.sub n1 n2) + | _ -> None in + let rec do_le e1 e2 = match e1, e2 with + | Sil.Var id, Sil.Const (Sil.Cint n) -> + let rng = IdMap.find id ev in + add_top rng id n + | Sil.BinOp (Sil.MinusA, Sil.Var id1, Sil.Var id2), Sil.Const (Sil.Cint n) -> + let rng1 = IdMap.find id1 ev in + let rng2 = IdMap.find id2 ev in + update_top rng1 id1 (rng2.top +++ (Some n)); + update_bottom rng2 id2 (rng1.bottom --- (Some n)) + | Sil.BinOp (Sil.PlusA, Sil.Var id1, Sil.Var id2), Sil.Const (Sil.Cint n) -> + let rng1 = IdMap.find id1 ev in + let rng2 = IdMap.find id2 ev in + update_top rng1 id1 (Some n --- rng2.bottom); + update_top rng2 id2 (Some n --- rng1.bottom) + | _ -> if debug then assert false in + let rec do_lt e1 e2 = match e1, e2 with + | Sil.Const (Sil.Cint n), Sil.Var id -> + let rng = IdMap.find id ev in + add_bottom rng id (n ++ Sil.Int.one) + | Sil.Const (Sil.Cint n), Sil.BinOp (Sil.PlusA, Sil.Var id1, Sil.Var id2) -> + let rng1 = IdMap.find id1 ev in + let rng2 = IdMap.find id2 ev in + update_bottom rng1 id1 (Some (n ++ Sil.Int.one) --- rng2.top); + update_bottom rng2 id2 (Some (n ++ Sil.Int.one) --- rng1.top) + | _ -> if debug then assert false in + let rec do_neq e1 e2 = match e1, e2 with + | Sil.Var id, Sil.Const (Sil.Cint n) + | Sil.Const(Sil.Cint n), Sil.Var id -> + let rng = IdMap.find id ev in + add_excluded rng id n + | Sil.Var id1, Sil.Var id2 -> + (match solved ev id1, solved ev id2 with + | None, None -> () + | Some _, Some _ -> () + | Some n1, None -> + do_neq (Sil.exp_int n1) e2 + | None, Some n2 -> + do_neq e1 (Sil.exp_int n2)) + | Sil.Var id1, Sil.BinOp(Sil.PlusA, Sil.Var id2, Sil.Const (Sil.Cint n)) -> + (match solved ev id1, solved ev id2 with + | None, None -> () + | Some _, Some _ -> () + | Some n1, None -> + do_neq (Sil.exp_int (n1 -- n)) (Sil.Var id2) + | None, Some n2 -> + do_neq (Sil.Var id1) (Sil.exp_int (n2 ++ n))) + | _ -> if debug then assert false in + let do_ident id = + if debug then F.fprintf F.std_formatter "constraints before doing %a:@.%a@." (Ident.pp pe_text) id pp_eval ev; + let rng = IdMap.find id ev in + pi_iter do_le do_lt do_neq pi_relevant; + choose id rng in + list_iter do_ident vars; + if debug then F.fprintf F.std_formatter "solution to pure constraints:@.%a@." pp_eval ev; + let solution = IdMap.map (function { bottom = Some n } -> n | _ -> assert false) ev in + solution +end + +type varinfo = + { typ: Sil.typ; (* type of the variable *) + alloc: bool (* whether the variable needs allocation (on lhs of |->, lists) *) + } + +type idmap = varinfo IdMap.t (* map from identifier to varinfo *) + +let extend_idmap id vinfo idmap = + let vinfo' = + try + let old_vinfo = IdMap.find id !idmap in + { old_vinfo with alloc = old_vinfo.alloc || vinfo.alloc } + with Not_found -> vinfo in + idmap := IdMap.add id vinfo' !idmap + +let pe = pe_text + +(** Given a sigma, create an idmap *) +let create_idmap sigma : idmap = + let idmap = ref IdMap.empty in + let rec do_exp e typ = match e, typ with + | Sil.Const _, _ -> () + | Sil.Var id, _ -> + extend_idmap id { typ = typ; alloc = false } idmap + | Sil.BinOp (Sil.PlusA, e1, e2), _ -> + do_exp e1 typ; + do_exp e2 typ + | Sil.BinOp (Sil.PlusPI, e1, e2), _ -> + do_exp e1 typ; + do_exp e2 (Sil.Tint Sil.IULong) + | Sil.Lfield (e1, f, t), _ -> + do_exp e1 typ + | Sil.Sizeof _, _ -> () + | _ -> + L.err "Unmatched exp: %a : %a@." (Sil.pp_exp pe) e (Sil.pp_typ_full pe) typ; + assert false in + let rec do_se se typ = match se, typ with + | Sil.Eexp (e, inst), _ -> + do_exp e typ + | Sil.Estruct (fsel, _), Sil.Tstruct (ftal, sftal, _, _, _, _, _) -> + do_struct fsel ftal + | Sil.Earray (size, esel, _), Sil.Tarray (typ, size') -> + do_se (Sil.Eexp (size, Sil.inst_none)) (Sil.Tint Sil.IULong); + do_array esel typ + | _ -> + L.err "Unmatched sexp: %a : %a@." (Sil.pp_sexp pe) se (Sil.pp_typ_full pe) typ; + assert false + and do_struct fsel ftal = match fsel, ftal with + | [], _ -> () + | (f1, se) :: fsel', (f2, typ, a2) :: ftl' when Ident.fieldname_equal f1 f2 -> + do_se se typ; + do_struct fsel' ftl' + | (f1, se) :: fsel', (f2, typ, a2) :: ftal' -> + do_struct fsel ftal' + | _:: _, [] -> assert false + and do_array esel typ = match esel with + | (e, se):: esel' -> + do_se (Sil.Eexp (e, Sil.inst_none)) (Sil.Tint Sil.IULong); + do_se se typ; + do_array esel' typ + | [] -> () in + let do_lhs_e e t = match e with + | Sil.Var id -> + extend_idmap id { typ = t; alloc = true } idmap + | _ -> () in + let do_hpred = function + | Sil.Hpointsto (e, se, Sil.Sizeof (typ, _)) -> + do_lhs_e e (Sil.Tptr (typ, Sil.Pk_pointer)); + do_se se typ + | Sil.Hlseg (k, hpar, e, f, el) -> + do_lhs_e e (Sil.Tptr (Sil.Tvoid, Sil.Pk_pointer)); + do_se (Sil.Eexp (f, Sil.inst_none)) (Sil.Tptr (Sil.Tvoid, Sil.Pk_pointer)); + list_iter (fun e -> do_se (Sil.Eexp (e, Sil.inst_none)) Sil.Tvoid) el + | hpred -> + L.err "do_hpred not implemented %a@." (Sil.pp_hpred pe) hpred in + list_iter do_hpred sigma; + !idmap + +module Code : sig + type t + val add_from_pp : t -> (Format.formatter -> unit -> unit) -> unit + val add_line : t -> string -> unit + val append : t -> t -> unit + val empty : unit -> t + val pp : F.formatter -> t -> unit + val set_indent : string -> unit + val to_list : t -> string list +end = struct + type t = string list ref + let indent = ref "" + let to_list code = + list_rev !code + let pp fmt code = + let doit line = F.fprintf fmt "%s@\n" line in + list_iter doit (to_list code); + F.fprintf fmt "@." + let empty () = ref [] + let add_line code l = + code := (!indent ^ l) :: !code + let add_from_pp code pp = + add_line code (pp_to_string pp ()) + let append code1 code2 = + code1 := !code2 @ !code1 + let set_indent s = + indent := s +end + +type code = Code.t + +(** pretty print generated code *) +let pp_code = Code.pp + +(** pretty print an ident in C *) +let pp_id_c pe fmt id = + let name = Ident.get_name id in + let stamp = Ident.get_stamp id in + let varname = Ident.name_to_string name in + F.fprintf fmt "%s%d" varname stamp + +(** pretty print an expression in C *) +let rec pp_exp_c pe fmt = function + | Sil.Lfield (e, f, t) -> + F.fprintf fmt "&(%a->%a)" (pp_exp_c pe) e Ident.pp_fieldname f + | Sil.Var id -> + pp_id_c pe fmt id + | e -> + Sil.pp_exp pe fmt e + +(** pretty print a type in C *) +let pp_typ_c pe typ = + let pp_nil fmt () = () in + Sil.pp_type_decl pe pp_nil pp_exp_c typ + +(** Convert a pvar to a string by just extracting the name *) +let pvar_to_string pvar = + Mangled.to_string (Sil.pvar_get_name pvar) + +(** pretty print an expression list in C *) +let pp_exp_list_c pe f expl = + (pp_seq (pp_exp_c pe)) f expl + +(** Name of the recusive function for a specific list para *) +let mk_lseg_name id proc_name spec_num = + "mk_lseg_" ^ string_of_int id ^ Procname.to_string proc_name ^ "_spec" ^ string_of_int spec_num + +let mk_size_name id = + "_size_" ^ string_of_int id + +let pp_texp_for_malloc fmt = + let rec handle_arr_size typ = match typ with + | Sil.Tvar _ | Sil.Tint _ | Sil.Tfloat _ | Sil.Tvoid | Sil.Tfun _ | Sil.Tenum _ -> + typ + | Sil.Tptr (t, pk) -> + Sil.Tptr (handle_arr_size t, pk) + | Sil.Tstruct (ftal, sftal, csu, nameo, supers, def_mthds, iann) -> + Sil.Tstruct (list_map (fun (f, t, a) -> (f, handle_arr_size t, a)) ftal, sftal, csu, nameo, supers, def_mthds, iann) + | Sil.Tarray (t, e) -> + Sil.Tarray (handle_arr_size t, e) in + function + | Sil.Sizeof (typ, _) -> + let typ' = handle_arr_size typ in + F.fprintf fmt "sizeof(%a)" (pp_typ_c pe) typ' + | e -> pp_exp_c pe fmt e + +(* generate code for sigma *) +let gen_sigma code proc_name spec_num env idmap sigma = + let post_code = Code.empty () in + let rec do_strexp code' base need_deref = function + | Sil.Eexp (e, inst) -> + let lhs = if need_deref then "(*"^base^")" else base in + let pp f () = F.fprintf f "%s = %a;" lhs (pp_exp_c pe) e in + Code.add_from_pp code' pp + | Sil.Estruct (fsel, _) -> + let accessor = if need_deref then "->" else "." in + list_iter (fun (f, se) -> do_strexp code' (base ^ accessor ^ Ident.fieldname_to_string f) false se) fsel + | Sil.Earray (size, esel, _) -> + list_iter (fun (e, se) -> + let pp f () = F.fprintf f "%a" (pp_exp_c pe) e in + let index = pp_to_string pp () in + do_strexp code' (base ^ "[" ^ index ^ "]") false se) esel in + + let gen_hpred = function + | Sil.Hpointsto (Sil.Lvar pvar, se, _) -> + let base = pvar_to_string pvar in + do_strexp post_code base false se + | Sil.Hpointsto (Sil.Var id, se, te) -> + let pp1 f () = + F.fprintf f "%a = malloc(%a);" (pp_id_c pe) id pp_texp_for_malloc te in + let pp2 f () = + F.fprintf f "if(%a == NULL) exit(12);" (pp_id_c pe) id in + Code.add_from_pp code pp1; + Code.add_from_pp code pp2; + let pp3 f () = F.fprintf f "%a" (pp_id_c pe) id in + let base = pp_to_string pp3 () in + do_strexp post_code base true se + | Sil.Hlseg (k, hpar, Sil.Var id, f, el) -> + let hpara_id = Sil.Predicates.get_hpara_id env hpar in + let size_var = mk_size_name hpara_id in + let mk_name = mk_lseg_name hpara_id proc_name spec_num in + let pp_el fmt el = + if el != [] then pp_exp_list_c pe fmt el in + let pp1 fmt () = + F.fprintf fmt "int %s = 42;" size_var in + let pp2 fmt () = + F.fprintf fmt "%a = %s(%s, %a%a);" (pp_id_c pe) id mk_name size_var (pp_exp_c pe) f pp_el el in + Code.add_from_pp code pp1; + Code.add_from_pp code pp2 + | hpred -> + L.err "gen_hpred not implemented: %a@." (Sil.pp_hpred pe) hpred in + list_iter gen_hpred sigma; + Code.append code post_code + +(* generate code corresponding to equalities in the pure part *) +let gen_init_equalities code pure = + let do_atom = function + | Sil.Aeq (Sil.Var id, e) -> + let pp f () = F.fprintf f "%a = %a;" (pp_id_c pe) id (pp_exp_c pe) e in + Code.add_from_pp code pp + | _ -> () in + list_iter do_atom pure + +(** generate variable declarations *) +let gen_var_decl code idmap parameters = + let do_parameter (name, typ) = + let pp_name f () = Format.fprintf f "%s" name in + let pp f () = F.fprintf f "%a;" (Sil.pp_type_decl pe pp_name pp_exp_c) typ in + Code.add_from_pp code pp in + let do_vinfo id { typ = typ; alloc = alloc } = + let pp_var f () = pp_id_c pe f id in + let pp f () = F.fprintf f "%a;" (Sil.pp_type_decl pe pp_var pp_exp_c) typ in + Code.add_from_pp code pp in + list_iter do_parameter parameters; + IdMap.iter do_vinfo idmap + +(** initialize variables not requiring allocation *) +let gen_init_vars code solutions idmap = + let get_const id c = + try Sil.Cint (IdMap.find id solutions) + with Not_found -> c in + let do_vinfo id { typ = typ; alloc = alloc } = + if not alloc then + let const = match typ with + | Sil.Tint _ | Sil.Tvoid -> + get_const id (Sil.Cint Sil.Int.zero) + | Sil.Tfloat _ -> + Sil.Cfloat 0.0 + | Sil.Tptr _ -> + get_const id (Sil.Cint Sil.Int.zero) + | Sil.Tfun _ -> + Sil.Cint Sil.Int.zero + | typ -> + L.err "do_vinfo type undefined: %a@." (Sil.pp_typ_full pe) typ; + assert false in + let pp fmt () = + F.fprintf fmt "%a = (%a) %a;" (pp_id_c pe) id (Sil.pp_typ_full pe) typ (Sil.pp_exp pe) (Sil.Const const) in + Code.add_from_pp code pp in + IdMap.iter do_vinfo idmap + +(** apply a filter to the identifiers of an idmap *) +let filter_idmap filter idmap = + let idmap' = ref IdMap.empty in + IdMap.iter (fun id x -> if filter id then idmap' := IdMap.add id x !idmap') idmap; + !idmap' + +let pp_svars fmt svars = + if svars != [] then F.fprintf fmt "%a" (pp_comma_seq (pp_id_c pe)) svars + + +let gen_hpara code proc_name spec_num env id hpara = + let mk_name = mk_lseg_name id proc_name spec_num in + let size_name = mk_size_name id in + let pp1 f () = + F.fprintf f "void* %s(int %s, void* %a%a) {" mk_name size_name (pp_id_c pe) hpara.Sil.next pp_svars hpara.Sil.svars in + let pp2 f () = + F.fprintf f "%a= %s(%s -1 , %a%a);" (pp_id_c pe) hpara.Sil.next mk_name size_name (pp_id_c pe) hpara.Sil.next pp_svars hpara.Sil.svars in + let line1 = pp_to_string pp1 () in + let idmap = create_idmap hpara.Sil.body in + let idmap_ex = + let filter i = + list_exists (Ident.equal i) hpara.Sil.evars in + filter_idmap filter idmap in + let idmap_no_next = + let filter i = + not (Ident.equal i hpara.Sil.next) in + filter_idmap filter idmap in + let line11 = "if ("^size_name^" == 0) {" in + let line12 = "return " ^ (pp_to_string (pp_id_c pe) hpara.Sil.next) ^ ";" in + let line13 ="} else {" in + let line14 = pp_to_string pp2 () in + let line2 = "return " ^ (pp_to_string (pp_id_c pe) hpara.Sil.root) ^ ";" in + let line3 = "}" in + Code.add_line code line1; + Code.set_indent " "; + gen_var_decl code idmap_no_next []; + Code.add_line code line11; + Code.set_indent " "; + Code.add_line code line12; + Code.set_indent " "; + Code.add_line code line13; + Code.set_indent " "; + Code.add_line code line14; + gen_init_vars code IdMap.empty idmap_ex; + gen_sigma code proc_name spec_num env idmap hpara.Sil.body; + Code.add_line code line2; + Code.set_indent " "; + Code.add_line code line3; + Code.set_indent ""; + Code.add_line code line3; + Code.add_line code "" + +let gen_hpara_dll code proc_name spec_num env id hpara_dll = assert false + +(** Generate epilog for the test case *) +let gen_epilog code proc_name parameters = + let pp_parameter fmt (name, typ) = + F.fprintf fmt "%s" name in + let pp f () = F.fprintf f "%a(%a);" Procname.pp proc_name (pp_comma_seq pp_parameter) parameters in + let line1 = pp_to_string pp () in + let line2 = "}" in + Code.add_line code line1; + Code.set_indent ""; + Code.add_line code line2 + +let test_function_name proc_name spec_num = + "unit_test_" ^ Procname.to_string proc_name ^ "_spec" ^ string_of_int spec_num + +(** Generate prolog for test case *) +let gen_prolog code fname proc_name spec_num = + let pp f () = + let fun_name = test_function_name proc_name spec_num in + F.fprintf f "/**@\n *@\n * Procedure: %a()@\n * File: %s@\n" Procname.pp proc_name fname; + F.fprintf f " * Unit Test: %s()@\n * Created: %a@\n *@\n */@\n@\n" fun_name pp_current_time (); + F.fprintf f "void %s() {" fun_name in + Code.add_from_pp code pp; + Code.set_indent " " + +let solve_constraints pure idmap = + let vars = ref [] in + let do_vinfo id { typ = typ; alloc = alloc } = + if not alloc then vars := !vars @ [id] in + IdMap.iter do_vinfo idmap; + Constraint.solve_from_pure pure !vars + +(** generate a unit test form a spec *) +let genunit fname proc_name spec_num parameters spec = + let pre = Specs.Jprop.to_prop spec.Specs.pre in + let pure = Prop.get_pure pre in + let sigma = Prop.get_sigma pre in + let env = Prop.prop_pred_env pre in + let idmap = create_idmap sigma in + let code = Code.empty () in + Sil.Predicates.iter env + (gen_hpara code proc_name spec_num env) + (gen_hpara_dll code proc_name spec_num env); + gen_prolog code fname proc_name spec_num; + gen_var_decl code idmap parameters; + gen_init_vars code (solve_constraints pure idmap) idmap; + gen_init_equalities code pure; + gen_sigma code proc_name spec_num env idmap sigma; + gen_epilog code proc_name parameters; + code + +(** generate code for a main calling all the unit test functions passed as argument *) +let genmain proc_numspecs_list = + let code = Code.empty () in + let do_one_proc (proc_name, num_specs) = + for i = 1 to num_specs do + let test_fun_name = test_function_name proc_name i in + let line = test_fun_name ^ "();" in + Code.add_line code line done in + Code.add_line code "int main() {"; + Code.set_indent " "; + list_iter do_one_proc proc_numspecs_list; + Code.add_line code "printf(\"unit test terminated\\n\");"; + Code.add_line code "return 0;"; + Code.set_indent ""; + Code.add_line code "}"; + code diff --git a/infer/src/backend/autounit.mli b/infer/src/backend/autounit.mli new file mode 100644 index 000000000..5dae77db1 --- /dev/null +++ b/infer/src/backend/autounit.mli @@ -0,0 +1,19 @@ +(* +* Copyright (c) 2009-2013 Monoidics ltd. +* Copyright (c) 2013- Facebook. +* All rights reserved. +*) + +(** Generate unit tests automatically from specs *) + +(** type of generated code *) +type code + +(** pretty print generated code *) +val pp_code : Format.formatter -> code -> unit + +(** generate a unit test form a spec *) +val genunit : string -> Procname.t -> int -> (string * Sil.typ) list -> Prop.normal Specs.spec -> code + +(** generate code for a main calling all the unit test functions passed as argument *) +val genmain : (Procname.t * int) list -> code \ No newline at end of file diff --git a/infer/src/backend/buckets.ml b/infer/src/backend/buckets.ml new file mode 100644 index 000000000..11ed8951b --- /dev/null +++ b/infer/src/backend/buckets.ml @@ -0,0 +1,109 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Classify bugs into buckets *) + +module L = Logging +module F = Format +open Utils + +let verbose = Config.trace_error + +(** check if the error was reported inside a nested loop +the implementation is approximate: check if the last two visits to a loop were entering loops *) +let check_nested_loop path pos_opt = + let trace_length = ref 0 in + let loop_visits_log = ref [] in + let in_nested_loop () = match !loop_visits_log with + | true :: true :: _ -> + if !verbose then L.d_strln "in nested loop"; + true (* last two loop visits were entering loops *) + | _ -> false in + let do_node_caller node = match Cfg.Node.get_kind node with + | Cfg.Node.Prune_node (b, (Sil.Ik_dowhile | Sil.Ik_for | Sil.Ik_while), _) -> + (* if !verbose then L.d_strln ((if b then "enter" else "exit") ^ " node " ^ (string_of_int (Cfg.Node.get_id node))); *) + loop_visits_log := b :: !loop_visits_log + | _ -> () in + let do_any_node level node = + incr trace_length; + (* L.d_strln ("level " ^ string_of_int level ^ " (Cfg.Node.get_id node) " ^ string_of_int nid); *) + () in + let f level p session exn_opt = + let node = Paths.Path.curr_node p in + do_any_node level node; + if level = 0 then do_node_caller node; + () in + Paths.Path.iter_longest_sequence f pos_opt path; + in_nested_loop () + +(** Check that we know where the value was last assigned, and that there is a local access instruction at that line. **) +let check_access access_opt = + let find_bucket line_number null_case_flag = + let find_formal_ids node = (* find ids obtained by a letref on a formal parameter *) + let node_instrs = Cfg.Node.get_instrs node in + let formals = Cfg.Procdesc.get_formals (Cfg.Node.get_proc_desc node) in + let formal_names = list_map (fun (s, _) -> Mangled.from_string s) formals in + let is_formal pvar = + let name = Sil.pvar_get_name pvar in + list_exists (Mangled.equal name) formal_names in + let formal_ids = ref [] in + let process_formal_letref = function + | Sil.Letderef (id, Sil.Lvar pvar, _, _) -> + let is_java_this = + !Sil.curr_language = Sil.Java && Sil.pvar_is_this pvar in + if not is_java_this && is_formal pvar then formal_ids := id :: !formal_ids + | _ -> () in + list_iter process_formal_letref node_instrs; + !formal_ids in + let formal_param_used_in_call = ref false in + let has_call_or_sets_null node = + let rec exp_is_null = function + | Sil.Const (Sil.Cint n) -> Sil.Int.iszero n + | Sil.Cast (_, e) -> exp_is_null e + | _ -> false in + let filter = function + | Sil.Call (_, _, etl, _, _) -> + let formal_ids = find_formal_ids node in + let arg_is_formal_param (e, t) = match e with + | Sil.Var id -> list_exists (Ident.equal id) formal_ids + | _ -> false in + if list_exists arg_is_formal_param etl then formal_param_used_in_call := true; + true + | Sil.Set (_, _, e, _) -> exp_is_null e + | _ -> false in + list_exists filter (Cfg.Node.get_instrs node) in + let local_access_found = ref false in + let do_node node = + if (Cfg.Node.get_loc node).Sil.line = line_number && has_call_or_sets_null node then + begin + local_access_found := true + end in + let path, pos_opt = State.get_path () in + Paths.Path.iter_all_nodes_nocalls do_node path; + if !local_access_found then + let bucket = + if null_case_flag then Localise.BucketLevel.b5 else + if check_nested_loop path pos_opt then Localise.BucketLevel.b3 + else if !formal_param_used_in_call then Localise.BucketLevel.b2 + else Localise.BucketLevel.b1 in + Some bucket + else None in + + match access_opt with + | Some (Localise.Last_assigned (n, ncf)) -> + find_bucket n ncf + | Some (Localise.Returned_from_call n) -> + find_bucket n false + | Some (Localise.Last_accessed (n, is_nullable)) when is_nullable -> + Some Localise.BucketLevel.b1 + | _ -> None + +let classify_access desc access_opt = + L.d_strln "Doing classification"; + let show_in_message = !Config.show_buckets in + match check_access access_opt with + | None -> Localise.error_desc_set_bucket desc Localise.BucketLevel.b5 show_in_message + | Some bucket -> Localise.error_desc_set_bucket desc bucket show_in_message diff --git a/infer/src/backend/buckets.mli b/infer/src/backend/buckets.mli new file mode 100644 index 000000000..c03999cf2 --- /dev/null +++ b/infer/src/backend/buckets.mli @@ -0,0 +1,12 @@ +(* +* Copyright (c) 2009-2013 Monoidics ltd. +* Copyright (c) 2013- Facebook. +* All rights reserved. +*) + +(** Classify bugs into buckets *) + +open Utils + +(** Classify the bucket of an error desc using Location.access information *) +val classify_access : Localise.error_desc -> Localise.access option -> Localise.error_desc diff --git a/infer/src/backend/callbacks.ml b/infer/src/backend/callbacks.ml new file mode 100644 index 000000000..2f203cedd --- /dev/null +++ b/infer/src/backend/callbacks.ml @@ -0,0 +1,241 @@ +(* +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +open Utils +module L = Logging + +(** Module to register and invoke callbacks *) + +(** Inline a synthetic (access or bridge) method. *) +let inline_synthetic_method ret_ids etl proc_desc proc_name loc_call : Sil.instr option = + let modified = ref None in + let debug = false in + let found instr instr' = + modified := Some instr'; + if debug then + begin + L.stderr "XX inline_synthetic_method found instr: %a@." (Sil.pp_instr pe_text) instr; + L.stderr "XX inline_synthetic_method instr': %a@." (Sil.pp_instr pe_text) instr' + end in + let do_instr node instr = + match instr, ret_ids, etl with + | Sil.Letderef (id1, Sil.Lfield (Sil.Var id2, fn, ft), bt, loc), [ret_id], [(e1, t1)] -> (* getter for fields *) + let instr' = Sil.Letderef (ret_id, Sil.Lfield (e1, fn, ft), bt, loc_call) in + found instr instr' + | Sil.Letderef (id1, Sil.Lfield (Sil.Lvar pvar, fn, ft), bt, loc), [ret_id], [] + when Sil.pvar_is_global pvar -> (* getter for static fields *) + let instr' = Sil.Letderef (ret_id, Sil.Lfield (Sil.Lvar pvar, fn, ft), bt, loc_call) in + found instr instr' + | Sil.Set (Sil.Lfield (ex1, fn, ft), bt , ex2, loc), _, [(e1, t1); (e2, t2)] -> (* setter for fields *) + let instr' = Sil.Set (Sil.Lfield (e1, fn, ft), bt , e2, loc_call) in + found instr instr' + | Sil.Set (Sil.Lfield (Sil.Lvar pvar, fn, ft), bt , ex2, loc), _, [(e1, t1)] + when Sil.pvar_is_global pvar -> (* setter for static fields *) + let instr' = Sil.Set (Sil.Lfield (Sil.Lvar pvar, fn, ft), bt , e1, loc_call) in + found instr instr' + | Sil.Call (ret_ids', Sil.Const (Sil.Cfun pn), etl', loc', cf), _, _ + when list_length ret_ids = list_length ret_ids' + && list_length etl' = list_length etl -> + let instr' = Sil.Call (ret_ids, Sil.Const (Sil.Cfun pn), etl, loc_call, cf) in + found instr instr' + | Sil.Call (ret_ids', Sil.Const (Sil.Cfun pn), etl', loc', cf), _, _ + when list_length ret_ids = list_length ret_ids' + && list_length etl' + 1 = list_length etl -> + let etl1 = match list_rev etl with (* remove last element *) + | _ :: l -> list_rev l + | [] -> assert false in + let instr' = Sil.Call (ret_ids, Sil.Const (Sil.Cfun pn), etl1, loc_call, cf) in + found instr instr' + | _ -> () in + Cfg.Procdesc.iter_instrs do_instr proc_desc; + !modified + +(** Find synthetic (access or bridge) methods in the procedure and inline them in the cfg. *) +let proc_inline_synthetic_methods cfg proc_desc : unit = + let instr_inline_synthetic_method = function + | Sil.Call (ret_ids, Sil.Const (Sil.Cfun pn), etl, loc, _) -> + (match Cfg.Procdesc.find_from_name cfg pn with + | Some pd -> + let is_access = Procname.java_is_access_method pn in + let attributes = Cfg.Procdesc.get_attributes pd in + let is_synthetic = attributes.Sil.is_synthetic_method in + let is_bridge = attributes.Sil.is_bridge_method in + if is_access || is_bridge || is_synthetic + then inline_synthetic_method ret_ids etl pd pn loc + else None + | None -> None) + | _ -> None in + let node_inline_synthetic_methods node = + let modified = ref false in + let do_instr instr = match instr_inline_synthetic_method instr with + | None -> instr + | Some instr' -> + modified := true; + instr' in + let instrs = Cfg.Node.get_instrs node in + let instrs' = list_map do_instr instrs in + if !modified then Cfg.Node.replace_instrs node instrs' in + Cfg.Procdesc.iter_nodes node_inline_synthetic_methods proc_desc + + +type proc_callback_t = + Procname.t list -> + (Procname.t -> Cfg.Procdesc.t option) -> + Idenv.t -> + Sil.tenv -> + Procname.t -> + Cfg.Procdesc.t -> + unit + +type cluster_callback_t = + Procname.t list -> + (Procname.t -> Cfg.Procdesc.t option) -> + (Idenv.t * Sil.tenv * Procname.t * Cfg.Procdesc.t) list -> + unit + +let procedure_callbacks = ref [] +let cluster_callbacks = ref [] + +let register_procedure_callback language_opt (callback: proc_callback_t) = + procedure_callbacks := (language_opt, callback):: !procedure_callbacks + +let register_cluster_callback language_opt (callback: cluster_callback_t) = + cluster_callbacks := (language_opt, callback):: !cluster_callbacks + +let unregister_all_callbacks () = + procedure_callbacks := []; + cluster_callbacks := [] + + +(** Collect what we need to know about a procedure for the analysis. *) +let get_procedure_definition exe_env proc_name = + let cfg = Exe_env.get_cfg exe_env proc_name in + let tenv = Exe_env.get_tenv exe_env proc_name in + Option.map + (fun proc_desc -> + proc_inline_synthetic_methods cfg proc_desc; + let idenv = Idenv.create cfg proc_desc + and language = (Cfg.Procdesc.get_attributes proc_desc).Sil.language in + (idenv, tenv, proc_name, proc_desc, language)) + (Cfg.Procdesc.find_from_name cfg proc_name) + +let get_language proc_name = if Procname.is_java proc_name then Sil.Java else Sil.C_CPP + +(** Invoke all registered procedure callbacks on a set of procedures. *) +let iterate_procedure_callbacks all_procs exe_env proc_name = + let procedure_language = get_language proc_name in + Sil.curr_language := procedure_language; + + let cfg = Exe_env.get_cfg exe_env proc_name in + let get_procdesc proc_name = + let cfg = try Exe_env.get_cfg exe_env proc_name with Not_found -> cfg in + Cfg.Procdesc.find_from_name cfg proc_name in + + let update_time proc_name elapsed = + let prev_summary = Specs.get_summary_unsafe proc_name in + let stats_time = prev_summary.Specs.stats.Specs.stats_time +. elapsed in + let stats = { prev_summary.Specs.stats with Specs.stats_time = stats_time } in + let summary = { prev_summary with Specs.stats = stats } in + Specs.add_summary proc_name summary in + + Option.may + (fun (idenv, tenv, proc_name, proc_desc, language) -> + list_iter + (fun (language_opt, proc_callback) -> + let language_matches = match language_opt with + | Some language -> language = procedure_language + | None -> true in + if language_matches then + begin + let init_time = Unix.gettimeofday () in + proc_callback all_procs get_procdesc idenv tenv proc_name proc_desc; + let elapsed = Unix.gettimeofday () -. init_time in + update_time proc_name elapsed + end) + !procedure_callbacks) + (get_procedure_definition exe_env proc_name) + +(** Invoke all registered cluster callbacks on a cluster of procedures. *) +let iterate_cluster_callbacks all_procs exe_env proc_names = + let get_procdesc proc_name = + try + let cfg = Exe_env.get_cfg exe_env proc_name in + Cfg.Procdesc.find_from_name cfg proc_name + with Not_found -> None in + + let procedure_definitions = + list_map (get_procedure_definition exe_env) proc_names + |> list_flatten_options in + + let environment = + list_map + (fun (idenv, tenv, proc_name, proc_desc, _) -> (idenv, tenv, proc_name, proc_desc)) + procedure_definitions in + + (** Procedures matching the given language or all if no language is specified. *) + let relevant_procedures language_opt = + Option.map_default + (fun l -> list_filter (fun p -> l = get_language p) proc_names) + proc_names + language_opt in + + list_iter + (fun (language_opt, cluster_callback) -> + let proc_names = relevant_procedures language_opt in + if list_length proc_names > 0 then + cluster_callback all_procs get_procdesc environment) + !cluster_callbacks + +(** Invoke all procedure and cluster callbacks on a given environment. *) +let iterate_callbacks store_summary call_graph exe_env = + let proc_names = Cg.get_defined_nodes call_graph in + let saved_language = !Sil.curr_language in + + let cluster_id proc_name = + match get_language proc_name with + | Sil.Java -> Procname.java_get_class proc_name + | _ -> "unknown" in + let cluster proc_names = + let cluster_map = + list_fold_left + (fun map proc_name -> + let proc_cluster = cluster_id proc_name in + let bucket = try StringMap.find proc_cluster map with Not_found -> [] in + StringMap.add proc_cluster (proc_name:: bucket) map) + StringMap.empty + proc_names in + (* Return all values of the map *) + list_map snd (StringMap.bindings cluster_map) in + let reset_summary proc_name = + let cfg_opt = + try + Some (Exe_env.get_cfg exe_env proc_name) with + | Not_found -> None in + let procdesc_opt = match cfg_opt with + | Some cfg -> + Cfg.Procdesc.find_from_name cfg proc_name + | None -> None in + let loc = match procdesc_opt with + | Some proc_desc -> + Cfg.Procdesc.get_loc proc_desc + | None -> Sil.loc_none in + Specs.reset_summary call_graph proc_name loc in + + + (* Make sure summaries exists. *) + list_iter reset_summary proc_names; + + + (* Invoke callbacks. *) + list_iter + (iterate_procedure_callbacks proc_names exe_env) + proc_names; + + list_iter + (iterate_cluster_callbacks proc_names exe_env) + (cluster proc_names); + + list_iter store_summary proc_names; + + Sil.curr_language := saved_language diff --git a/infer/src/backend/callbacks.mli b/infer/src/backend/callbacks.mli new file mode 100644 index 000000000..56f39b2c7 --- /dev/null +++ b/infer/src/backend/callbacks.mli @@ -0,0 +1,42 @@ +(* +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +(** Module to register and invoke callbacks *) + + +(** Type of a procedure callback: +- List of all the procedures the callback will be called on. +- get_proc_desc to get a proc desc from a proc name. +- Idenv to look up the definition of ids in a cfg. +- Type environment. +- Procedure for the callback to act on. *) +type proc_callback_t = + Procname.t list -> + (Procname.t -> Cfg.Procdesc.t option) -> + Idenv.t -> + Sil.tenv -> + Procname.t -> + Cfg.Procdesc.t -> + unit + +type cluster_callback_t = + Procname.t list -> + (Procname.t -> Cfg.Procdesc.t option) -> + (Idenv.t * Sil.tenv * Procname.t * Cfg.Procdesc.t) list -> + unit + +(** register a procedure callback *) +val register_procedure_callback : Sil.language option -> proc_callback_t -> unit + +(** register a cluster callback *) +val register_cluster_callback : Sil.language option -> cluster_callback_t -> unit + +(** un-register all the procedure callbacks currently registered *) +val unregister_all_callbacks : unit -> unit + +(** Invoke all the registered callbacks. *) +val iterate_callbacks : (Procname.t -> unit) -> Cg.t -> Exe_env.t -> unit + +(** Find synthetic (access or bridge) methods in the procedure and inline them in the cfg. *) +val proc_inline_synthetic_methods: Cfg.cfg -> Cfg.Procdesc.t -> unit diff --git a/infer/src/backend/cfg.ml b/infer/src/backend/cfg.ml new file mode 100644 index 000000000..5e512e722 --- /dev/null +++ b/infer/src/backend/cfg.ml @@ -0,0 +1,933 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +module L = Logging +module F = Format + +open Utils (* No abbreviation for Utils, as every module can depend on it *) + +(* ============== START of ADT node and proc_desc ============== *) + +(* =============== START of module Node =============== *) +module Node = struct + type nodekind = + | Start_node of proc_desc + | Exit_node of proc_desc + | Stmt_node of string + | Join_node + | Prune_node of bool * Sil.if_kind * string (** (true/false branch, if_kind, comment) *) + | Skip_node of string + + and t = { (** a node *) + nd_id : int; (** unique id of the node *) + mutable nd_dist_exit : int option; (** distance to the exit node *) + mutable nd_temps : Ident.t list; (** temporary variables *) + mutable nd_dead_pvars_after : Sil.pvar list; (** dead program variables after executing the instructions *) + mutable nd_dead_pvars_before : Sil.pvar list; (** dead program variables before executing the instructions *) + mutable nd_exn : t list; (** exception nodes in the cfg *) + mutable nd_instrs : Sil.instr list; (** instructions for symbolic execution *) + mutable nd_kind : nodekind; (** kind of node *) + mutable nd_loc : Sil.location; (** location in the source code *) + mutable nd_preds : t list; (** predecessor nodes in the cfg *) + mutable nd_proc : proc_desc option; (** proc desc from cil *) + mutable nd_succs : t list; (** successor nodes in the cfg *) + } + and proc_desc = { (** procedure description *) + pd_attributes : Sil.proc_attributes; (** attributes of the procedure *) + pd_id : int; (** unique proc_desc identifier *) + pd_name : Procname.t; (** name of the procedure *) + pd_is_defined : bool; (** true iff the procedure is defined, and not just declared *) + pd_ret_type : Sil.typ; (** return type *) + pd_formals : (string * Sil.typ) list; (** name and type of formal parameters *) + mutable pd_locals : (Mangled.t * Sil.typ) list; (** name and type of local variables *) + pd_captured : (Mangled.t * Sil.typ) list; (** name and type of blocks' captured variables *) + mutable pd_nodes : t list; (** list of nodes of this procedure *) + mutable pd_start_node : t; (** start node of this procedure *) + mutable pd_exit_node : t; (** exit node of ths procedure *) + mutable pd_loc : Sil.location; (** location of this procedure in the source code *) + mutable pd_flags : proc_flags; (** flags for the procedure *) + pd_err_log: Errlog.t; (** error log at translation time *) + } + + let exn_handler_kind = Stmt_node "exception handler" + let exn_sink_kind = Stmt_node "exceptions sink" + + type cfg = (** data type for the control flow graph *) + { node_id : int ref; + node_list : t list ref; + name_pdesc_tbl : proc_desc Procname.Hash.t; (** Map proc name to procdesc *) + mutable priority_set : Procname.Set.t (** set of function names to be analyzed first *) } + + let create_cfg () = (** create a new empty cfg *) + { node_id = ref 0; + node_list = ref []; + name_pdesc_tbl = Procname.Hash.create 1000; + priority_set = Procname.Set.empty } + + let compute_enabled_verbose = false + + (** restrict the cfg to the given enabled (active and not shadowed) procedures *) + let cfg_restrict_enabled cfg source enabled = + match enabled with + | None -> () + | Some enabled_procs -> + if compute_enabled_verbose then L.err "cfg_restrict_enabled: checking enabled in %s@." (DB.source_file_to_string source); + let is_enabled pname = Procname.Set.mem pname enabled_procs in + let in_address_set pname = Procname.Set.mem pname cfg.priority_set in + let node_list' = + let filter_node node = match node.nd_proc with + | None -> true + | Some pdesc -> is_enabled pdesc.pd_name in + list_filter filter_node !(cfg.node_list) in + let procs_to_remove = + let psetr = ref Procname.Set.empty in + let do_proc pname pdesc = + if pdesc.pd_is_defined && not (is_enabled pname) && not (in_address_set pname) then psetr := Procname.Set.add pname !psetr in + Procname.Hash.iter do_proc cfg.name_pdesc_tbl; + !psetr in + let remove_proc pname = + if compute_enabled_verbose then L.err "cfg_restrict_enabled: Removing proc not enabled from the cfg: %s@." (Procname.to_filename pname); + Procname.Hash.remove cfg.name_pdesc_tbl pname in + cfg.node_list := node_list'; + Procname.Set.iter remove_proc procs_to_remove + + let node_id_gen cfg = incr cfg.node_id; !(cfg.node_id) + + let pdesc_tbl_add cfg proc_name proc_desc = + Procname.Hash.add cfg.name_pdesc_tbl proc_name proc_desc + + let pdesc_tbl_remove cfg proc_name = + Procname.Hash.remove cfg.name_pdesc_tbl proc_name + + let pdesc_tbl_find cfg proc_name = + Procname.Hash.find cfg.name_pdesc_tbl proc_name + + let proc_name_to_proc_desc cfg proc_name = + pdesc_tbl_find cfg proc_name + + let iter_proc_desc cfg f = + Procname.Hash.iter f cfg.name_pdesc_tbl + + let iter_types cfg f = + let node_iter_types node = + list_iter (Sil.instr_iter_types f) node.nd_instrs in + let pdesc_iter_types pname pdesc = + Sil.typ_iter_types f pdesc.pd_ret_type; + list_iter (fun (_, t) -> Sil.typ_iter_types f t) pdesc.pd_formals; + list_iter (fun (_, t) -> Sil.typ_iter_types f t) pdesc.pd_locals; + list_iter node_iter_types pdesc.pd_nodes in + iter_proc_desc cfg pdesc_iter_types + + let dummy () = { + nd_id = 0; + nd_dist_exit = None; + nd_temps = []; + nd_dead_pvars_after = []; + nd_dead_pvars_before = []; + nd_instrs = []; + nd_kind = Skip_node "dummy"; + nd_loc = Sil.loc_none; + nd_proc = None; + nd_succs = []; nd_preds = []; nd_exn = []; + } + + let compare node1 node2 = + int_compare node1.nd_id node2.nd_id + + let hash node = + Hashtbl.hash node.nd_id + + let equal node1 node2 = + (compare node1 node2 = 0) + + let get_all_nodes cfg = !(cfg.node_list) + + let create cfg loc kind instrs pdesc temps = + let node_id = node_id_gen cfg in + let node = + { nd_id = node_id; + nd_dist_exit = None; + nd_temps = temps; + nd_dead_pvars_after = []; + nd_dead_pvars_before = []; + nd_instrs = instrs; + nd_kind = kind; + nd_loc = loc; + nd_preds = []; + nd_proc = Some pdesc; + nd_succs = []; + nd_exn = [] + } in + cfg.node_list := node :: !(cfg.node_list); + pdesc.pd_nodes <- node :: pdesc.pd_nodes; + node + + (** Get the unique id of the node *) + let get_id node = node.nd_id + + let get_succs node = node.nd_succs + + type node = t + module NodeSet = Set.Make(struct + type t = node + let compare = compare + end) + + let get_sliced_succs node f = + let visited = ref NodeSet.empty in + let rec slice_nodes nodes : NodeSet.t = + let do_node acc n = + visited := NodeSet.add n !visited; + if f n then NodeSet.singleton n + else NodeSet.union acc (slice_nodes (list_filter (fun s -> not (NodeSet.mem s !visited)) n.nd_succs)) in + list_fold_left do_node NodeSet.empty nodes in + NodeSet.elements (slice_nodes node.nd_succs) + + let get_sliced_preds node f = + let visited = ref NodeSet.empty in + let rec slice_nodes nodes : NodeSet.t = + let do_node acc n = + visited := NodeSet.add n !visited; + if f n then NodeSet.singleton n + else NodeSet.union acc (slice_nodes (list_filter (fun s -> not (NodeSet.mem s !visited)) n.nd_preds)) in + list_fold_left do_node NodeSet.empty nodes in + NodeSet.elements (slice_nodes node.nd_preds) + + let get_exn node = node.nd_exn + + let set_proc_desc node proc = node.nd_proc <- Some proc + + (** Set the successor nodes and exception nodes, and build predecessor links *) + let set_succs_exn node succs exn = + node.nd_succs <- succs; + node.nd_exn <- exn; + list_iter (fun n -> n.nd_preds <- (node :: n.nd_preds)) succs + + (** Get the predecessors of the node *) + let get_preds node = node.nd_preds + + (** Generates a list of nodes starting at a given node and recursively adding the results of the generator *) + let get_generated_slope start_node generator = + let visited = ref NodeSet.empty in + let rec nodes n = + visited := NodeSet.add n !visited; + let succs = list_filter (fun n -> not (NodeSet.mem n !visited)) (generator n) in + match list_length succs with + | 1 -> n:: (nodes (list_hd succs)) + | _ -> [n] in + nodes start_node + + (** Get the node kind *) + let get_kind node = node.nd_kind + + (** Set the node kind *) + let set_kind node kind = node.nd_kind <- kind + + (** Comparison for node kind *) + let kind_compare k1 k2 = match k1, k2 with + | Start_node pd1, Start_node pd2 -> + int_compare pd1.pd_id pd2.pd_id + | Start_node _, _ -> -1 + | _, Start_node _ -> 1 + | Exit_node pd1, Exit_node pd2 -> + int_compare pd1.pd_id pd2.pd_id + | Exit_node _, _ -> -1 + | _, Exit_node _ -> 1 + | Stmt_node s1, Stmt_node s2 -> + string_compare s1 s2 + | Stmt_node _, _ -> -1 + | _, Stmt_node _ -> 1 + | Join_node, Join_node -> 0 + | Join_node, _ -> -1 + | _, Join_node -> 1 + | Prune_node (is_true_branch1, if_kind1, descr1), + Prune_node (is_true_branch2, if_kind2, descr2) -> + let n = bool_compare is_true_branch1 is_true_branch2 in + if n <> 0 then n else let n = Pervasives.compare if_kind1 if_kind2 in + if n <> 0 then n else string_compare descr1 descr2 + | Prune_node _, _ -> -1 + | _, Prune_node _ -> 1 + | Skip_node s1, Skip_node s2 -> + string_compare s1 s2 + + (** Get the instructions to be executed *) + let get_instrs node = + node.nd_instrs + + (** Get the list of callee procnames from the node *) + let get_callees node = + let collect callees instr = + match instr with + | Sil.Call (_, exp, _, _, _) -> + begin + match exp with + | Sil.Const (Sil.Cfun procname) -> procname:: callees + | _ -> callees + end + | _ -> callees in + list_fold_left collect [] (get_instrs node) + + (** Get the location of the node *) + let get_loc n = n.nd_loc + + (** Get the source location of the last instruction in the node *) + let get_last_loc n = + match list_rev (get_instrs n) with + | instr :: _ -> Sil.instr_get_loc instr + | [] -> n.nd_loc + + (** Set the location of the node *) + let set_loc n loc = n.nd_loc <- loc + + let pp f node = + F.fprintf f "%n" (get_id node) + + (** Get the proc desc of the node *) + let get_proc_desc node = + let proc_desc = match node.nd_proc with + | None -> + L.out "node_get_proc_desc: at node %d@\n" node.nd_id; + assert false + | Some proc_desc -> proc_desc in + proc_desc + + let proc_desc_from_name cfg proc_name = + try Some (pdesc_tbl_find cfg proc_name) + with Not_found -> None + + (** Set the proc desc of the node *) + let node_set_proc_desc pdesc node = + node.nd_proc <- Some pdesc + + let set_temps node temps = + node.nd_temps <- temps + + let get_temps node = + node.nd_temps + + let set_dead_pvars node after dead = + if after then node.nd_dead_pvars_after <- dead + else node.nd_dead_pvars_before <- dead + + let get_dead_pvars node after = + if after then node.nd_dead_pvars_after + else node.nd_dead_pvars_before + + let get_distance_to_exit node = + node.nd_dist_exit + + (** Append the instructions and temporaries to the list of instructions to execute *) + let append_instrs_temps node instrs temps = + node.nd_instrs <- node.nd_instrs @ instrs; + node.nd_temps <- node.nd_temps @ temps + + (** Add the instructions and temporaties at the beginning of the list of instructions to execute *) + let prepend_instrs_temps node instrs temps = + node.nd_instrs <- instrs @ node.nd_instrs; + node.nd_temps <- temps @ node.nd_temps + + (** Replace the instructions to be executed. *) + let replace_instrs node instrs = + node.nd_instrs <- instrs + + let proc_desc_get_ret_var pdesc = Sil.mk_pvar Ident.name_return pdesc.pd_name + + (** Add declarations for local variables and return variable to the node *) + let add_locals_ret_declaration node locals = + let loc = get_loc node in + let pdesc = get_proc_desc node in + let proc_name = pdesc.pd_name in + let ret_var = + let ret_type = pdesc.pd_ret_type in + (proc_desc_get_ret_var pdesc, ret_type) in + let construct_decl (x, typ) = + (Sil.mk_pvar x proc_name, typ) in + let ptl = ret_var :: list_map construct_decl locals in + let instr = Sil.Declare_locals (ptl, loc) in + prepend_instrs_temps node [instr] [] + + (** Counter for identifiers of procdescs *) + let proc_desc_id_counter = ref 0 + + let remove_node cfg node = + let remove_node_in_cfg nodes = + list_filter (fun node' -> not (equal node node')) nodes in + cfg.node_list := remove_node_in_cfg !(cfg.node_list) + + let proc_desc_remove cfg name remove_nodes = + (if remove_nodes then + let pdesc = pdesc_tbl_find cfg name in + list_iter (remove_node cfg) pdesc.pd_nodes); + pdesc_tbl_remove cfg name + + let proc_desc_get_start_node proc_desc = + proc_desc.pd_start_node + + let proc_desc_get_err_log proc_desc = + proc_desc.pd_err_log + + let proc_desc_get_attributes proc_desc = + proc_desc.pd_attributes + + let proc_desc_get_exit_node proc_desc = + proc_desc.pd_exit_node + + (** Compute the distance of each node to the exit node, if not computed already *) + let proc_desc_compute_distance_to_exit_node proc_desc = + let exit_node = proc_desc.pd_exit_node in + let rec mark_distance dist nodes = + let next_nodes = ref [] in + let do_node node = + match node.nd_dist_exit with + | Some _ -> () + | None -> + node.nd_dist_exit <- Some dist; + next_nodes := node.nd_preds @ !next_nodes in + list_iter do_node nodes; + if !next_nodes != [] then mark_distance (dist + 1) !next_nodes in + mark_distance 0 [exit_node] + + (** Set the start node of the proc desc *) + let proc_desc_set_start_node pdesc node = + pdesc.pd_start_node <- node + + (** Set the exit node of the proc desc *) + let proc_desc_set_exit_node pdesc node = + pdesc.pd_exit_node <- node + + (** Set a flag for the proc desc *) + let proc_desc_set_flag pdesc key value = + proc_flags_add pdesc.pd_flags key value + + (** Return the return type of the procedure *) + let proc_desc_get_ret_type proc_desc = + proc_desc.pd_ret_type + + let proc_desc_get_proc_name proc_desc = + proc_desc.pd_name + + (** Return [true] iff the procedure is defined, and not just declared *) + let proc_desc_is_defined proc_desc = + proc_desc.pd_is_defined + + let proc_desc_get_loc proc_desc = + proc_desc.pd_loc + + (** Return name and type of formal parameters *) + let proc_desc_get_formals proc_desc = + proc_desc.pd_formals + + (** Return name and type of local variables *) + let proc_desc_get_locals proc_desc = + proc_desc.pd_locals + + (** Return name and type of captured variables *) + let proc_desc_get_captured proc_desc = + proc_desc.pd_captured + + let proc_desc_get_nodes proc_desc = + proc_desc.pd_nodes + + (** List of nodes in the procedure up to the first branching *) + let proc_desc_get_slope proc_desc = + get_generated_slope (proc_desc_get_start_node proc_desc) get_succs + + (** List of nodes in the procedure sliced by a predicate up to the first branching *) + let proc_desc_get_sliced_slope proc_desc f = + get_generated_slope (proc_desc_get_start_node proc_desc) (fun n -> get_sliced_succs n f) + + (** Get flags for the proc desc *) + let proc_desc_get_flags proc_desc = + proc_desc.pd_flags + + (** Append the locals to the list of local variables *) + let proc_desc_append_locals proc_desc new_locals = + proc_desc.pd_locals <- proc_desc.pd_locals @ new_locals + + (** Get the cyclomatic complexity for the procedure *) + let proc_desc_get_cyclomatic proc_desc = + let num_edges = ref 0 in + let num_nodes = ref 0 in + let num_connected = 1 in (* always one connected component in a procedure's cfg *) + let nodes = proc_desc_get_nodes proc_desc in + let do_node node = + incr num_nodes; + num_edges := !num_edges + list_length (get_succs node) in + list_iter do_node nodes; + let cyclo = !num_edges - !num_nodes + 2 * num_connected in (* formula for cyclomatic complexity *) + cyclo + + (** Print extended instructions for the node, highlighting the given subinstruction if present *) + let pp_instr pe0 ~sub_instrs instro fmt node = + let pe = match instro with + | None -> pe0 + | Some instr -> pe_extend_colormap pe0 (Obj.repr instr) Red in + let instrs = get_instrs node in + let pp_loc fmt () = F.fprintf fmt " %a " Sil.pp_loc (get_loc node) in + let print_sub_instrs () = F.fprintf fmt "%a" (Sil.pp_instr_list pe) instrs in + match get_kind node with + | Stmt_node s -> + if sub_instrs then print_sub_instrs () + else F.fprintf fmt "statements (%s) %a" s pp_loc () + | Prune_node (is_true_branch, if_kind, descr) -> + if sub_instrs then print_sub_instrs () + else F.fprintf fmt "assume %s %a" descr pp_loc () + | Exit_node _ -> + if sub_instrs then print_sub_instrs () + else F.fprintf fmt "exit %a" pp_loc () + | Skip_node s -> + if sub_instrs then print_sub_instrs () + else F.fprintf fmt "skip (%s) %a" s pp_loc () + | Start_node _ -> + if sub_instrs then print_sub_instrs () + else F.fprintf fmt "start %a" pp_loc () + | Join_node -> + if sub_instrs then print_sub_instrs () + else F.fprintf fmt "join %a" pp_loc () + + (** Dump extended instructions for the node *) + let d_instrs ~(sub_instrs: bool) (curr_instr: Sil.instr option) (node: t) = + L.add_print_action (L.PTnode_instrs, Obj.repr (sub_instrs, curr_instr, node)) + + (** Return a description of the cfg node *) + let get_description pe node = + let str = + match get_kind node with + | Stmt_node _ -> + "Instructions" + | Prune_node (is_true_branch, if_kind, descr) -> + "Conditional" ^ " " ^ descr + | Exit_node _ -> + "Exit" + | Skip_node s -> + "Skip" + | Start_node _ -> + "Start" + | Join_node -> + "Join" in + let pp fmt () = F.fprintf fmt "%s\n%a@?" str (pp_instr pe None ~sub_instrs: true) node in + pp_to_string pp () + + let proc_desc_iter_nodes f proc_desc = + list_iter f (list_rev (proc_desc_get_nodes proc_desc)) + + let proc_desc_fold_nodes f acc proc_desc = + (*list_fold_left (fun acc node -> f acc node) acc (list_rev (proc_desc_get_nodes proc_desc))*) + list_fold_left f acc (list_rev (proc_desc_get_nodes proc_desc)) + + (** iterate over the calls from the procedure: (callee,location) pairs *) + let proc_desc_iter_calls f pdesc = + let do_node node = + list_iter + (fun callee_pname -> f (callee_pname, get_loc node)) + (get_callees node) in + list_iter do_node (proc_desc_get_nodes pdesc) + + let proc_desc_iter_slope f proc_desc = + let visited = ref NodeSet.empty in + let rec do_node node = begin + visited := NodeSet.add node !visited; + f node; + match get_succs node with + | [n] -> if not (NodeSet.mem n !visited) then do_node n + | _ -> () + end in + do_node (proc_desc_get_start_node proc_desc) + + (** iterate between two nodes or until we reach a branching structure *) + let proc_desc_iter_slope_range f proc_desc src_node dst_node = + let visited = ref NodeSet.empty in + let rec do_node node = begin + visited := NodeSet.add node !visited; + f node; + match get_succs node with + | [n] -> + if not (NodeSet.mem n !visited) + && not (equal node dst_node) + then do_node n + | _ -> () + end in + do_node src_node + + let proc_desc_iter_slope_calls f proc_desc = + let do_node node = + list_iter + (fun callee_pname -> f callee_pname) + (get_callees node) in + proc_desc_iter_slope do_node proc_desc + + let proc_desc_iter_instrs f proc_desc = + let do_node node = + list_iter (fun i -> f node i) (get_instrs node) in + proc_desc_iter_nodes do_node proc_desc + + let proc_desc_fold_instrs f acc proc_desc = + let fold_node acc node = + list_fold_left (fun acc instr -> f acc node instr) acc (get_instrs node) in + proc_desc_fold_nodes fold_node acc proc_desc + +end +(* =============== END of module Node =============== *) + +type node = Node.t +type cfg = Node.cfg + +(** Serializer for control flow graphs *) +let cfg_serializer : cfg Serialization.serializer = Serialization.create_serializer Serialization.cfg_key + +(** Load a cfg from a file *) +let load_cfg_from_file (filename : DB.filename) : cfg option = + Serialization.from_file cfg_serializer filename + +(** save a copy in the results dir of the source files of procedures defined in the cfg, unless an updated copy already exists *) +let save_source_files cfg = + let process_proc pname pdesc = + let loc = Node.proc_desc_get_loc pdesc in + let source_file = loc.Sil.file in + let source_file_str = DB.source_file_to_abs_path source_file in + let dest_file = DB.source_file_in_resdir source_file in + let dest_file_str = DB.filename_to_string dest_file in + let needs_copy = + Node.proc_desc_is_defined pdesc && + Sys.file_exists source_file_str && + (not (Sys.file_exists dest_file_str) || + DB.file_modified_time (DB.filename_from_string source_file_str) > DB.file_modified_time dest_file) in + if needs_copy then + match Utils.copy_file source_file_str dest_file_str with + | Some _ -> () + | None -> L.err "Error cannot create copy of source file %s@." source_file_str in + Node.iter_proc_desc cfg process_proc + +(** Save a cfg into a file *) +let store_cfg_to_file (filename : DB.filename) (save_sources : bool) (cfg: cfg) = + if save_sources then save_source_files cfg; + Serialization.to_file cfg_serializer filename cfg + +(* =============== START of module Procdesc =============== *) +module Procdesc = struct + type t = Node.proc_desc + let compute_distance_to_exit_node = Node.proc_desc_compute_distance_to_exit_node + + type proc_desc_builder = + { cfg : cfg; + name: Procname.t; + is_defined : bool; (** is defined and not just declared *) + proc_attributes : Sil.proc_attributes; + ret_type : Sil.typ; (** return type *) + formals : (string * Sil.typ) list; + locals : (Mangled.t * Sil.typ) list; + captured : (Mangled.t * Sil.typ) list; (** variables captured in an ObjC block *) + loc : Sil.location; + } + + let create (b : proc_desc_builder) = + let open Node in + incr proc_desc_id_counter; + let pdesc = + { + pd_attributes = b.proc_attributes; + pd_id = !proc_desc_id_counter; + pd_name = b.name; + pd_is_defined = b.is_defined; + pd_ret_type = b.ret_type; + pd_formals = b.formals; + pd_locals = b.locals; + pd_captured = b.captured; + pd_nodes =[]; + pd_start_node = dummy (); + pd_exit_node = dummy (); + pd_loc = b.loc; + pd_flags = proc_flags_empty (); + pd_err_log = Errlog.empty (); + } in + pdesc_tbl_add b.cfg b.name pdesc; + pdesc + + let remove = Node.proc_desc_remove + let find_from_name = Node.proc_desc_from_name + let get_attributes = Node.proc_desc_get_attributes + let get_cyclomatic = Node.proc_desc_get_cyclomatic + let get_err_log = Node.proc_desc_get_err_log + let get_exit_node = Node.proc_desc_get_exit_node + let get_flags = Node.proc_desc_get_flags + let get_formals = Node.proc_desc_get_formals + let get_loc = Node.proc_desc_get_loc + let get_locals = Node.proc_desc_get_locals + let get_captured = Node.proc_desc_get_captured + let get_nodes = Node.proc_desc_get_nodes + let get_slope = Node.proc_desc_get_slope + let get_sliced_slope = Node.proc_desc_get_sliced_slope + let get_proc_name = Node.proc_desc_get_proc_name + let get_ret_type = Node.proc_desc_get_ret_type + let get_ret_var pdesc = Sil.mk_pvar Ident.name_return (get_proc_name pdesc) + let get_start_node = Node.proc_desc_get_start_node + let is_defined = Node.proc_desc_is_defined + let iter_nodes = Node.proc_desc_iter_nodes + let iter_calls = Node.proc_desc_iter_calls + let iter_instrs = Node.proc_desc_iter_instrs + let fold_instrs = Node.proc_desc_fold_instrs + let iter_slope = Node.proc_desc_iter_slope + let iter_slope_calls = Node.proc_desc_iter_slope_calls + let iter_slope_range = Node.proc_desc_iter_slope_range + let set_exit_node = Node.proc_desc_set_exit_node + let set_flag = Node.proc_desc_set_flag + let set_start_node = Node.proc_desc_set_start_node + let append_locals = Node.proc_desc_append_locals +end + +(* =============== END of module Procdesc =============== *) + +(** Hash table with nodes as keys. *) +module NodeHash = Hashtbl.Make(Node) + +(** Set of nodes. *) +module NodeSet = Node.NodeSet + +let iter_proc_desc = Node.iter_proc_desc + +(** Iterate over all the types (and subtypes) in the CFG *) +let iter_types = Node.iter_types + +let rec pp_node_list f = function + | [] -> () + | [node] -> Node.pp f node + | node:: nodes -> + F.fprintf f "%a, %a" Node.pp node pp_node_list nodes + +(** Get all the procdescs (defined and declared) *) +let get_all_procs cfg = + let procs = ref [] in + let f pname pdesc = procs := pdesc :: !procs in + iter_proc_desc cfg f; !procs + +(** Get the procedures whose body is defined in this cfg *) +let get_defined_procs cfg = + list_filter Procdesc.is_defined (get_all_procs cfg) + +(** get the function names which should be analyzed before the other ones *) +let get_priority_procnames cfg = + cfg.Node.priority_set + +(** set the function names whose address has been taken in this file *) +let set_procname_priority cfg pname = + cfg.Node.priority_set <- Procname.Set.add pname cfg.Node.priority_set + +(** add instructions to remove temporaries *) +let add_removetemps_instructions cfg = + let all_nodes = Node.get_all_nodes cfg in + let do_node node = + let loc = Node.get_last_loc node in + let temps = Node.get_temps node in + if temps != [] then Node.append_instrs_temps node [Sil.Remove_temps (temps, loc)] [] in + list_iter do_node all_nodes + +(** add instructions to perform abstraction *) +let add_abstraction_instructions cfg = + let converging_node node = (* true if there is a succ node s.t.: it is an exit node, or the succ of >1 nodes *) + let is_exit node = match Node.get_kind node with + | Node.Exit_node _ -> true + | _ -> false in + let succ_nodes = Node.get_succs node in + if list_exists is_exit succ_nodes then true + else match succ_nodes with + | [] -> false + | [h] -> list_length (Node.get_preds h) > 1 + | _ -> false in + let node_requires_abstraction node = + match Node.get_kind node with + | Node.Start_node _ + | Node.Join_node -> + false + | Node.Exit_node _ + | Node.Stmt_node _ + | Node.Prune_node _ + | Node.Skip_node _ -> + converging_node node in + let all_nodes = Node.get_all_nodes cfg in + let do_node node = + let loc = Node.get_last_loc node in + if node_requires_abstraction node then Node.append_instrs_temps node [Sil.Abstract loc] [] in + list_iter do_node all_nodes + +let get_name_of_parameter (curr_f : Procdesc.t) (x, typ) = + Sil.mk_pvar (Mangled.from_string x) (Procdesc.get_proc_name curr_f) + +let get_name_of_local (curr_f : Procdesc.t) (x, typ) = + Sil.mk_pvar x (Procdesc.get_proc_name curr_f) + +(* returns a list of local static variables (ie local variables defined static) in a proposition *) +let get_name_of_objc_static_locals (curr_f : Procdesc.t) p = + let pname = Procname.to_string (Procdesc.get_proc_name curr_f) in + let local_static e = + match e with (* is a local static if it's a global and it has a static local name *) + | Sil.Lvar pvar when (Sil.pvar_is_global pvar) && (Sil.is_static_local_name pname pvar) -> [pvar] + | _ -> [] in + let hpred_local_static hpred = + match hpred with + | Sil.Hpointsto(e, _, _) -> [local_static e] + | _ -> [] in + let vars_sigma = list_map hpred_local_static (Prop.get_sigma p) in + list_flatten (list_flatten vars_sigma) + +(* returns a list of local variables that points to an objc block in a proposition *) +let get_name_of_objc_block_locals p = + let local_blocks e = + match e with + | Sil.Lvar pvar when (Sil.is_block_pvar pvar) -> + [pvar] + | _ -> [] in + let hpred_local_blocks hpred = + match hpred with + | Sil.Hpointsto(e, _, _) -> [local_blocks e] + | _ -> [] in + let vars_sigma = list_map hpred_local_blocks (Prop.get_sigma p) in + list_flatten (list_flatten vars_sigma) + +let remove_abducted_retvars p = + (* compute the heap predicates reachable from the set of seed expressions in [exps] *) + let compute_reachable_hpreds sigma seed_exps = + let rec collect_exps exps = function + | Sil.Eexp (e, _) -> + Sil.ExpSet.add e exps + | Sil.Estruct (flds, _) -> + list_fold_left (fun exps (fld, strexp) -> collect_exps exps strexp) exps flds + | Sil.Earray (_, elems, _) -> + list_fold_left (fun exps (index, strexp) -> collect_exps exps strexp) exps elems in + let rec compute_reachable_hpreds_rec sigma (reach, exps) = + let add_hpred_if_reachable (reach, exps) = function + | Sil.Hpointsto (lhs, rhs, _) as hpred when Sil.ExpSet.mem lhs exps-> + let reach' = Sil.HpredSet.add hpred reach in + let exps' = collect_exps exps rhs in + (reach', exps') + | _ -> reach, exps in + let reach', exps' = list_fold_left add_hpred_if_reachable (reach, exps) sigma in + if (Sil.HpredSet.cardinal reach) = (Sil.HpredSet.cardinal reach') then (reach, exps) + else compute_reachable_hpreds_rec sigma (reach', exps') in + let reach, _ = compute_reachable_hpreds_rec sigma (Sil.HpredSet.empty, seed_exps) in + Sil.HpredSet.elements reach in + (* separate the abducted pvars from the normal ones, deallocate the abducted ones*) + let abducted_pvars, normal_pvars = + list_fold_left + (fun pvars hpred -> + match hpred with + | Sil.Hpointsto (Sil.Lvar pvar, Sil.Eexp (e, _), _) -> + let abducted_pvars, normal_pvars = pvars in + if Sil.pvar_is_abducted_retvar pvar then pvar :: abducted_pvars, normal_pvars + else abducted_pvars, pvar :: normal_pvars + | _ -> pvars) + ([], []) + (Prop.get_sigma p) in + let _, p' = Prop.deallocate_stack_vars p abducted_pvars in + let normal_pvar_set = + list_fold_left + (fun normal_pvar_set pvar -> Sil.ExpSet.add (Sil.Lvar pvar) normal_pvar_set) + Sil.ExpSet.empty + normal_pvars in + (* walk forward from non-abducted pvars, keep everything reachable. remove everything else *) + let sigma' = compute_reachable_hpreds (Prop.get_sigma p') normal_pvar_set in + let _ = Prop.normalize (Prop.replace_sigma sigma' p) in + let is_abducted_retvar_hpred hpred = + match hpred with + | Sil.Hpointsto (Sil.Lvar pvar, _, _) -> Sil.pvar_is_abducted_retvar pvar + | _ -> false in + let sigma' = list_filter (fun hpred -> not (is_abducted_retvar_hpred hpred)) (Prop.get_sigma p) in + Prop.normalize (Prop.replace_sigma sigma' p) + +let remove_locals (curr_f : Procdesc.t) p = + let names_of_locals = list_map (get_name_of_local curr_f) (Procdesc.get_locals curr_f) in + let names_of_locals' = match !Sil.curr_language with + | Sil.C_CPP -> (* in ObjC to deal with block we need to remove static locals *) + let names_of_static_locals = get_name_of_objc_static_locals curr_f p in + let names_of_block_locals = get_name_of_objc_block_locals p in + names_of_block_locals @ names_of_locals @ names_of_static_locals + | _ -> names_of_locals in + let removed, p' = Prop.deallocate_stack_vars p names_of_locals' in + (removed, if !Config.angelic_execution then remove_abducted_retvars p' else p') + +let remove_formals (curr_f : Procdesc.t) p = + let names_of_formals = list_map (get_name_of_parameter curr_f) (Procdesc.get_formals curr_f) in + Prop.deallocate_stack_vars p names_of_formals + +(** remove the return variable from the prop *) +let remove_ret (curr_f : Procdesc.t) (p: Prop.normal Prop.t) = + let pname = Procdesc.get_proc_name curr_f in + let name_of_ret = Procdesc.get_ret_var curr_f in + let _, p' = Prop.deallocate_stack_vars p [(Sil.pvar_to_callee pname name_of_ret)] in + p' + +(** remove locals and return variable from the prop *) +let remove_locals_ret (curr_f : Procdesc.t) p = + snd (remove_locals curr_f (remove_ret curr_f p)) + +(** Remove locals and formal parameters from the prop. +Return the list of stack variables whose address was still present after deallocation. *) +let remove_locals_formals (curr_f : Procdesc.t) p = + let pvars1, p1 = remove_formals curr_f p in + let pvars2, p2 = remove_locals curr_f p1 in + pvars1 @ pvars2, p2 + +(** remove seed vars from a prop *) +let remove_seed_vars (prop: 'a Prop.t) : Prop.normal Prop.t = + let hpred_not_seed = function + | Sil.Hpointsto(Sil.Lvar pv, _, _) -> not (Sil.pvar_is_seed pv) + | _ -> true in + let sigma = Prop.get_sigma prop in + let sigma' = list_filter hpred_not_seed sigma in + Prop.normalize (Prop.replace_sigma sigma' prop) + +(** checks whether a cfg is connected or not *) +let check_cfg_connectedness cfg = + let is_exit_node n = + match Node.get_kind n with + | Node.Exit_node _ -> true + | _ -> false in + let broken_node n = + let succs = Node.get_succs n in + let preds = Node.get_preds n in + match Node.get_kind n with + | Node.Start_node _ -> (list_length succs = 0) || (list_length preds > 0) + | Node.Exit_node _ -> (list_length succs > 0) || (list_length preds = 0) + | Node.Stmt_node _ | Node.Prune_node _ + | Node.Skip_node _ -> (list_length succs = 0) || (list_length preds = 0) + | Node.Join_node -> + (* Join node has the exception that it may be without predecessors and pointing to an exit node *) + (* if the if brances end with a return *) + (match succs with + | [n'] when is_exit_node n' -> false + | _ -> (list_length preds = 0)) in + let do_pdesc pd = + let pname = Procname.to_string (Procdesc.get_proc_name pd) in + let nodes = Procdesc.get_nodes pd in + let broken = list_exists broken_node nodes in + if broken then + L.out "\n ***BROKEN CFG: '%s'\n" pname + else + L.out "\n ***CONNECTED CFG: '%s'\n" pname in + let pdescs = get_all_procs cfg in + list_iter do_pdesc pdescs + +(** Given a mangled name of a block return its procdesc if exists*) +let get_block_pdesc cfg block = + let pdescs = get_defined_procs cfg in + let is_block_pdesc pd = + let name = Procdesc.get_proc_name pd in + (Procname.to_string name) = (Mangled.to_string block) in + try + let block_pdesc = list_find is_block_pdesc pdescs in + Some block_pdesc + with Not_found -> None + +(** Removes seeds variables from a prop corresponding to captured variables in an objc block *) +let remove_seed_captured_vars_block captured_vars prop = + let is_captured pname vn = Mangled.equal pname vn in + let hpred_seed_captured = function + | Sil.Hpointsto(Sil.Lvar pv, _, _) -> + let pname = Sil.pvar_get_name pv in + (Sil.pvar_is_seed pv) && (list_mem is_captured pname captured_vars) + | _ -> false in + let sigma = Prop.get_sigma prop in + let sigma' = list_filter (fun hpred -> not (hpred_seed_captured hpred)) sigma in + Prop.normalize (Prop.replace_sigma sigma' prop) diff --git a/infer/src/backend/cfg.mli b/infer/src/backend/cfg.mli new file mode 100644 index 000000000..eb65748e6 --- /dev/null +++ b/infer/src/backend/cfg.mli @@ -0,0 +1,325 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Control Flow Graph for Interprocedural Analysis *) + +open Utils + +(** {2 ADT node and proc_desc} *) + +type node +type cfg + +(** Load a cfg from a file *) +val load_cfg_from_file: DB.filename -> cfg option + +(** Save a cfg into a file, and save a copy of the source files if the boolean is true *) +val store_cfg_to_file: DB.filename -> bool -> cfg -> unit + +(** proc description *) +module Procdesc : sig +(** proc description *) + type t + + (** Compute the distance of each node to the exit node, if not computed already *) + val compute_distance_to_exit_node : t -> unit + + type proc_desc_builder = + { cfg : cfg; + name: Procname.t; + is_defined : bool; (** is defined and not just declared *) + proc_attributes : Sil.proc_attributes; + ret_type : Sil.typ; (** return type *) + formals : (string * Sil.typ) list; + locals : (Mangled.t * Sil.typ) list; + captured : (Mangled.t * Sil.typ) list; (** variables captured in an ObjC block *) + loc : Sil.location; + } + + (** Create a procdesc *) + val create : proc_desc_builder -> t + + (** [remove cfg name remove_nodes] remove the procdesc [name] from the control flow graph [cfg]. *) + (** It also removes all the nodes from the procedure from the cfg if remove_nodes is true *) + val remove: cfg -> Procname.t -> bool -> unit + + (** Find the procdesc given the proc name. Return None if not found. *) + val find_from_name : cfg -> Procname.t -> t option + + (** Get the attributes of the procedure. *) + val get_attributes : t -> Sil.proc_attributes + + (** Get the cyclomatic complexity for the procedure *) + val get_cyclomatic : t -> int + + val get_err_log : t -> Errlog.t + + val get_exit_node : t -> node + + (** Get flags for the proc desc *) + val get_flags : t -> proc_flags + + (** Return name and type of formal parameters *) + val get_formals : t -> (string * Sil.typ) list + + (** Return loc information for the procedure *) + val get_loc : t -> Sil.location + + (** Return name and type of local variables *) + val get_locals : t -> (Mangled.t * Sil.typ) list + + (** Return name and type of block's captured variables *) + val get_captured : t -> (Mangled.t * Sil.typ) list + + val get_nodes : t -> node list + + (** Get the procedure's nodes up until the first branching *) + val get_slope : t -> node list + + (** Get the sliced procedure's nodes up until the first branching *) + val get_sliced_slope : t -> (node -> bool) -> node list + + val get_proc_name : t -> Procname.t + + (** Return the return type of the procedure and type string *) + val get_ret_type : t -> Sil.typ + + val get_ret_var : t -> Sil.pvar + + val get_start_node : t -> node + + (** Return [true] iff the procedure is defined, and not just declared *) + val is_defined : t -> bool + + (** iterate over all the nodes of a procedure *) + val iter_nodes : (node -> unit) -> t -> unit + + (** iterate over the calls from the procedure: (callee,location) pairs *) + val iter_calls : ((Procname.t * Sil.location) -> unit) -> t -> unit + + (** iterate over all nodes and their instructions *) + val iter_instrs : (node -> Sil.instr -> unit) -> t -> unit + + (** fold over all nodes and their instructions *) + val fold_instrs : ('a -> node -> Sil.instr -> 'a) -> 'a -> t -> 'a + + (** iterate over all nodes until we reach a branching structure *) + val iter_slope : (node -> unit) -> t -> unit + + (** iterate over all calls until we reach a branching structure *) + val iter_slope_calls : (Procname.t -> unit) -> t -> unit + + (** iterate between two nodes or until we reach a branching structure *) + val iter_slope_range : (node -> unit) -> t -> node -> node -> unit + + val set_exit_node : t -> node -> unit + + (** Set a flag for the proc desc *) + val set_flag : t -> string -> string -> unit + + val set_start_node : t -> node -> unit + + (** append a list of new local variables to the existing list of local variables *) + val append_locals : t -> (Mangled.t * Sil.typ) list -> unit +end + +(** node of the control flow graph *) +module Node : sig + type t = node (** type of nodes *) + + (** kind of cfg node *) + type nodekind = + | Start_node of Procdesc.t + | Exit_node of Procdesc.t + | Stmt_node of string + | Join_node + | Prune_node of bool * Sil.if_kind * string (** (true/false branch, if_kind, comment) *) + | Skip_node of string + + (** kind of Stmt_node for an exception handler. *) + val exn_handler_kind : nodekind + + (** kind of Stmt_node for an exceptions sink. *) + val exn_sink_kind : nodekind + + (** Append the instructions and temporaries to the list of instructions to execute *) + val append_instrs_temps : t -> Sil.instr list -> Ident.t list -> unit + + (** Add the instructions and temporaries at the beginning of the list of instructions to execute *) + val prepend_instrs_temps : t -> Sil.instr list -> Ident.t list -> unit + + (** Replace the instructions to be executed. *) + val replace_instrs : t -> Sil.instr list -> unit + + (** Add declarations for local variables and return variable to the node *) + val add_locals_ret_declaration : t -> (Mangled.t * Sil.typ) list -> unit + + (** restrict the cfg to the given enabled (active and not shadowed) procedures *) + val cfg_restrict_enabled : cfg -> DB.source_file -> Procname.Set.t option -> unit + + (** Compare two nodes *) + val compare : t -> t -> int + + (** [create cfg loc kind instrs proc_desc temps] create a new cfg node + with the given location, kind, list of instructions, + procdesc and list of temporary variables *) + val create : cfg -> Sil.location -> nodekind -> Sil.instr list -> Procdesc.t -> Ident.t list -> t + + (** create a new empty cfg *) + val create_cfg : unit -> cfg + + (** Dump extended instructions for the node *) + val d_instrs : sub_instrs: bool -> Sil.instr option -> t -> unit + + (** Create a dummy node *) + val dummy : unit -> t + + (** Check if two nodes are equal *) + val equal : t -> t -> bool + + (** Get all the nodes *) + val get_all_nodes : cfg -> t list + + (** Get the (after/before) dead program variables. After/before indicated with the true/false flag. *) + val get_dead_pvars: t -> bool -> Sil.pvar list + + (** Get the distance to the exit node, if it has been computed *) + val get_distance_to_exit: t -> int option + + (** Return a description of the node *) + val get_description : Utils.printenv -> t -> string + + (** Get the exception nodes from the current node *) + val get_exn : t -> t list + + (** Get the unique id of the node *) + val get_id : t -> int + + (** Get the source location of the node *) + val get_loc : t -> Sil.location + + (** Get the source location of the last instruction in the node *) + val get_last_loc : t -> Sil.location + + (** Get the kind of the current node *) + val get_kind : t -> nodekind + + (** Get the predecessor nodes of the current node *) + val get_preds : t -> t list + + (** Get a list of unique nodes until the first branch starting from a node with subsequent applications of a generator function *) + val get_generated_slope : t -> (t -> t list) -> t list + + (** Get the proc desc associated to the node *) + val get_proc_desc : t -> Procdesc.t + + (** Get the instructions to be executed *) + val get_instrs : t -> Sil.instr list + + (** Get the list of callee procnames from the node *) + val get_callees : t -> Procname.t list + + (** Get the successor nodes of the current node *) + val get_succs : t -> t list + + (** Get the successor nodes of a node where the given predicate evaluates to true *) + val get_sliced_succs : t -> (t -> bool) -> t list + + (** Get the predecessor nodes of a node where the given predicate evaluates to true *) + val get_sliced_preds : t -> (t -> bool) -> t list + + (** Get the temporary variables introduced for the instructions stored in the node *) + val get_temps: t -> Ident.t list + + (** Hash function for nodes *) + val hash : t -> int + + (** Comparison for node kind *) + val kind_compare : nodekind -> nodekind -> int + + (** Pretty print the node *) + val pp : Format.formatter -> t -> unit + + (** Print extended instructions for the node, highlighting the given subinstruction if present *) + val pp_instr : printenv -> sub_instrs: bool -> Sil.instr option -> Format.formatter -> t -> unit + + (** Replace the instructions to be executed. *) + val replace_instrs : t -> Sil.instr list -> unit + + (** Set the (after/before) dead program variables. After/before indicated with the true/false flag. *) + val set_dead_pvars : t -> bool -> Sil.pvar list -> unit + + (** Set the node kind *) + val set_kind : t -> nodekind -> unit + + (** Set the source location of the node *) + val set_loc : t -> Sil.location -> unit + + (** Set the proc desc associated to the node *) + val set_proc_desc : t -> Procdesc.t -> unit + + (** Set the successor nodes and exception nodes, and build predecessor links *) + val set_succs_exn : t -> t list -> t list -> unit + + (** Set the temporary variables *) + val set_temps : t -> Ident.t list -> unit +end + +(** Hash table with nodes as keys. *) +module NodeHash : Hashtbl.S with type key = Node.t + +(** Set of nodes. *) +module NodeSet : Set.S with type elt = Node.t + +val pp_node_list : Format.formatter -> Node.t list -> unit + +(** {2 Functions for manipulating an interprocedural CFG} *) + +(** Iterate over all the procdesc's *) +val iter_proc_desc : cfg -> (Procname.t -> Procdesc.t -> unit) -> unit + +(** Iterate over all the types (and subtypes) in the CFG *) +val iter_types : cfg -> (Sil.typ -> unit) -> unit + +(** Get all the procedures (defined and declared) *) +val get_all_procs : cfg -> Procdesc.t list + +(** Get the procedures whose body is defined in this cfg *) +val get_defined_procs : cfg -> Procdesc.t list + +(** get the function names which should be analyzed before the other ones *) +val get_priority_procnames : cfg -> Procname.Set.t + +(** set the function names whose address has been taken in this file *) +val set_procname_priority : cfg -> Procname.t -> unit + +(** add instructions to remove temporaries *) +val add_removetemps_instructions : cfg -> unit + +(** add instructions to perform abstraction *) +val add_abstraction_instructions : cfg -> unit + +(** remove the return variable from the prop *) +val remove_ret : Procdesc.t -> Prop.normal Prop.t -> Prop.normal Prop.t + +(** remove locals and return variable from the prop *) +val remove_locals_ret : Procdesc.t -> Prop.normal Prop.t -> Prop.normal Prop.t + +(** Deallocate the stack variables in [pvars], and replace them by normal variables. +Return the list of stack variables whose address was still present after deallocation. *) +val remove_locals_formals : Procdesc.t -> Prop.normal Prop.t -> Sil.pvar list * Prop.normal Prop.t + +(** remove seed vars from a prop *) +val remove_seed_vars : 'a Prop.t -> Prop.normal Prop.t + +(** checks whether a cfg is connected or not *) +val check_cfg_connectedness : cfg -> unit + +(** Given a mangled name of a block return its procdesc if exists*) +val get_block_pdesc : cfg -> Mangled.t -> Procdesc.t option + +(** Removes seeds variables from a prop corresponding to captured variables in an objc block *) +val remove_seed_captured_vars_block : Mangled.t list -> Prop.normal Prop.t -> Prop.normal Prop.t \ No newline at end of file diff --git a/infer/src/backend/cg.ml b/infer/src/backend/cg.ml new file mode 100644 index 000000000..75aaf23ef --- /dev/null +++ b/infer/src/backend/cg.ml @@ -0,0 +1,339 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Module for call graphs *) + +module L = Logging +module F = Format +open Utils + +let pp_nodeset fmt set = + let f node = F.fprintf fmt "%a@ " Procname.pp node in + Procname.Set.iter f set + +type node = Procname.t + +type in_out_calls = + { in_calls: int; (** total number of in calls transitively *) + out_calls: int (** total number of out calls transitively *) + } + +type node_info = + { mutable defined : bool; + mutable parents: Procname.Set.t; + mutable children: Procname.Set.t; + mutable ancestors : Procname.Set.t option ; (** ancestors are computed lazily *) + mutable heirs : Procname.Set.t option ; (** heirs are computed lazily *) + mutable recursive_dependents : Procname.Set.t option ; (** recursive dependents are computed lazily *) + mutable in_out_calls : in_out_calls option; (** calls are computed lazily *) + } + +(** Type for call graph *) +type t = + { + mutable source : DB.source_file; (** path for the source file *) + mutable nLOC : int; (** number of LOC *) + node_map : node_info Procname.Hash.t (** map from node to node_info *) + } + +let create () = + { source = !DB.current_source; + nLOC = !Config.nLOC; + node_map = Procname.Hash.create 3 } + +let _add_node g n defined = + try + let info = Procname.Hash.find g.node_map n in + if defined then info.defined <- true + with Not_found -> + let info = + { defined = defined; + parents = Procname.Set.empty; + children = Procname.Set.empty; + ancestors = None; + heirs = None; + recursive_dependents = None; + in_out_calls = None } in + Procname.Hash.add g.node_map n info + +let add_node g n = + _add_node g n true + +(** Compute the ancestors of the node, if not already computed *) +let compute_ancestors g node = + let todo = ref (Procname.Set.singleton node) in + let seen = ref Procname.Set.empty in + let result = ref Procname.Set.empty in + while not (Procname.Set.is_empty !todo) do + let current = Procname.Set.choose !todo in + todo := Procname.Set.remove current !todo; + if not (Procname.Set.mem current !seen) then + begin + seen := Procname.Set.add current !seen; + let info = Procname.Hash.find g current in + match info.ancestors with + | Some ancestors -> + result := Procname.Set.union !result ancestors + | None -> + result := Procname.Set.union !result info.parents; + todo := Procname.Set.union !todo info.parents + end + done; + !result + +(** Compute the heirs of the node, if not already computed *) +let compute_heirs g node = + let todo = ref (Procname.Set.singleton node) in + let seen = ref Procname.Set.empty in + let result = ref Procname.Set.empty in + while not (Procname.Set.is_empty !todo) do + let current = Procname.Set.choose !todo in + todo := Procname.Set.remove current !todo; + if not (Procname.Set.mem current !seen) then + begin + seen := Procname.Set.add current !seen; + let info = Procname.Hash.find g current in + match info.heirs with + | Some heirs -> + result := Procname.Set.union !result heirs + | None -> + result := Procname.Set.union !result info.children; + todo := Procname.Set.union !todo info.children + end + done; + !result + +(** Compute the ancestors of the node, if not pre-computed already *) +let get_ancestors (g: t) node = + let info = Procname.Hash.find g.node_map node in + match info.ancestors with + | None -> + let ancestors = compute_ancestors g.node_map node in + info.ancestors <- Some ancestors; + let size = Procname.Set.cardinal ancestors in + if size > 1000 then L.err "%a has %d ancestors@." Procname.pp node size; + ancestors + | Some ancestors -> ancestors + +(** Compute the heirs of the node, if not pre-computed already *) +let get_heirs (g: t) node = + let info = Procname.Hash.find g.node_map node in + match info.heirs with + | None -> + let heirs = compute_heirs g.node_map node in + info.heirs <- Some heirs; + let size = Procname.Set.cardinal heirs in + if size > 1000 then L.err "%a has %d heirs@." Procname.pp node size; + heirs + | Some heirs -> heirs + +let node_defined (g: t) n = + try + let info = Procname.Hash.find g.node_map n in + info.defined + with Not_found -> false + +let add_edge g nfrom nto = + _add_node g nfrom false; + _add_node g nto false; + let info_from = Procname.Hash.find g.node_map nfrom in + let info_to = Procname.Hash.find g.node_map nto in + info_from.children <- Procname.Set.add nto info_from.children; + info_to.parents <- Procname.Set.add nfrom info_to.parents + +(** iterate over the elements of a node_map in node order *) +let node_map_iter f g = + let table = ref [] in + Procname.Hash.iter (fun node info -> table := (node, info) :: !table) g.node_map; + let cmp ((n1: Procname.t), _) ((n2: Procname.t), _) = Procname.compare n1 n2 in + list_iter (fun (n, info) -> f n info) (list_sort cmp !table) + +(** if not None, restrict defined nodes to the given set *) +let restrict_defined (g: t) (nodeset_opt: Procname.Set.t option) = + match nodeset_opt with + | None -> () + | Some nodeset -> + let f node info = + if not (Procname.Set.mem node nodeset) then info.defined <- false in + node_map_iter f g + +let get_nodes (g: t) = + let nodes = ref Procname.Set.empty in + let f node info = + nodes := Procname.Set.add node !nodes in + node_map_iter f g; + !nodes + +let map_option f l = + let lo = list_filter (function | Some _ -> true | None -> false) (list_map f l) in + list_map (function Some p -> p | None -> assert false) lo + +let compute_calls g node = + { in_calls = Procname.Set.cardinal (get_ancestors g node); + out_calls = Procname.Set.cardinal (get_heirs g node) } + +(** Compute the calls of the node, if not pre-computed already *) +let get_calls (g: t) node = + let info = Procname.Hash.find g.node_map node in + match info.in_out_calls with + | None -> + let calls = compute_calls g node in + info.in_out_calls <- Some calls; + calls + | Some calls -> calls + +let get_all_nodes (g: t) = + let nodes = Procname.Set.elements (get_nodes g) in + list_map (fun node -> (node, get_calls g node)) nodes + +let get_nodes_and_calls (g: t) = + list_filter (fun (n, calls) -> node_defined g n) (get_all_nodes g) + +let node_get_num_ancestors g n = + (n, Procname.Set.cardinal (get_ancestors g n)) + +let get_edges (g: t) : ((node * int) * (node * int)) list = + let edges = ref [] in + let f node info = + Procname.Set.iter (fun nto -> edges := (node_get_num_ancestors g node, node_get_num_ancestors g nto)::!edges) info.children in + node_map_iter f g; + !edges + +(** Return all the children of [n], whether defined or not *) +let get_all_children (g: t) n = + (Procname.Hash.find g.node_map n).children + +(** Return the children of [n] which are defined *) +let get_defined_children (g: t) n = + Procname.Set.filter (node_defined g) (get_all_children g n) + +(** Return the parents of [n] *) +let get_parents (g: t) n = + (Procname.Hash.find g.node_map n).parents + +(** Return true if [n] is recursive *) +let is_recursive (g: t) n = + Procname.Set.mem n (get_ancestors g n) + +(** [call_recursively source dest] returns [true] if function [source] recursively calls function [dest] *) +let calls_recursively (g: t) source dest = + Procname.Set.mem source (get_ancestors g dest) + +(** Return the children of [n] which are not heirs of [n] *) +let get_nonrecursive_dependents (g: t) n = + let is_not_recursive pn = not (Procname.Set.mem pn (get_ancestors g n)) in + let res0 = Procname.Set.filter is_not_recursive (get_all_children g n) in + let res = Procname.Set.filter (node_defined g) res0 in + (* L.err "Nonrecursive dependents of %s: %a@\n" n pp_nodeset res; *) + res + +(** Return the ancestors of [n] which are also heirs of [n] *) +let compute_recursive_dependents (g: t) n = + let reached_from_n pn = Procname.Set.mem n (get_ancestors g pn) in + let res0 = Procname.Set.filter reached_from_n (get_ancestors g n) in + let res = Procname.Set.filter (node_defined g) res0 in + (* L.err "Recursive dependents of %s: %a@\n" n pp_nodeset res; *) + res + +(** Compute the ancestors of [n] which are also heirs of [n], if not pre-computed already *) +let get_recursive_dependents (g: t) n = + let info = Procname.Hash.find g.node_map n in + match info.recursive_dependents with + | None -> + let recursive_dependents = compute_recursive_dependents g n in + info.recursive_dependents <- Some recursive_dependents; + recursive_dependents + | Some recursive_dependents -> recursive_dependents + +(** Return the nodes dependent on [n] *) +let get_dependents (g: t) n = + Procname.Set.union (get_nonrecursive_dependents g n) (get_recursive_dependents g n) + +(** Return all the nodes with their defined children *) +let get_nodes_and_defined_children (g: t) = + let nodes = ref Procname.Set.empty in + node_map_iter (fun n info -> if info.defined then nodes := Procname.Set.add n !nodes) g; + let nodes_list = Procname.Set.elements !nodes in + list_map (fun n -> (n, get_defined_children g n)) nodes_list + +type nodes_and_edges = (node * bool) list * (node * node) list + +(** Return the list of nodes, with defined flag, and the list of edges *) +let get_nodes_and_edges (g: t) : nodes_and_edges = + let nodes = ref [] in + let edges = ref [] in + let do_children node info nto = + edges := (node, nto) :: !edges in + let f node info = + nodes := (node, info.defined) :: !nodes; + Procname.Set.iter (do_children node info) info.children in + node_map_iter f g; + (!nodes, !edges) + +(** Return the list of nodes which are defined *) +let get_defined_nodes (g: t) = + let (nodes, _) = get_nodes_and_edges g in + list_map fst (list_filter (fun pair -> snd pair) nodes) + +(** Return the path of the source file *) +let get_source (g: t) = + g.source + +(** Return the number of LOC of the source file *) +let get_nLOC (g: t) = + g.nLOC + +(** [extend cg1 gc2] extends [cg1] in place with nodes and edges from [gc2]; undefined nodes become defined if at least one side is. *) +let extend cg_old cg_new = + let nodes, edges = get_nodes_and_edges cg_new in + list_iter (fun (node, defined) -> _add_node cg_old node defined) nodes; + list_iter (fun (nfrom, nto) -> add_edge cg_old nfrom nto) edges + +(** Begin support for serialization *) + +let callgraph_serializer : (DB.source_file * int * nodes_and_edges) Serialization.serializer = Serialization.create_serializer Serialization.cg_key + +(** Load a call graph from a file *) +let load_from_file (filename : DB.filename) : t option = + let g = create () in + match Serialization.from_file callgraph_serializer filename with + | None -> None + | Some (source, nLOC, (nodes, edges)) -> + list_iter (fun (node, defined) -> if defined then add_node g node) nodes; + list_iter (fun (nfrom, nto) -> add_edge g nfrom nto) edges; + g.source <- source; + g.nLOC <- nLOC; + Some g + +(** Save a call graph into a file *) +let store_to_file (filename : DB.filename) (call_graph : t) = + Serialization.to_file callgraph_serializer filename (call_graph.source, call_graph.nLOC, (get_nodes_and_edges call_graph)) + +let pp_graph_dotty get_specs (g: t) fmt = + let nodes_with_calls = get_all_nodes g in + let num_specs n = try list_length (get_specs n) with exn when exn_not_timeout exn -> - 1 in + let get_color (n, calls) = + if num_specs n != 0 then "green" else "red" in + let get_shape (n, calls) = + if node_defined g n then "box" else "diamond" in + let pp_node fmt (n, calls) = + F.fprintf fmt "\"%s\"" (Procname.to_filename n) in + let pp_node_label fmt (n, calls) = + F.fprintf fmt "\"%a | calls=%d %d | specs=%d)\"" Procname.pp n calls.in_calls calls.out_calls (num_specs n) in + F.fprintf fmt "digraph {@\n"; + list_iter (fun nc -> F.fprintf fmt "%a [shape=box,label=%a,color=%s,shape=%s]@\n" pp_node nc pp_node_label nc (get_color nc) (get_shape nc)) nodes_with_calls; + list_iter (fun (s, d) -> F.fprintf fmt "%a -> %a@\n" pp_node s pp_node d) (get_edges g); + F.fprintf fmt "}@." + +(** Print the current call graph as a dotty file. If the filename is [None], use the current file dir inside the DB dir. *) +let save_call_graph_dotty fname_opt get_specs (g: t) = + let fname_dot = match fname_opt with + | None -> DB.Results_dir.path_to_filename DB.Results_dir.Abs_source_dir ["call_graph.dot"] + | Some fname -> fname in + let outc = open_out (DB.filename_to_string fname_dot) in + let fmt = F.formatter_of_out_channel outc in + pp_graph_dotty get_specs g fmt; + close_out outc diff --git a/infer/src/backend/cg.mli b/infer/src/backend/cg.mli new file mode 100644 index 000000000..6c024177d --- /dev/null +++ b/infer/src/backend/cg.mli @@ -0,0 +1,48 @@ +(* +* Copyright (c) 2009-2013 Monoidics ltd. +* Copyright (c) 2013- Facebook. +* All rights reserved. +*) + +(** Module for call graphs *) + +open Utils + +type in_out_calls = + { in_calls: int; (** total number of in calls transitively *) + out_calls: int (** total number of out calls transitively *) + } + +type t (** the type of a call graph *) + +(** A call graph consists of a set of nodes (Procname.t), and edges between them. +A node can be defined or undefined (to represent whether we have code for it). +In an edge from [n1] to [n2], indicating that [n1] calls [n2], [n1] is the parent and [n2] is the child. +Node [n1] is dependent on [n2] if there is a path from [n1] to [n2] using the child relationship. *) + +(** [add_edge cg f t] adds an edge from [f] to [t] in the call graph [cg]. The nodes are also added as undefined, unless already present. *) +val add_edge : t -> Procname.t -> Procname.t -> unit + +val add_node : t -> Procname.t -> unit (** Add a node to the call graph as defined *) +val calls_recursively: t -> Procname.t -> Procname.t -> bool (** [calls_recursively g source dest] returns [true] if function [source] recursively calls function [dest] *) +val create : unit -> t (** Create an empty call graph *) +val extend : t -> t -> unit (** [extend cg1 gc2] extends [cg1] in place with nodes and edges from [gc2]; undefined nodes become defined if at least one side is. *) +val get_all_children : t -> Procname.t -> Procname.Set.t (** Return all the children of [n], whether defined or not *) +val get_ancestors : t -> Procname.t -> Procname.Set.t (** Compute the ancestors of the node, if not pre-computed already *) +val get_calls : t -> Procname.t -> in_out_calls (** Return the in/out calls of the node *) +val get_defined_nodes : t -> Procname.t list (** Return the list of nodes which are defined *) +val get_defined_children: t -> Procname.t -> Procname.Set.t (** Return the children of [n] which are defined *) +val get_dependents: t -> Procname.t -> Procname.Set.t (** Return the nodes dependent on [n] *) +val get_nLOC: t -> int (** Return the number of LOC of the source file *) +val get_nodes_and_calls : t -> (Procname.t * in_out_calls) list (** Return the list of nodes with calls *) +val get_nodes_and_defined_children : t -> (Procname.t * Procname.Set.t) list (** Return all the nodes with their defined children *) +val get_nodes_and_edges : t -> (Procname.t * bool) list * (Procname.t * Procname.t) list (** Return the list of nodes, with defined flag, and the list of edges *) +val get_nonrecursive_dependents : t -> Procname.t -> Procname.Set.t (** Return the children of [n] which are not heirs of [n] and are defined *) +val get_parents : t -> Procname.t -> Procname.Set.t (** Return the parents of [n] *) +val get_recursive_dependents: t -> Procname.t -> Procname.Set.t (** Return the ancestors of [n] which are also heirs of [n] *) +val get_source : t -> DB.source_file (** Return the path of the source file *) +val load_from_file : DB.filename -> t option (** Load a call graph from a file *) +val node_defined : t -> Procname.t -> bool (** Returns true if the node is defined *) +val restrict_defined : t -> Procname.Set.t option -> unit (** if not None, restrict defined nodes to the given set *) +val save_call_graph_dotty : DB.filename option -> (Procname.t -> 'a list) -> t -> unit (** Print the current call graph as a dotty file. If the filename is [None], use the current file dir inside the DB dir. *) +val store_to_file : DB.filename -> t -> unit (** Save a call graph into a file *) diff --git a/infer/src/backend/config.ml b/infer/src/backend/config.ml new file mode 100644 index 000000000..12bd138b4 --- /dev/null +++ b/infer/src/backend/config.ml @@ -0,0 +1,377 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +module F = Format;; + +(* Initialization *) + +F.set_margin 100 + +let set_minor_heap_size nMb = (* increase the minor heap size to speed up gc *) + let ctrl = Gc.get () in + let oneMb = 1048576 in + let new_size = max ctrl.Gc.minor_heap_size (nMb * oneMb) + in Gc.set { ctrl with Gc.minor_heap_size = new_size };; + +set_minor_heap_size 1 + +let from_env_variable var_name = + try + let _ = Sys.getenv var_name in true + with Not_found -> false + +let specs_dir_name = "specs" +let captured_dir_name = "captured" +let sources_dir_name = "sources" + +let default_in_zip_results_dir = "infer" + +let default_buck_out = "buck-out" + +let global_tenv_filename = "global.tenv" + +(** List of paths to the directories containing specs for library functions. *) +let specs_library = ref [] + +(** path to lib/specs to retrieve the default models *) +let models_dir = + let bin_dir = Filename.dirname Sys.executable_name in + let lib_dir = Filename.concat (Filename.concat bin_dir Filename.parent_dir_name) "lib" in + let lib_specs_dir = Filename.concat lib_dir specs_dir_name in + lib_specs_dir + +module JarCache = +struct + let infer_cache : string option ref = ref None + + let mkdir s = + try + Unix.mkdir s 0o700; + true + with Unix.Unix_error _ -> false + + let extract_specs dest_dir jarfile = + let zip_channel = Zip.open_in jarfile in + let entries = Zip.entries zip_channel in + let extract_entry entry = + let dest_file = Filename.concat dest_dir (Filename.basename entry.Zip.filename) in + if Filename.check_suffix entry.Zip.filename ".specs" + then Zip.copy_entry_to_file zip_channel entry dest_file in + List.iter extract_entry entries; + Zip.close_in zip_channel + + let handle_jar jarfile = + match !infer_cache with + | Some cache_dir -> + let basename = Filename.basename jarfile in + let key = basename ^ CRC.crc16 jarfile in + let key_dir = Filename.concat cache_dir key in + + if (mkdir key_dir) + then extract_specs key_dir jarfile; + + specs_library := !specs_library @ [key_dir] + | None -> () +end + +type zip_library = { + zip_filename: string; + zip_channel: Zip.in_file; + models: bool; +} + +let zip_filename zip_library = + zip_library.zip_filename + +let zip_channel zip_library = + zip_library.zip_channel + +(** list of the zip files to search for specs files *) +let zip_libraries : zip_library list ref = ref [] + +let add_zip_library zip_filename = + if !JarCache.infer_cache != None + then + JarCache.handle_jar zip_filename + else + (* The order matters, the jar files should be added following the order *) + (* specs files should be searched in them *) + zip_libraries := !zip_libraries @ [{ zip_filename = zip_filename; zip_channel = Zip.open_in zip_filename; models = false }] + +let add_models zip_filename = + zip_libraries := !zip_libraries @ [{ zip_filename = zip_filename; zip_channel = Zip.open_in zip_filename; models = true }] + +let project_root : string option ref = ref None + +(** FLAGS AND GLOBAL VARIABLES *) + +(** Flag for abstracting fields of structs +0 = no +1 = forget some fields during matching (and so lseg abstraction) *) +let abs_struct = ref 1 + +(** Flag for abstracting numerical values +0 = no abstraction. +1 = evaluate all expressions abstractly. +2 = 1 + abstract constant integer values during join. +*) +let abs_val = ref 2 + +(** if true, completely ignore the possibility that errors can be caused by unknown procedures +* during the symbolic execution phase *) +let angelic_execution = ref true + +(** Flag for forgetting memory leak +false = no +true = forget leaked memory cells during abstraction +*) +let allowleak = ref false + +(** Flag for ignoring arrays and pointer arithmetic. +0 = treats both features soundly. +1 = assumes that the size of every array is infinite. +2 = assumes that all heap dereferences via array indexing and pointer arithmetic are correct. +*) +let array_level = ref 0 + +(** Check whether to report Analysis_stops message in user mode *) +let analysis_stops = ref false + +type os_type = Unix | Win32 | Cygwin + +let os_type = match Sys.os_type with + | "Win32" -> Win32 + | "Cygwin" -> Cygwin + | _ -> Unix + +(** default path of the project results directory *) +let default_results_dir = + let dir = match os_type with + | Win32 -> Sys.getenv "TEMP" + | Unix | Cygwin -> "/tmp" in + Filename.concat dir "infer_results_dir" + +(** If true shows internal exceptions*) +let developer_mode = ref false + +(** flag: dotty output filename **) +let dotty_output = ref "icfg.dot" + +(** command line option to activate the eradicate checker. *) +let eradicate = ref false + +(** should the checkers be run? *) +let checkers_enabled () = not !eradicate + +(** Flag for footprint discovery mode *) +let footprint = ref true + +(** Set in the middle of forcing delayed prints *) +let forcing_delayed_prints = ref false + +(** If true, treat calls to no-arg getters as idempotent w.r.t non-nullness *) +let idempotent_getters = ref true + +(** Flag set when running in a child process (thus changes to global state will be lost *) +let in_child_process = ref false + +(** Flag to activate intraprocedural-only analysis *) +let intraprocedural = ref false + +(** if active, join x+j and x+k for constants j and k *) +let join_plus = ref true + +(** Flag to tune the final information-loss check used by the join +0 = use the most aggressive join for preconditions +1 = use the least aggressive join for preconditions +*) +let join_cond = ref 1 + +(** Flag for turning on the transformation that +null is assigned to a program variable when it becomes dead. +**) +let liveness = ref true + +(** if true, give static procs a long name filename::procname *) +let long_static_proc_names = ref false + +(** Number of lines of code in original file *) +let nLOC = ref 0 + +(** max number of procedures in each cluster *) +let max_cluster_size = ref 2000 + +(** Maximum number of processes to be used for parallel execution *) +let max_num_proc = ref 0 + +(** Maximum level of recursion during the analysis, after which a timeout is generated *) +let max_recursion = ref 5 + +(** Flag to tune the level of applying the meet operator for +preconditions during the footprint analysis. +0 = do not use the meet. +1 = use the meet to generate new preconditions. +*) +let meet_level = ref 1 + +(** Monitor the size of the props, and print every time the current max is exceeded *) +let monitor_prop_size = ref false + +(** Flag for using the nonempty lseg only **) +let nelseg = ref false + +(** Number of cores to be used for parallel execution *) +let num_cores = ref 1 + +(** Flag to activate nonstop mode: the analysis continues after in encounters errors *) +let nonstop = ref false + +(** Flag for the on-the-fly predicate discovery *) +let on_the_fly = ref true + +(** if true, skip the re-execution phase *) +let only_footprint = ref false + +(** flag: only analyze procedures which were analyzed before but have no specs *) +let only_nospecs = ref false + +(** flag: only analyze procedures dependent on previous skips which now have a .specs file *) +let only_skips = ref false + +(** if true, user simple pretty printing *) +let pp_simple = ref true + +(** flag: if true print full type info *) +let print_types = ref false + +(** if true, acrtivate color printing by diff'ing w.r.t. previous prop *) +let print_using_diff = ref true + +(** path of the project results directory *) +let results_dir = ref default_results_dir + +(** If not "", only consider functions recursively called by function [!slice_fun] *) +let slice_fun = ref "" + +(** Flag to tune the level of abstracting the postconditions of specs discovered +by the footprint analysis. +0 = nothing special. +1 = filter out redundant posts implied by other posts. *) +let spec_abs_level = ref 1 + +(** Flag for test mode *) +let test = ref true + +(** Flag set to enable detailed tracing informatin during error explanation *) +let trace_error = ref false + +(** Flag set to enable detailed tracing information during re-arrangement *) +let trace_rearrange = ref false + +(** Flag set to enable detailed tracing information during join *) +let trace_join = ref false + +(** Flag set to enable detailed tracing information during array abstraction *) +let trace_absarray = ref false + +(** Consider the size of types during analysis, e.g. cannot use an int pointer to write to a char *) +let type_size = ref false + +(** if true, compact summaries before saving *) +let save_compact_summaries = ref true + +(** flag: if true enables printing proposition compatible for the SMT project *) +let smt_output = ref false + +(** flag: if true performs taint analysis *) +let taint_analysis = ref false + +(** set to true to printing tracing information for the analysis *) +let trace_anal = ref false + +(** Flag for turning on the optimization based on locality +0 = no +1 = based on reachability +*) +let undo_join = ref true + +(** visit mode for the worklist: +0 depth - fist visit +1 bias towards exit node +2 least visited first *) +let worklist_mode = ref 0 + +(** flag: if true write dot files in db dir*) +let write_dotty = ref false + +(** flag: if true write html files in db dir *) +let write_html = ref false + +let subtype_multirange = ref true + +let optimistic_cast = ref false + +(** if true, filter out errors in low likelyhood buckets, and only show then in developer mode *) +let filter_buckets = ref false + +(** if true, show buckets in textual description of errors *) +let show_buckets = ref true + +(** if true, show memory leak buckets in textual description of errors *) +let show_ml_buckets = ref true + +(** if true, print cfg nodes in the dot file that are not defined in that file *) +let dotty_cfg_libs = ref true + +(** if true, it deals with messages (method calls) in objective-c using the objective-c +typical semantics. That is: if the receiver is nil then the method is nop and it returns 0. +When the flag is false we deal with messages as standard method / function calls *) +let objc_method_call_semantics = ref true + +(** if true, generate preconditions for runtime exceptions in Java and report errors for the public +methods having preconditions to throw runtime exceptions *) +let report_runtime_exceptions = ref false + +(** if true, sanity-check inferred preconditions against Nullable annotations and report +inconsistencies *) +let report_nullable_inconsistency = ref true + +(** true if the current objective-c source file is compiled with automatic reference counting (ARC) *) +let arc_mode = ref false + +let objc_memory_model_on = ref false + +let report_assertion_failure = from_env_variable "REPORT_ASSERTION_FAILURE" +let default_failure_name = "Assertion_failure" + +module Experiment = struct + + (** if true, activate the subtyping routines in C++ as well, not just in Java *) + let activate_subtyping_in_cpp = ref false + + (** if true, a precondition with e.g. index 3 in an array does not require the caller to have index 3 too + this mimics what happens with direct access to the array without a procedure call, where the index is simply materialized if not there *) + let allow_missing_index_in_proc_call = ref true + + (** if true, a procedure call succeeds even when there is a bound error + this mimics what happens with a direct array access where an error is produced and the analysis continues *) + let bound_error_allowed_in_procedure_call = ref true + +end + +let source_file_extentions = [".java"; ".m"; ".mm"; ".c"; ".cc"; ".cpp"; ".h"] + +let anonymous_block_prefix = "__objc_anonymous_block_" + +let anonymous_block_num_sep = "______" + +let property_attributes = "property_attributes" + +let unsafe_unret = "<\"Unsafe_unretained\">" + +let weak = "<\"Weak\">" + +let assign = "<\"Assign\">" diff --git a/infer/src/backend/dom.ml b/infer/src/backend/dom.ml new file mode 100644 index 000000000..671025cc7 --- /dev/null +++ b/infer/src/backend/dom.ml @@ -0,0 +1,2048 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Operators for the abstract domain. In particular, join and meet. *) + +module L = Logging +module F = Format +open Utils + +let (++) = Sil.Int.add +let (--) = Sil.Int.sub + +type pi = Sil.atom list +type sigma = Sil.hpred list + +(** {2 Object representing the status of the join operation} *) + +module JoinState : sig + + type mode = Pre | Post + val get_footprint : unit -> bool + val set_footprint : bool -> unit + +end = struct + + type mode = Pre | Post + let footprint = ref false (* set to true when we are doing join of footprints *) + let get_footprint () = !footprint + let set_footprint b = footprint := b + +end + +(** {2 Utility functions for ids} *) + +let can_rename id = + Ident.is_primed id || Ident.is_footprint id + +(** {2 Utility functions for sigma} *) + +let sigma_equal sigma1 sigma2 = + let rec f sigma1_rest sigma2_rest = + match (sigma1_rest, sigma2_rest) with + | [], [] -> () + | [], _:: _ | _:: _, [] -> + (L.d_strln "failure reason 1"; raise Fail) + | hpred1:: sigma1_rest', hpred2:: sigma2_rest' -> + if Sil.hpred_equal hpred1 hpred2 then f sigma1_rest' sigma2_rest' + else (L.d_strln "failure reason 2"; raise Fail) in + let sigma1_sorted = list_sort Sil.hpred_compare sigma1 in + let sigma2_sorted = list_sort Sil.hpred_compare sigma2 in + f sigma1_sorted sigma2_sorted + +let sigma_get_start_lexps_sort sigma = + let exp_compare_neg e1 e2 = - (Sil.exp_compare e1 e2) in + let filter e = Sil.fav_for_all (Sil.exp_fav e) Ident.is_normal in + let lexps = Sil.hpred_list_get_lexps filter sigma in + list_sort exp_compare_neg lexps + +(** {2 Utility functions for side} *) + +type side = Lhs | Rhs + +let select side e1 e2 = + match side with + | Lhs -> e1 + | Rhs -> e2 + +let opposite side = + match side with + | Lhs -> Rhs + | Rhs -> Lhs + +let do_side side f e1 e2 = + match side with + | Lhs -> f e1 e2 + | Rhs -> f e2 e1 + +(** {2 Sets for expression pairs} *) + +module EPset = Set.Make + (struct + type t = Sil.exp * Sil.exp + let compare (e1, e1') (e2, e2') = + match (Sil.exp_compare e1 e2) with + | i when i <> 0 -> i + | _ -> Sil.exp_compare e1' e2' + end) + +let epset_add e e' set = + match (Sil.exp_compare e e') with + | i when i <= 0 -> EPset.add (e, e') set + | _ -> EPset.add (e', e) set + +(** {2 Module for maintaining information about noninjectivity during join} *) + +module NonInj : sig + + val init : unit -> unit + val final : unit -> unit + val add : side -> Sil.exp -> Sil.exp -> unit + val check : side -> Sil.exp list -> bool + +end = struct + + (* mappings from primed or footprint var exps to primed or footprint var exps *) + let equiv_tbl1 = Hashtbl.create 32 + let equiv_tbl2 = Hashtbl.create 32 + + (* mappings from primed or footprint var exps to normal var, lvar or const exps *) + let const_tbl1 = Hashtbl.create 32 + let const_tbl2 = Hashtbl.create 32 + + let reset () = + Hashtbl.clear equiv_tbl1; + Hashtbl.clear equiv_tbl2; + Hashtbl.clear const_tbl1; + Hashtbl.clear const_tbl2 + + let init () = reset () + let final () = reset () + + let lookup' tbl e default = + match e with + | Sil.Var _ -> + begin + try Hashtbl.find tbl e + with Not_found -> (Hashtbl.replace tbl e default; default) + end + | _ -> assert false + + let lookup_equiv' tbl e = + lookup' tbl e e + let lookup_const' tbl e = + lookup' tbl e Sil.ExpSet.empty + + let rec find' tbl e = + let e' = lookup_equiv' tbl e in + match e' with + | Sil.Var _ -> + if Sil.exp_equal e e' then e + else + begin + let root = find' tbl e' in + Hashtbl.replace tbl e root; + root + end + | _ -> assert false + + let union' tbl const_tbl e1 e2 = + let r1 = find' tbl e1 in + let r2 = find' tbl e2 in + let new_r, old_r = + match (Sil.exp_compare r1 r2) with + | i when i <= 0 -> r1, r2 + | _ -> r2, r1 in + let new_c = lookup_const' const_tbl new_r in + let old_c = lookup_const' const_tbl old_r in + let res_c = Sil.ExpSet.union new_c old_c in + if Sil.ExpSet.cardinal res_c > 1 then (L.d_strln "failure reason 3"; raise Fail); + Hashtbl.replace tbl old_r new_r; + Hashtbl.replace const_tbl new_r res_c + + let replace_const' tbl const_tbl e c = + let r = find' tbl e in + let set = Sil.ExpSet.add c (lookup_const' const_tbl r) in + if Sil.ExpSet.cardinal set > 1 then (L.d_strln "failure reason 4"; raise Fail); + Hashtbl.replace const_tbl r set + + let add side e e' = + let tbl, const_tbl = + match side with + | Lhs -> equiv_tbl1, const_tbl1 + | Rhs -> equiv_tbl2, const_tbl2 + in + match e, e' with + | Sil.Var id, Sil.Var id' -> + begin + match can_rename id, can_rename id' with + | true, true -> union' tbl const_tbl e e' + | true, false -> replace_const' tbl const_tbl e e' + | false, true -> replace_const' tbl const_tbl e' e + | _ -> L.d_strln "failure reason 5"; raise Fail + end + | Sil.Var id, Sil.Const _ | Sil.Var id, Sil.Lvar _ -> + if (can_rename id) then replace_const' tbl const_tbl e e' + else (L.d_strln "failure reason 6"; raise Fail) + | Sil.Const _, Sil.Var id' | Sil.Lvar _, Sil.Var id' -> + if (can_rename id') then replace_const' tbl const_tbl e' e + else (L.d_strln "failure reason 7"; raise Fail) + | _ -> + if not (Sil.exp_equal e e') then (L.d_strln "failure reason 8"; raise Fail) else () + + let check side es = + let f = function Sil.Var id -> can_rename id | _ -> false in + let vars, nonvars = list_partition f es in + let tbl, const_tbl = + match side with + | Lhs -> equiv_tbl1, const_tbl1 + | Rhs -> equiv_tbl2, const_tbl2 + in + if (list_length nonvars > 1) then false + else + match vars, nonvars with + | [], _ | [_], [] -> true + | v:: vars', _ -> + let r = find' tbl v in + let set = lookup_const' const_tbl r in + (list_for_all (fun v' -> Sil.exp_equal (find' tbl v') r) vars') && + (list_for_all (fun c -> Sil.ExpSet.mem c set) nonvars) + +end + +(** {2 Modules for checking whether join or meet loses too much info} *) + +module type InfoLossCheckerSig = +sig + val init : sigma -> sigma -> unit + val final : unit -> unit + val lost_little : side -> Sil.exp -> Sil.exp list -> bool + val add : side -> Sil.exp -> Sil.exp -> unit +end + +module Dangling : sig + + val init : sigma -> sigma -> unit + val final : unit -> unit + val check : side -> Sil.exp -> bool + +end = struct + + let lexps1 = ref Sil.ExpSet.empty + let lexps2 = ref Sil.ExpSet.empty + + let get_lexp_set' sigma = + let lexp_lst = Sil.hpred_list_get_lexps (fun _ -> true) sigma in + list_fold_left (fun set e -> Sil.ExpSet.add e set) Sil.ExpSet.empty lexp_lst + let init sigma1 sigma2 = + lexps1 := get_lexp_set' sigma1; + lexps2 := get_lexp_set' sigma2 + let final () = + lexps1 := Sil.ExpSet.empty; + lexps2 := Sil.ExpSet.empty + + (* conservatively checks whether e is dangling *) + let check side e = + let lexps = + match side with + | Lhs -> !lexps1 + | Rhs -> !lexps2 + in + match e with + | Sil.Var id -> can_rename id && not (Sil.ExpSet.mem e lexps) + | Sil.Const _ -> not (Sil.ExpSet.mem e lexps) + | Sil.BinOp _ -> not (Sil.ExpSet.mem e lexps) + | _ -> false +end + +module CheckJoinPre : InfoLossCheckerSig = struct + + let init sigma1 sigma2 = + NonInj.init (); + Dangling.init sigma1 sigma2 + + let final () = + NonInj.final (); + Dangling.final () + + let fail_case side e es = + let side_op = opposite side in + match e with + | Sil.Lvar _ -> false + | Sil.Var id when Ident.is_normal id -> list_length es >= 1 + | Sil.Var id -> + if !Config.join_cond = 0 then + list_exists (Sil.exp_equal Sil.exp_zero) es + else if Dangling.check side e then + begin + let r = list_exists (fun e' -> not (Dangling.check side_op e')) es in + if r then begin + L.d_str ".... Dangling Check (dang e:"; Sil.d_exp e; + L.d_str ") (? es:"; Sil.d_exp_list es; L.d_strln ") ...."; + L.d_ln () + end; + r + end + else + begin + let r = list_exists (Dangling.check side_op) es in + if r then begin + L.d_str ".... Dangling Check (notdang e:"; Sil.d_exp e; + L.d_str ") (? es:"; Sil.d_exp_list es; L.d_strln ") ...."; + L.d_ln () + end; + r + end + | _ -> false + + let lost_little side e es = + let side_op = opposite side in + let es = match e with Sil.Const _ -> [] | _ -> es in + if (fail_case side e es) then false + else + match es with + | [] | [_] -> true + | _ -> (NonInj.check side_op es) + + let add = NonInj.add +end + +module CheckJoinPost : InfoLossCheckerSig = struct + + let init sigma1 sigma2 = + NonInj.init () + + let final () = + NonInj.final () + + let fail_case side e es = + match e with + | Sil.Lvar _ -> false + | Sil.Var id when Ident.is_normal id -> list_length es >= 1 + | Sil.Var id -> false + | _ -> false + + let lost_little side e es = + let side_op = opposite side in + let es = match e with Sil.Const _ -> [] | _ -> es in + if (fail_case side e es) then false + else + match es with + | [] | [_] -> true + | _ -> NonInj.check side_op es + + let add = NonInj.add +end + +module CheckJoin : sig + + val init : JoinState.mode -> sigma -> sigma -> unit + val final : unit -> unit + val lost_little : side -> Sil.exp -> Sil.exp list -> bool + val add : side -> Sil.exp -> Sil.exp -> unit + +end = struct + + let mode_ref : JoinState.mode ref = ref JoinState.Post + + let init mode sigma1 sigma2 = + mode_ref := mode; + match mode with + | JoinState.Pre -> CheckJoinPre.init sigma1 sigma2 + | JoinState.Post -> CheckJoinPost.init sigma1 sigma2 + + let final () = + match !mode_ref with + | JoinState.Pre -> CheckJoinPre.final (); mode_ref := JoinState.Post + | JoinState.Post -> CheckJoinPost.final (); mode_ref := JoinState.Post + + let lost_little side e es = + match !mode_ref with + | JoinState.Pre -> CheckJoinPre.lost_little side e es + | JoinState.Post -> CheckJoinPost.lost_little side e es + + let add side e1 e2 = + match !mode_ref with + | JoinState.Pre -> CheckJoinPre.add side e1 e2 + | JoinState.Post -> CheckJoinPost.add side e1 e2 +end + +module CheckMeet : InfoLossCheckerSig = struct + + let lexps1 = ref Sil.ExpSet.empty + let lexps2 = ref Sil.ExpSet.empty + + let init sigma1 sigma2 = + let lexps1_lst = Sil.hpred_list_get_lexps (fun _ -> true) sigma1 in + let lexps2_lst = Sil.hpred_list_get_lexps (fun _ -> true) sigma2 in + lexps1 := Sil.elist_to_eset lexps1_lst; + lexps2 := Sil.elist_to_eset lexps2_lst + + let final () = + lexps1 := Sil.ExpSet.empty; + lexps2 := Sil.ExpSet.empty + + let lost_little side e es = + let lexps = match side with + | Lhs -> !lexps1 + | Rhs -> !lexps2 + in + match es, e with + | [], _ -> + true + | [Sil.Const _], Sil.Lvar _ -> + false + | [Sil.Const _], Sil.Var _ -> + not (Sil.ExpSet.mem e lexps) + | [Sil.Const _], _ -> + assert false + | [_], Sil.Lvar _ | [_], Sil.Var _ -> + true + | [_], _ -> + assert false + | _, Sil.Lvar _ | _, Sil.Var _ -> + false + | _, Sil.Const _ -> + assert false + | _ -> assert false + + let add = NonInj.add +end + +(** {2 Module for worklist} *) + +module Todo : sig + + exception Empty + type t + val init : unit -> unit + val final : unit -> unit + val reset : (Sil.exp * Sil.exp * Sil.exp) list -> unit + val push : (Sil.exp * Sil.exp * Sil.exp) -> unit + val pop : unit -> (Sil.exp * Sil.exp * Sil.exp) + val set : t -> unit + val take : unit -> t + +end = struct + + exception Empty + type t = (Sil.exp * Sil.exp * Sil.exp) list + + let tbl = ref [] + + let init () = tbl := [] + let final () = tbl := [] + let reset todo = tbl := todo + + let push e = + tbl := e :: !tbl + let pop () = + match !tbl with + | h:: t -> tbl := t; h + | _ -> raise Empty + + let set todo = tbl := todo + let take () = let res = !tbl in tbl := []; res + +end + +(** {2 Module for introducing fresh variables} *) + +module FreshVarExp : sig + + val init : unit -> unit + val get_fresh_exp : Sil.exp -> Sil.exp -> Sil.exp + val get_induced_pi : unit -> Sil.atom list + val lookup : side -> Sil.exp -> (Sil.exp * Sil.exp) option + val final : unit -> unit + +end = struct + + let t = ref [] + + let init () = t := [] + let final () = t := [] + + let entry_compare (e1, e2, _) (e1', e2', _) = + let n1 = Sil.exp_compare e1 e2 in + if n1 <> 0 then n1 else Sil.exp_compare e2 e2' + + let get_fresh_exp e1 e2 = + try + let (_, _, e) = list_find (fun (e1', e2', _) -> Sil.exp_equal e1 e1' && Sil.exp_equal e2 e2') !t in + e + with Not_found -> + let e = Sil.exp_get_undefined (JoinState.get_footprint ()) in + t := (e1, e2, e)::!t; + e + + let lookup side e = + try + let (e1, e2, e) = list_find (fun (e1', e2', _) -> Sil.exp_equal e (select side e1' e2')) !t in + Some (e, select (opposite side) e1 e2) + with Not_found -> + None + + let get_induced_atom acc strict_lower upper e = + let ineq_lower = Prop.mk_inequality (Sil.BinOp(Sil.Lt, strict_lower, e)) in + let ineq_upper = Prop.mk_inequality (Sil.BinOp(Sil.Le, e, upper)) in + ineq_lower:: ineq_upper:: acc + + let minus2_to_2 = list_map Sil.Int.of_int [-2; -1; 0; 1; 2] + + let get_induced_pi () = + let t_sorted = list_sort entry_compare !t in + + let add_and_chk_eq e1 e1' n = + match e1, e1' with + | Sil.Const (Sil.Cint n1), Sil.Const (Sil.Cint n1') -> Sil.Int.eq (n1 ++ n) n1' + | _ -> false in + let add_and_gen_eq e e' n = + let e_plus_n = Sil.BinOp(Sil.PlusA, e, Sil.exp_int n) in + Prop.mk_eq e_plus_n e' in + let rec f_eqs_entry ((e1, e2, e) as entry) eqs_acc t_seen = function + | [] -> eqs_acc, t_seen + | ((e1', e2', e') as entry'):: t_rest' -> + try + let n = list_find (fun n -> add_and_chk_eq e1 e1' n && add_and_chk_eq e2 e2' n) minus2_to_2 in + let eq = add_and_gen_eq e e' n in + let eqs_acc' = eq:: eqs_acc in + f_eqs_entry entry eqs_acc' t_seen t_rest' + with Not_found -> + let t_seen' = entry':: t_seen in + f_eqs_entry entry eqs_acc t_seen' t_rest' in + let rec f_eqs eqs_acc t_acc = function + | [] -> (eqs_acc, t_acc) + | entry:: t_rest -> + let eqs_acc', t_rest' = f_eqs_entry entry eqs_acc [] t_rest in + let t_acc' = entry:: t_acc in + f_eqs eqs_acc' t_acc' t_rest' in + let eqs, t_minimal = f_eqs [] [] t_sorted in + + let f_ineqs acc (e1, e2, e) = + match e1, e2 with + | Sil.Const (Sil.Cint n1), Sil.Const (Sil.Cint n2) -> + let strict_lower1, upper1 = if Sil.Int.leq n1 n2 then (n1 -- Sil.Int.one, n2) else (n2 -- Sil.Int.one, n1) in + let e_strict_lower1 = Sil.exp_int strict_lower1 in + let e_upper1 = Sil.exp_int upper1 in + get_induced_atom acc e_strict_lower1 e_upper1 e + | _ -> acc in + list_fold_left f_ineqs eqs t_minimal + +end + +(** {2 Modules for renaming} *) + +module Rename : sig + + type data_opt = ExtFresh | ExtDefault of Sil.exp + + val init : unit -> unit + val final : unit -> unit + val reset : unit -> (Sil.exp * Sil.exp * Sil.exp) list + + val extend : Sil.exp -> Sil.exp -> data_opt -> Sil.exp + val check : (side -> Sil.exp -> Sil.exp list -> bool) -> bool + + val get : Sil.exp -> Sil.exp -> Sil.exp option + val get_others : side -> Sil.exp -> (Sil.exp * Sil.exp) option + val get_other_atoms : side -> Sil.atom -> (Sil.atom * Sil.atom) option + + val lookup : side -> Sil.exp -> Sil.exp + val lookup_list : side -> Sil.exp list -> Sil.exp list + val lookup_list_todo : side -> Sil.exp list -> Sil.exp list + + val to_subst_proj : side -> Sil.fav -> Sil.subst + val to_subst_emb : side -> Sil.subst + val pp : printenv -> Format.formatter -> (Sil.exp * Sil.exp * Sil.exp) list -> unit + +end = struct + + type t = (Sil.exp * Sil.exp * Sil.exp) list + let tbl = ref [] + let empty = [] + + let init () = tbl := [] + let final () = tbl := [] + let reset () = + let f = function + | Sil.Var id, e, _ | e, Sil.Var id, _ -> + (Ident.is_footprint id) && + (Sil.fav_for_all (Sil.exp_fav e) (fun id -> not (Ident.is_primed id))) + | _ -> false in + let t' = list_filter f !tbl in + tbl := t'; + t' + + let push v = tbl := v :: !tbl + + let check lost_little = + let f side e = + let side_op = opposite side in + let assoc_es = + match e with + | Sil.Const _ -> [] + | Sil.Lvar _ | Sil.Var _ + | Sil.BinOp (Sil.PlusA, Sil.Var _, _) -> + let is_same_e (e1, e2, _) = Sil.exp_equal e (select side e1 e2) in + let assoc = list_filter is_same_e !tbl in + list_map (fun (e1, e2, _) -> select side_op e1 e2) assoc + | _ -> + L.d_str "no pattern match in check lost_little e: "; Sil.d_exp e; L.d_ln (); + raise Fail in + lost_little side e assoc_es in + let lhs_es = list_map (fun (e1, _, _) -> e1) !tbl in + let rhs_es = list_map (fun (_, e2, _) -> e2) !tbl in + (list_for_all (f Rhs) rhs_es) && (list_for_all (f Lhs) lhs_es) + + let lookup_side' side e = + let f (e1, e2, _) = Sil.exp_equal e (select side e1 e2) in + list_filter f !tbl + + let lookup_side_induced' side e = + let res = ref [] in + let f v = match v, side with + | (Sil.BinOp (Sil.PlusA, e1', Sil.Const (Sil.Cint i)), e2, e'), Lhs + when Sil.exp_equal e e1' -> + let c' = Sil.exp_int (Sil.Int.neg i) in + let v' = (e1', Sil.BinOp(Sil.PlusA, e2, c'), Sil.BinOp (Sil.PlusA, e', c')) in + res := v'::!res + | (e1, Sil.BinOp (Sil.PlusA, e2', Sil.Const (Sil.Cint i)), e'), Rhs + when Sil.exp_equal e e2' -> + let c' = Sil.exp_int (Sil.Int.neg i) in + let v' = (Sil.BinOp(Sil.PlusA, e1, c'), e2', Sil.BinOp (Sil.PlusA, e', c')) in + res := v'::!res + | _ -> () in + begin + list_iter f !tbl; + list_rev !res + end + + (* Return the triple whose side is [e], if it exists unique *) + let lookup' todo side e : Sil.exp = + match e with + | Sil.Var id when can_rename id -> + begin + let r = lookup_side' side e in + match r with + | [(e1, e2, id) as t] -> if todo then Todo.push t; id + | _ -> L.d_strln "failure reason 9"; raise Fail + end + | Sil.Var _ | Sil.Const _ | Sil.Lvar _ -> if todo then Todo.push (e, e, e); e + | _ -> L.d_strln "failure reason 10"; raise Fail + + let lookup side e = lookup' false side e + let lookup_todo side e = lookup' true side e + let lookup_list side l = list_map (lookup side) l + let lookup_list_todo side l = list_map (lookup_todo side) l + + let to_subst_proj (side: side) vars = + let renaming_restricted = + list_filter (function (_, _, Sil.Var i) -> Sil.fav_mem vars i | _ -> assert false) !tbl in + let sub_list_side = + list_map + (function (e1, e2, Sil.Var i) -> (i, select side e1 e2) | _ -> assert false) + renaming_restricted in + let sub_list_side_sorted = + list_sort (fun (i, e) (i', e') -> Sil.exp_compare e e') sub_list_side in + let rec find_duplicates = + function + | (i, e):: ((i', e'):: l' as t) -> Sil.exp_equal e e' || find_duplicates t + | _ -> false in + if find_duplicates sub_list_side_sorted then (L.d_strln "failure reason 11"; raise Fail) + else Sil.sub_of_list sub_list_side + + let to_subst_emb (side : side) = + let renaming_restricted = + let pick_id_case (e1, e2, e) = + match select side e1 e2 with + | Sil.Var i -> can_rename i + | _ -> false in + list_filter pick_id_case !tbl in + let sub_list = + let project (e1, e2, e) = + match select side e1 e2 with + | Sil.Var i -> (i, e) + | _ -> assert false in + list_map project renaming_restricted in + let sub_list_sorted = + let compare (i, _) (i', _) = Ident.compare i i' in + list_sort compare sub_list in + let rec find_duplicates = function + | (i, _):: ((i', _):: l' as t) -> Ident.equal i i' || find_duplicates t + | _ -> false in + if find_duplicates sub_list_sorted then (L.d_strln "failure reason 12"; raise Fail) + else Sil.sub_of_list sub_list_sorted + + let get e1 e2 = + let f (e1', e2', _) = Sil.exp_equal e1 e1' && Sil.exp_equal e2 e2' in + match (list_filter f !tbl) with + | [] -> None + | (_, _, e):: _ -> Some e + + let get_others' f_lookup side e = + let side_op = opposite side in + let r = f_lookup side e in + match r with + | [] -> None + | [(e1, e2, e')] -> Some (e', select side_op e1 e2) + | _ -> None + let get_others = get_others' lookup_side' + let get_others_direct_or_induced side e = + let others = get_others side e in + match others with + | None -> get_others' lookup_side_induced' side e + | Some _ -> others + let get_others_deep side = function + | Sil.BinOp(op, e, e') -> + let others = get_others_direct_or_induced side e in + let others' = get_others_direct_or_induced side e' in + (match others, others' with + | None, _ | _, None -> None + | Some (e_res, e_op), Some(e_res', e_op') -> + let e_res'' = Sil.BinOp(op, e_res, e_res') in + let e_op'' = Sil.BinOp(op, e_op, e_op') in + Some (e_res'', e_op'')) + | _ -> None + + let get_other_atoms side atom_in = + let build_other_atoms construct side e = + if !Config.trace_join then (L.d_str "build_other_atoms: "; Sil.d_exp e; L.d_ln ()); + let others1 = get_others_direct_or_induced side e in + let others2 = match others1 with None -> get_others_deep side e | Some _ -> others1 in + match others2 with + | None -> None + | Some (e_res, e_op) -> + let a_res = construct e_res in + let a_op = construct e_op in + if !Config.trace_join then begin + L.d_str "build_other_atoms (successful) "; + Sil.d_atom a_res; L.d_str ", "; Sil.d_atom a_op; L.d_ln () + end; + Some (a_res, a_op) in + let exp_contains_only_normal_ids e = + let fav = Sil.exp_fav e in + Sil.fav_for_all fav Ident.is_normal in + let atom_contains_only_normal_ids a = + let fav = Sil.atom_fav a in + Sil.fav_for_all fav Ident.is_normal in + let normal_ids_only = atom_contains_only_normal_ids atom_in in + if normal_ids_only then Some (atom_in, atom_in) + else + begin + match atom_in with + | Sil.Aneq((Sil.Var id as e), e') | Sil.Aneq(e', (Sil.Var id as e)) + when (exp_contains_only_normal_ids e' && not (Ident.is_normal id)) -> + build_other_atoms (fun e0 -> Prop.mk_neq e0 e') side e + + | Sil.Aeq((Sil.Var id as e), e') | Sil.Aeq(e', (Sil.Var id as e)) + when (exp_contains_only_normal_ids e' && not (Ident.is_normal id)) -> + build_other_atoms (fun e0 -> Prop.mk_eq e0 e') side e + + | Sil.Aeq(Sil.BinOp(Sil.Le, e, e'), Sil.Const (Sil.Cint i)) + | Sil.Aeq(Sil.Const (Sil.Cint i), Sil.BinOp(Sil.Le, e, e')) + when Sil.Int.isone i && (exp_contains_only_normal_ids e') -> + let construct e0 = Prop.mk_inequality (Sil.BinOp(Sil.Le, e0, e')) in + build_other_atoms construct side e + + | Sil.Aeq(Sil.BinOp(Sil.Lt, e', e), Sil.Const (Sil.Cint i)) + | Sil.Aeq(Sil.Const (Sil.Cint i), Sil.BinOp(Sil.Lt, e', e)) + when Sil.Int.isone i && (exp_contains_only_normal_ids e') -> + let construct e0 = Prop.mk_inequality (Sil.BinOp(Sil.Lt, e', e0)) in + build_other_atoms construct side e + + | _ -> None + end + + type data_opt = ExtFresh | ExtDefault of Sil.exp + + (* Extend the renaming relation. At least one of e1 and e2 + * should be a primed or footprint variable *) + let extend e1 e2 default_op = + try + let eq_to_e (f1, f2, _) = Sil.exp_equal e1 f1 && Sil.exp_equal e2 f2 in + let _, _, res = list_find eq_to_e !tbl in + res + with Not_found -> + let fav1 = Sil.exp_fav e1 in + let fav2 = Sil.exp_fav e2 in + let no_ren1 = not (Sil.fav_exists fav1 can_rename) in + let no_ren2 = not (Sil.fav_exists fav2 can_rename) in + let some_primed () = Sil.fav_exists fav1 Ident.is_primed || Sil.fav_exists fav2 Ident.is_primed in + let e = + if (no_ren1 && no_ren2) then + if (Sil.exp_equal e1 e2) then e1 else (L.d_strln "failure reason 13"; raise Fail) + else + match default_op with + | ExtDefault e -> e + | ExtFresh -> + let kind = if JoinState.get_footprint () && not (some_primed ()) then Ident.kfootprint else Ident.kprimed in + Sil.Var (Ident.create_fresh kind) in + let entry = e1, e2, e in + push entry; + Todo.push entry; + e + + let pp pe f renaming = + let pp_triple f (e1, e2, e3) = + F.fprintf f "(%a,%a,%a)" (Sil.pp_exp pe) e3 (Sil.pp_exp pe) e1 (Sil.pp_exp pe) e2 in + (pp_seq pp_triple) f renaming + +end + +(** {2 Functions for constructing fresh sil data types} *) + +let extend_side' kind side e = + match Rename.get_others side e with + | None -> + let e_op = Sil.Var (Ident.create_fresh kind) in + let e_new = Sil.Var (Ident.create_fresh kind) in + let e1, e2 = + match side with + | Lhs -> e, e_op + | Rhs -> e_op, e in + Rename.extend e1 e2 (Rename.ExtDefault (e_new)) + | Some (e', _) -> e' + +let rec exp_construct_fresh side e = + match e with + | Sil.Var id -> + if Ident.is_normal id then + (Todo.push (e, e, e); e) + else if Ident.is_footprint id then + extend_side' Ident.kfootprint side e + else + extend_side' Ident.kprimed side e + | Sil.Const _ -> e + | Sil.Cast (t, e1) -> + let e1' = exp_construct_fresh side e1 in + Sil.Cast (t, e1') + | Sil.UnOp(unop, e1, topt) -> + let e1' = exp_construct_fresh side e1 in + Sil.UnOp(unop, e1', topt) + | Sil.BinOp(binop, e1, e2) -> + let e1' = exp_construct_fresh side e1 in + let e2' = exp_construct_fresh side e2 in + Sil.BinOp(binop, e1', e2') + | Sil.Lvar _ -> + e + | Sil.Lfield(e1, fld, typ) -> + let e1' = exp_construct_fresh side e1 in + Sil.Lfield(e1', fld, typ) + | Sil.Lindex(e1, e2) -> + let e1' = exp_construct_fresh side e1 in + let e2' = exp_construct_fresh side e2 in + Sil.Lindex(e1', e2') + | Sil.Sizeof _ -> + e + +let strexp_construct_fresh side = + let f (e, inst_opt) = (exp_construct_fresh side e, inst_opt) in + Sil.strexp_expmap f + +let hpred_construct_fresh side = + let f (e, inst_opt) = (exp_construct_fresh side e, inst_opt) in + Sil.hpred_expmap f + +(** {2 Join and Meet for Ids} *) + +let ident_same_kind_primed_footprint id1 id2 = + (Ident.is_primed id1 && Ident.is_primed id2) || + (Ident.is_footprint id1 && Ident.is_footprint id2) + +let ident_partial_join (id1: Ident.t) (id2: Ident.t) = + match Ident.is_normal id1, Ident.is_normal id2 with + | true, true -> + if Ident.equal id1 id2 then Sil.Var id1 else (L.d_strln "failure reason 14"; raise Fail) + | true, _ | _, true -> + Rename.extend (Sil.Var id1) (Sil.Var id2) Rename.ExtFresh + | _ -> + begin + if not (ident_same_kind_primed_footprint id1 id2) then + (L.d_strln "failure reason 15"; raise Fail) + else + let e1 = Sil.Var id1 in + let e2 = Sil.Var id2 in + Rename.extend e1 e2 Rename.ExtFresh + end + +let ident_partial_meet (id1: Ident.t) (id2: Ident.t) = + match Ident.is_normal id1, Ident.is_normal id2 with + | true, true -> + if Ident.equal id1 id2 then Sil.Var id1 + else (L.d_strln "failure reason 16"; raise Fail) + | true, _ -> + let e1, e2 = Sil.Var id1, Sil.Var id2 in + Rename.extend e1 e2 (Rename.ExtDefault(e1)) + | _, true -> + let e1, e2 = Sil.Var id1, Sil.Var id2 in + Rename.extend e1 e2 (Rename.ExtDefault(e2)) + | _ -> + if Ident.is_primed id1 && Ident.is_primed id2 then + Rename.extend (Sil.Var id1) (Sil.Var id2) Rename.ExtFresh + else if Ident.is_footprint id1 && Ident.equal id1 id2 then + let e = Sil.Var id1 in Rename.extend e e (Rename.ExtDefault(e)) + else + (L.d_strln "failure reason 17"; raise Fail) + +(** {2 Join and Meet for Exps} *) + +let const_partial_join c1 c2 = + if (Sil.const_equal c1 c2) then Sil.Const c1 + else match c1, c2 with + | Sil.Cfun _, Sil.Cfun _ + | Sil.Cstr _, Sil.Cstr _ + | Sil.Cclass _, Sil.Cclass _ + | Sil.Cattribute _, Sil.Cattribute _ -> + (L.d_strln "failure reason 18"; raise Fail) + | _ -> + if (!Config.abs_val >= 2) then FreshVarExp.get_fresh_exp (Sil.Const c1) (Sil.Const c2) + else (L.d_strln "failure reason 19"; raise Fail) + +let rec exp_partial_join (e1: Sil.exp) (e2: Sil.exp) : Sil.exp = + (* L.d_str "exp_partial_join "; Sil.d_exp e1; L.d_str " "; Sil.d_exp e2; L.d_ln (); *) + match e1, e2 with + | Sil.Var id1, Sil.Var id2 -> + ident_partial_join id1 id2 + + | Sil.Var id, Sil.Const c + | Sil.Const c, Sil.Var id -> + if Ident.is_normal id then + (L.d_strln "failure reason 20"; raise Fail) + else + Rename.extend e1 e2 Rename.ExtFresh + | Sil.Const c1, Sil.Const c2 -> + const_partial_join c1 c2 + + | Sil.Var id, Sil.Lvar _ + | Sil.Lvar _, Sil.Var id -> + if Ident.is_normal id then (L.d_strln "failure reason 21"; raise Fail) + else Rename.extend e1 e2 Rename.ExtFresh + + | Sil.BinOp(Sil.PlusA, Sil.Var id1, Sil.Const _), Sil.Var id2 + | Sil.Var id1, Sil.BinOp(Sil.PlusA, Sil.Var id2, Sil.Const _) + when ident_same_kind_primed_footprint id1 id2 -> + Rename.extend e1 e2 Rename.ExtFresh + | Sil.BinOp(Sil.PlusA, Sil.Var id1, Sil.Const (Sil.Cint c1)), Sil.Const (Sil.Cint c2) + when can_rename id1 -> + let c2' = c2 -- c1 in + let e_res = Rename.extend (Sil.Var id1) (Sil.exp_int c2') Rename.ExtFresh in + Sil.BinOp(Sil.PlusA, e_res, Sil.exp_int c1) + | Sil.Const (Sil.Cint c1), Sil.BinOp(Sil.PlusA, Sil.Var id2, Sil.Const (Sil.Cint c2)) + when can_rename id2 -> + let c1' = c1 -- c2 in + let e_res = Rename.extend (Sil.exp_int c1') (Sil.Var id2) Rename.ExtFresh in + Sil.BinOp(Sil.PlusA, e_res, Sil.exp_int c2) + | Sil.Cast(t1, e1), Sil.Cast(t2, e2) -> + if not (Sil.typ_equal t1 t2) then (L.d_strln "failure reason 22"; raise Fail) + else + let e1'' = exp_partial_join e1 e2 in + Sil.Cast (t1, e1'') + | Sil.UnOp(unop1, e1, topt1), Sil.UnOp(unop2, e2, topt2) -> + if not (Sil.unop_equal unop1 unop2) then (L.d_strln "failure reason 23"; raise Fail) + else Sil.UnOp (unop1, exp_partial_join e1 e2, topt1) (* should be topt1 = topt2 *) + | Sil.BinOp(Sil.PlusPI, e1, e1'), Sil.BinOp(Sil.PlusPI, e2, e2') -> + let e1'' = exp_partial_join e1 e2 in + let e2'' = match e1', e2' with + | Sil.Const _, Sil.Const _ -> exp_partial_join e1' e2' + | _ -> FreshVarExp.get_fresh_exp e1 e2 in + Sil.BinOp(Sil.PlusPI, e1'', e2'') + | Sil.BinOp(binop1, e1, e1'), Sil.BinOp(binop2, e2, e2') -> + if not (Sil.binop_equal binop1 binop2) then (L.d_strln "failure reason 24"; raise Fail) + else + let e1'' = exp_partial_join e1 e2 in + let e2'' = exp_partial_join e1' e2' in + Sil.BinOp(binop1, e1'', e2'') + | Sil.Lvar(pvar1), Sil.Lvar(pvar2) -> + if not (Sil.pvar_equal pvar1 pvar2) then (L.d_strln "failure reason 25"; raise Fail) + else e1 + | Sil.Lfield(e1, f1, t1), Sil.Lfield(e2, f2, t2) -> + if not (Sil.fld_equal f1 f2) then (L.d_strln "failure reason 26"; raise Fail) + else Sil.Lfield(exp_partial_join e1 e2, f1, t1) (* should be t1 = t2 *) + | Sil.Lindex(e1, e1'), Sil.Lindex(e2, e2') -> + let e1'' = exp_partial_join e1 e2 in + let e2'' = exp_partial_join e1' e2' in + Sil.Lindex(e1'', e2'') + | Sil.Sizeof (t1, st1), Sil.Sizeof (t2, st2) -> + Sil.Sizeof (typ_partial_join t1 t2, Sil.Subtype.join st1 st2) + | _ -> + L.d_str "exp_partial_join no match "; Sil.d_exp e1; L.d_str " "; Sil.d_exp e2; L.d_ln (); + raise Fail + +and size_partial_join size1 size2 = match size1, size2 with + | Sil.BinOp(Sil.PlusA, e1, Sil.Const c1), Sil.BinOp(Sil.PlusA, e2, Sil.Const c2) -> + let e' = exp_partial_join e1 e2 in + let c' = exp_partial_join (Sil.Const c1) (Sil.Const c2) in + Sil.BinOp (Sil.PlusA, e', c') + | Sil.BinOp(Sil.PlusA, _, _), Sil.BinOp(Sil.PlusA, _, _) -> + Rename.extend size1 size2 Rename.ExtFresh + | Sil.Var id1, Sil.Var id2 when Ident.equal id1 id2 -> + size1 + | _ -> exp_partial_join size1 size2 + +and typ_partial_join t1 t2 = match t1, t2 with + | Sil.Tptr (t1, pk1), Sil.Tptr (t2, pk2) when Sil.ptr_kind_compare pk1 pk2 = 0 -> + Sil.Tptr (typ_partial_join t1 t2, pk1) + | Sil.Tarray (typ1, size1), Sil.Tarray(typ2, size2) -> + let t = typ_partial_join typ1 typ2 in + let size = size_partial_join size1 size2 in + Sil.Tarray (t, size) + | _ when Sil.typ_equal t1 t2 -> t1 (* common case *) + | _ -> + L.d_str "typ_partial_join no match "; Sil.d_typ_full t1; L.d_str " "; Sil.d_typ_full t2; L.d_ln (); + raise Fail + +let rec exp_partial_meet (e1: Sil.exp) (e2: Sil.exp) : Sil.exp = + match e1, e2 with + | Sil.Var id1, Sil.Var id2 -> + ident_partial_meet id1 id2 + | Sil.Var id, Sil.Const _ -> + if not (Ident.is_normal id) then + Rename.extend e1 e2 (Rename.ExtDefault(e2)) + else (L.d_strln "failure reason 27"; raise Fail) + | Sil.Const _, Sil.Var id -> + if not (Ident.is_normal id) then + Rename.extend e1 e2 (Rename.ExtDefault(e1)) + else (L.d_strln "failure reason 28"; raise Fail) + | Sil.Const c1, Sil.Const c2 -> + if (Sil.const_equal c1 c2) then e1 else (L.d_strln "failure reason 29"; raise Fail) + | Sil.Cast(t1, e1), Sil.Cast(t2, e2) -> + if not (Sil.typ_equal t1 t2) then (L.d_strln "failure reason 30"; raise Fail) + else + let e1'' = exp_partial_meet e1 e2 in + Sil.Cast (t1, e1'') + | Sil.UnOp(unop1, e1, topt1), Sil.UnOp(unop2, e2, topt2) -> + if not (Sil.unop_equal unop1 unop2) then (L.d_strln "failure reason 31"; raise Fail) + else Sil.UnOp (unop1, exp_partial_meet e1 e2, topt1) (* should be topt1 = topt2 *) + | Sil.BinOp(binop1, e1, e1'), Sil.BinOp(binop2, e2, e2') -> + if not (Sil.binop_equal binop1 binop2) then (L.d_strln "failure reason 32"; raise Fail) + else + let e1'' = exp_partial_meet e1 e2 in + let e2'' = exp_partial_meet e1' e2' in + Sil.BinOp(binop1, e1'', e2'') + | Sil.Var id, Sil.Lvar _ -> + if not (Ident.is_normal id) then + Rename.extend e1 e2 (Rename.ExtDefault(e2)) + else (L.d_strln "failure reason 33"; raise Fail) + | Sil.Lvar _, Sil.Var id -> + if not (Ident.is_normal id) then + Rename.extend e1 e2 (Rename.ExtDefault(e1)) + else (L.d_strln "failure reason 34"; raise Fail) + | Sil.Lvar(pvar1), Sil.Lvar(pvar2) -> + if not (Sil.pvar_equal pvar1 pvar2) then (L.d_strln "failure reason 35"; raise Fail) + else e1 + | Sil.Lfield(e1, f1, t1), Sil.Lfield(e2, f2, t2) -> + if not (Sil.fld_equal f1 f2) then (L.d_strln "failure reason 36"; raise Fail) + else Sil.Lfield(exp_partial_meet e1 e2, f1, t1) (* should be t1 = t2 *) + | Sil.Lindex(e1, e1'), Sil.Lindex(e2, e2') -> + let e1'' = exp_partial_meet e1 e2 in + let e2'' = exp_partial_meet e1' e2' in + Sil.Lindex(e1'', e2'') + | _ -> (L.d_strln "failure reason 37"; raise Fail) + +let exp_list_partial_join = list_map2 exp_partial_join + +let exp_list_partial_meet = list_map2 exp_partial_meet + +let run_without_absval f e1 e2 = + let old_abs_val = !Config.abs_val in + let new_abs_val = if old_abs_val = 0 then 0 else 1 in + try + begin + Config.abs_val := new_abs_val; + let e = f e1 e2 in + Config.abs_val := old_abs_val; + e + end + with exn -> + begin + Config.abs_val := old_abs_val; + raise exn + end + +let exp_partial_join_without_absval e1 e2 = + run_without_absval exp_partial_join e1 e2 + +let exp_partial_meet_without_absval e1 e2 = + run_without_absval exp_partial_meet e1 e2 + + +(** {2 Join and Meet for Strexp} *) + +let rec strexp_partial_join mode (strexp1: Sil.strexp) (strexp2: Sil.strexp) : Sil.strexp = + + let rec f_fld_se_list inst mode acc fld_se_list1 fld_se_list2 = + match fld_se_list1, fld_se_list2 with + | [], [] -> Sil.Estruct (list_rev acc, inst) + | [], other_fsel | other_fsel, [] -> + begin + match mode with + | JoinState.Pre -> (L.d_strln "failure reason 42"; raise Fail) + | JoinState.Post -> Sil.Estruct (list_rev acc, inst) + end + | (fld1, se1):: fld_se_list1', (fld2, se2):: fld_se_list2' -> + let comparison = Sil.fld_compare fld1 fld2 in + if comparison = 0 then + let strexp' = strexp_partial_join mode se1 se2 in + let fld_se_list_new = (fld1, strexp') :: acc in + f_fld_se_list inst mode fld_se_list_new fld_se_list1' fld_se_list2' + else begin + match mode with + | JoinState.Pre -> + (L.d_strln "failure reason 43"; raise Fail) + | JoinState.Post -> + if comparison < 0 then begin + f_fld_se_list inst mode acc fld_se_list1' fld_se_list2 + end + else if comparison > 0 then begin + f_fld_se_list inst mode acc fld_se_list1 fld_se_list2' + end + else + assert false (* This case should not happen. *) + end in + + let rec f_idx_se_list inst size idx_se_list_acc idx_se_list1 idx_se_list2 = + match idx_se_list1, idx_se_list2 with + | [], [] -> Sil.Earray (size, list_rev idx_se_list_acc, inst) + | [], other_isel | other_isel, [] -> + begin + match mode with + | JoinState.Pre -> (L.d_strln "failure reason 44"; raise Fail) + | JoinState.Post -> + Sil.Earray (size, list_rev idx_se_list_acc, inst) + end + | (idx1, se1):: idx_se_list1', (idx2, se2):: idx_se_list2' -> + let idx = exp_partial_join idx1 idx2 in + let strexp' = strexp_partial_join mode se1 se2 in + let idx_se_list_new = (idx, strexp') :: idx_se_list_acc in + f_idx_se_list inst size idx_se_list_new idx_se_list1' idx_se_list2' in + + match strexp1, strexp2 with + | Sil.Eexp (e1, inst1), Sil.Eexp (e2, inst2) -> + Sil.Eexp (exp_partial_join e1 e2, Sil.inst_partial_join inst1 inst2) + | Sil.Estruct (fld_se_list1, inst1), Sil.Estruct (fld_se_list2, inst2) -> + let inst = Sil.inst_partial_join inst1 inst2 in + f_fld_se_list inst mode [] fld_se_list1 fld_se_list2 + | Sil.Earray (size1, idx_se_list1, inst1), Sil.Earray (size2, idx_se_list2, inst2) -> + let size = size_partial_join size1 size2 in + let inst = Sil.inst_partial_join inst1 inst2 in + f_idx_se_list inst size [] idx_se_list1 idx_se_list2 + | _ -> L.d_strln "no match in strexp_partial_join"; raise Fail + +let rec strexp_partial_meet (strexp1: Sil.strexp) (strexp2: Sil.strexp) : Sil.strexp = + + let construct side rev_list ref_list = + let construct_offset_se (off, se) = (off, strexp_construct_fresh side se) in + let acc = list_map construct_offset_se ref_list in + list_rev_with_acc acc rev_list in + + let rec f_fld_se_list inst acc fld_se_list1 fld_se_list2 = + match fld_se_list1, fld_se_list2 with + | [], [] -> + Sil.Estruct (list_rev acc, inst) + | [], _ -> + Sil.Estruct (construct Rhs acc fld_se_list2, inst) + | _, [] -> + Sil.Estruct (construct Lhs acc fld_se_list1, inst) + | (fld1, se1):: fld_se_list1', (fld2, se2):: fld_se_list2' -> + let comparison = Sil.fld_compare fld1 fld2 in + if comparison < 0 then + let se' = strexp_construct_fresh Lhs se1 in + let acc_new = (fld1, se'):: acc in + f_fld_se_list inst acc_new fld_se_list1' fld_se_list2 + else if comparison > 0 then + let se' = strexp_construct_fresh Rhs se2 in + let acc_new = (fld2, se'):: acc in + f_fld_se_list inst acc_new fld_se_list1 fld_se_list2' + else + let strexp' = strexp_partial_meet se1 se2 in + let acc_new = (fld1, strexp') :: acc in + f_fld_se_list inst acc_new fld_se_list1' fld_se_list2' in + + let rec f_idx_se_list inst size acc idx_se_list1 idx_se_list2 = + match idx_se_list1, idx_se_list2 with + | [],[] -> + Sil.Earray (size, list_rev acc, inst) + | [], _ -> + Sil.Earray (size, construct Rhs acc idx_se_list2, inst) + | _, [] -> + Sil.Earray (size, construct Lhs acc idx_se_list1, inst) + | (idx1, se1):: idx_se_list1', (idx2, se2):: idx_se_list2' -> + let idx = exp_partial_meet idx1 idx2 in + let se' = strexp_partial_meet se1 se2 in + let acc_new = (idx, se') :: acc in + f_idx_se_list inst size acc_new idx_se_list1' idx_se_list2' in + + match strexp1, strexp2 with + | Sil.Eexp (e1, inst1), Sil.Eexp (e2, inst2) -> + Sil.Eexp (exp_partial_meet e1 e2, Sil.inst_partial_meet inst1 inst2) + | Sil.Estruct (fld_se_list1, inst1), Sil.Estruct (fld_se_list2, inst2) -> + let inst = Sil.inst_partial_meet inst1 inst2 in + f_fld_se_list inst [] fld_se_list1 fld_se_list2 + | Sil.Earray (size1, idx_se_list1, inst1), Sil.Earray (size2, idx_se_list2, inst2) + when Sil.exp_equal size1 size2 -> + let inst = Sil.inst_partial_meet inst1 inst2 in + f_idx_se_list inst size1 [] idx_se_list1 idx_se_list2 + | _ -> (L.d_strln "failure reason 52"; raise Fail) + +(** {2 Join and Meet for kind, hpara, hpara_dll} *) + +let kind_join k1 k2 = match k1, k2 with + | Sil.Lseg_PE, _ -> Sil.Lseg_PE + | _, Sil.Lseg_PE -> Sil.Lseg_PE + | Sil.Lseg_NE, Sil.Lseg_NE -> Sil.Lseg_NE + +let kind_meet k1 k2 = match k1, k2 with + | Sil.Lseg_NE, _ -> Sil.Lseg_NE + | _, Sil.Lseg_NE -> Sil.Lseg_NE + | Sil.Lseg_PE, Sil.Lseg_PE -> Sil.Lseg_PE + +let hpara_partial_join (hpara1: Sil.hpara) (hpara2: Sil.hpara) : Sil.hpara = + if Match.hpara_match_with_impl true hpara2 hpara1 then + hpara1 + else if Match.hpara_match_with_impl true hpara1 hpara2 then + hpara2 + else + (L.d_strln "failure reason 53"; raise Fail) + +let hpara_partial_meet (hpara1: Sil.hpara) (hpara2: Sil.hpara) : Sil.hpara = + if Match.hpara_match_with_impl true hpara2 hpara1 then + hpara2 + else if Match.hpara_match_with_impl true hpara1 hpara2 then + hpara1 + else + (L.d_strln "failure reason 54"; raise Fail) + +let hpara_dll_partial_join (hpara1: Sil.hpara_dll) (hpara2: Sil.hpara_dll) : Sil.hpara_dll = + if Match.hpara_dll_match_with_impl true hpara2 hpara1 then + hpara1 + else if Match.hpara_dll_match_with_impl true hpara1 hpara2 then + hpara2 + else + (L.d_strln "failure reason 55"; raise Fail) + +let hpara_dll_partial_meet (hpara1: Sil.hpara_dll) (hpara2: Sil.hpara_dll) : Sil.hpara_dll = + if Match.hpara_dll_match_with_impl true hpara2 hpara1 then + hpara2 + else if Match.hpara_dll_match_with_impl true hpara1 hpara2 then + hpara1 + else + (L.d_strln "failure reason 56"; raise Fail) + +(** {2 Join and Meet for hpred} *) + +let hpred_partial_join mode (todo: Sil.exp * Sil.exp * Sil.exp) (hpred1: Sil.hpred) (hpred2: Sil.hpred) : Sil.hpred = + let e1, e2, e = todo in + match hpred1, hpred2 with + | Sil.Hpointsto (e1, se1, te1), Sil.Hpointsto (e2, se2, te2) -> + let te = exp_partial_join te1 te2 in + Prop.mk_ptsto e (strexp_partial_join mode se1 se2) te + | Sil.Hlseg (k1, hpara1, root1, next1, shared1), Sil.Hlseg (k2, hpara2, root2, next2, shared2) -> + let hpara' = hpara_partial_join hpara1 hpara2 in + let next' = exp_partial_join next1 next2 in + let shared' = exp_list_partial_join shared1 shared2 in + Prop.mk_lseg (kind_join k1 k2) hpara' e next' shared' + | Sil.Hdllseg (k1, para1, iF1, oB1, oF1, iB1, shared1), + Sil.Hdllseg (k2, para2, iF2, oB2, oF2, iB2, shared2) -> + let fwd1 = Sil.exp_equal e1 iF1 in + let fwd2 = Sil.exp_equal e2 iF2 in + let hpara' = hpara_dll_partial_join para1 para2 in + let iF', iB' = + if (fwd1 && fwd2) then (e, exp_partial_join iB1 iB2) + else if (not fwd1 && not fwd2) then (exp_partial_join iF1 iF2, e) + else (L.d_strln "failure reason 57"; raise Fail) in + let oF' = exp_partial_join oF1 oF2 in + let oB' = exp_partial_join oB1 oB2 in + let shared' = exp_list_partial_join shared1 shared2 in + Prop.mk_dllseg (kind_join k1 k2) hpara' iF' oB' oF' iB' shared' + | _ -> + assert false + +let hpred_partial_meet (todo: Sil.exp * Sil.exp * Sil.exp) (hpred1: Sil.hpred) (hpred2: Sil.hpred) : Sil.hpred = + let e1, e2, e = todo in + match hpred1, hpred2 with + | Sil.Hpointsto (e1, se1, te1), Sil.Hpointsto (e2, se2, te2) when Sil.exp_equal te1 te2 -> + Prop.mk_ptsto e (strexp_partial_meet se1 se2) te1 + | Sil.Hpointsto _, _ | _, Sil.Hpointsto _ -> + (L.d_strln "failure reason 58"; raise Fail) + | Sil.Hlseg (k1, hpara1, root1, next1, shared1), Sil.Hlseg (k2, hpara2, root2, next2, shared2) -> + let hpara' = hpara_partial_meet hpara1 hpara2 in + let next' = exp_partial_meet next1 next2 in + let shared' = exp_list_partial_meet shared1 shared2 in + Prop.mk_lseg (kind_meet k1 k2) hpara' e next' shared' + | Sil.Hdllseg (k1, para1, iF1, oB1, oF1, iB1, shared1), + Sil.Hdllseg (k2, para2, iF2, oB2, oF2, iB2, shared2) -> + let fwd1 = Sil.exp_equal e1 iF1 in + let fwd2 = Sil.exp_equal e2 iF2 in + let hpara' = hpara_dll_partial_meet para1 para2 in + let iF', iB' = + if (fwd1 && fwd2) then (e, exp_partial_meet iB1 iB2) + else if (not fwd1 && not fwd2) then (exp_partial_meet iF1 iF2, e) + else (L.d_strln "failure reason 59"; raise Fail) in + let oF' = exp_partial_meet oF1 oF2 in + let oB' = exp_partial_meet oB1 oB2 in + let shared' = exp_list_partial_meet shared1 shared2 in + Prop.mk_dllseg (kind_meet k1 k2) hpara' iF' oB' oF' iB' shared' + | _ -> + assert false + +(** {2 Join and Meet for Sigma} *) + +let find_hpred_by_address (e: Sil.exp) (sigma: sigma) : Sil.hpred option * sigma = + let is_root_for_e e' = + match (Prover.is_root Prop.prop_emp e' e) with + | None -> false + | Some _ -> true in + let contains_e = function + | Sil.Hpointsto (e', _, _) -> is_root_for_e e' + | Sil.Hlseg (_, _, e', _, _) -> is_root_for_e e' + | Sil.Hdllseg (_, _, iF, _, _, iB, _) -> is_root_for_e iF || is_root_for_e iB in + let rec f sigma_acc = function + | [] -> None, sigma + | hpred:: sigma -> + if contains_e hpred then + Some hpred, (list_rev sigma_acc) @ sigma + else + f (hpred:: sigma_acc) sigma in + f [] sigma + +let same_pred (hpred1: Sil.hpred) (hpred2: Sil.hpred) : bool = + match hpred1, hpred2 with + | Sil.Hpointsto _, Sil.Hpointsto _ -> true + | Sil.Hlseg _, Sil.Hlseg _ -> true + | Sil.Hdllseg _, Sil.Hdllseg _ -> true + | _ -> false + +(* check that applying renaming to the lhs / rhs of [sigma_new] +* gives [sigma] and that the renaming is injective *) + +let sigma_renaming_check (lhs: side) (sigma: sigma) (sigma_new: sigma) = + (* apply the lhs / rhs of the renaming to sigma, + * and check that the renaming of primed vars is injective *) + let fav_sigma = Prop.sigma_fav sigma_new in + let sub = Rename.to_subst_proj lhs fav_sigma in + let sigma' = Prop.sigma_sub sub sigma_new in + sigma_equal sigma sigma' + +let sigma_renaming_check_lhs = sigma_renaming_check Lhs +let sigma_renaming_check_rhs = sigma_renaming_check Rhs + +let rec sigma_partial_join' mode (sigma_acc: sigma) + (sigma1_in: sigma) (sigma2_in: sigma) : (sigma * sigma * sigma) = + + let lookup_and_expand side e e' = + match (Rename.get_others side e, side) with + | None, _ -> (L.d_strln "failure reason 60"; raise Fail) + | Some(e_res, e_op), Lhs -> (e_res, exp_partial_join e' e_op) + | Some(e_res, e_op), Rhs -> (e_res, exp_partial_join e_op e') in + + let join_list_and_non side root' hlseg e opposite = + match hlseg with + | Sil.Hlseg (_, hpara, root, next, shared) -> + let next' = do_side side exp_partial_join next opposite in + let shared' = Rename.lookup_list side shared in + CheckJoin.add side root next; + Sil.Hlseg (Sil.Lseg_PE, hpara, root', next', shared') + + | Sil.Hdllseg (k, hpara, iF, oB, oF, iB, shared) + when Sil.exp_equal iF e -> + let oF' = do_side side exp_partial_join oF opposite in + let shared' = Rename.lookup_list side shared in + let oB', iB' = lookup_and_expand side oB iB in + (* + let oB' = Rename.lookup side oB in + let iB' = Rename.lookup side iB in + *) + CheckJoin.add side iF oF; + CheckJoin.add side oB iB; + Sil.Hdllseg (Sil.Lseg_PE, hpara, root', oB', oF', iB', shared') + + | Sil.Hdllseg (k, hpara, iF, oB, oF, iB, shared) + when Sil.exp_equal iB e -> + let oB' = do_side side exp_partial_join oB opposite in + let shared' = Rename.lookup_list side shared in + let oF', iF' = lookup_and_expand side oF iF in + (* + let oF' = Rename.lookup side oF in + let iF' = Rename.lookup side iF in + *) + CheckJoin.add side iF oF; + CheckJoin.add side oB iB; + Sil.Hdllseg (Sil.Lseg_PE, hpara, iF', oB', oF', root', shared') + + | _ -> assert false in + + let update_list side lseg root' = + match lseg with + | Sil.Hlseg (k, hpara, _, next, shared) -> + let next' = Rename.lookup side next + and shared' = Rename.lookup_list_todo side shared in + Sil.Hlseg (k, hpara, root', next', shared') + | _ -> assert false in + + let update_dllseg side dllseg iF iB = + match dllseg with + | Sil.Hdllseg (k, hpara, _, oB, oF, _, shared) -> + let oB' = Rename.lookup side oB + and oF' = Rename.lookup side oF + and shared' = Rename.lookup_list_todo side shared in + Sil.Hdllseg (k, hpara, iF, oB', oF', iB, shared') + | _ -> assert false in + + (* Drop the part of 'other' sigma corresponding to 'target' sigma if possible. + 'side' describes that target is Lhs or Rhs. + 'todo' describes the start point. *) + + let cut_sigma side todo (target: sigma) (other: sigma) = + let list_is_empty l = if l != [] then (L.d_strln "failure reason 61"; raise Fail) in + let x = Todo.take () in + Todo.push todo; + let res = + match side with + | Lhs -> + let res, target', other' = sigma_partial_join' mode [] target other in + list_is_empty target'; + sigma_renaming_check_lhs target res; + other' + | Rhs -> + let res, other', target' = sigma_partial_join' mode [] other target in + list_is_empty target'; + sigma_renaming_check_rhs target res; + other' in + Todo.set x; + res in + + let cut_lseg side todo lseg sigma = + match lseg with + | Sil.Hlseg (_, hpara, root, next, shared) -> + let _, sigma_lseg = Sil.hpara_instantiate hpara root next shared in + cut_sigma side todo sigma_lseg sigma + | _ -> assert false in + + let cut_dllseg side todo root lseg sigma = + match lseg with + | Sil.Hdllseg (_, hpara, _, oB, oF, _, shared) -> + let _, sigma_dllseg = Sil.hpara_dll_instantiate hpara root oB oF shared in + cut_sigma side todo sigma_dllseg sigma + | _ -> assert false in + + try + let todo_curr = Todo.pop () in + let e1, e2, e = todo_curr in + if !Config.trace_join then begin + L.d_strln ".... sigma_partial_join' ...."; + L.d_str "TODO: "; Sil.d_exp e1; L.d_str ","; Sil.d_exp e2; L.d_str ","; Sil.d_exp e; L.d_ln (); + L.d_strln "SIGMA1 ="; Prop.d_sigma sigma1_in; L.d_ln (); + L.d_strln "SIGMA2 ="; Prop.d_sigma sigma2_in; L.d_ln (); + L.d_ln () + end; + let hpred_opt1, sigma1 = find_hpred_by_address e1 sigma1_in in + let hpred_opt2, sigma2 = find_hpred_by_address e2 sigma2_in in + match hpred_opt1, hpred_opt2 with + | None, None -> + sigma_partial_join' mode sigma_acc sigma1 sigma2 + + | Some (Sil.Hlseg (k, _, _, _, _) as lseg), None + | Some (Sil.Hdllseg (k, _, _, _, _, _, _) as lseg), None -> + if (not !Config.nelseg) || (Sil.lseg_kind_equal k Sil.Lseg_PE) then + let sigma_acc' = join_list_and_non Lhs e lseg e1 e2 :: sigma_acc in + sigma_partial_join' mode sigma_acc' sigma1 sigma2 + else + (L.d_strln "failure reason 62"; raise Fail) + + | None, Some (Sil.Hlseg (k, _, _, _, _) as lseg) + | None, Some (Sil.Hdllseg (k, _, _, _, _, _, _) as lseg) -> + if (not !Config.nelseg) || (Sil.lseg_kind_equal k Sil.Lseg_PE) then + let sigma_acc' = join_list_and_non Rhs e lseg e2 e1 :: sigma_acc in + sigma_partial_join' mode sigma_acc' sigma1 sigma2 + else + (L.d_strln "failure reason 63"; raise Fail) + + | None, _ | _, None -> (L.d_strln "failure reason 64"; raise Fail) + + | Some (hpred1), Some (hpred2) when same_pred hpred1 hpred2 -> + let hpred_res1 = hpred_partial_join mode todo_curr hpred1 hpred2 in + sigma_partial_join' mode (hpred_res1:: sigma_acc) sigma1 sigma2 + + | Some (Sil.Hlseg _ as lseg), Some (hpred2) -> + let sigma2' = cut_lseg Lhs todo_curr lseg (hpred2:: sigma2) in + let sigma_acc' = update_list Lhs lseg e :: sigma_acc in + sigma_partial_join' mode sigma_acc' sigma1 sigma2' + + | Some (hpred1), Some (Sil.Hlseg _ as lseg) -> + let sigma1' = cut_lseg Rhs todo_curr lseg (hpred1:: sigma1) in + let sigma_acc' = update_list Rhs lseg e :: sigma_acc in + sigma_partial_join' mode sigma_acc' sigma1' sigma2 + + | Some (Sil.Hdllseg (_, _, iF1, _, _, iB1, _) as dllseg), Some (hpred2) + when Sil.exp_equal e1 iF1 -> + let iB_res = exp_partial_join iB1 e2 in + let sigma2' = cut_dllseg Lhs todo_curr iF1 dllseg (hpred2:: sigma2) in + let sigma_acc' = update_dllseg Lhs dllseg e iB_res :: sigma_acc in + CheckJoin.add Lhs iF1 iB1; (* add equality iF1=iB1 *) + sigma_partial_join' mode sigma_acc' sigma1 sigma2' + + | Some (Sil.Hdllseg (_, _, iF1, _, _, iB1, _) as dllseg), Some (hpred2) + (* when Sil.exp_equal e1 iB1 *) -> + let iF_res = exp_partial_join iF1 e2 in + let sigma2' = cut_dllseg Lhs todo_curr iB1 dllseg (hpred2:: sigma2) in + let sigma_acc' = update_dllseg Lhs dllseg iF_res e :: sigma_acc in + CheckJoin.add Lhs iF1 iB1; (* add equality iF1=iB1 *) + sigma_partial_join' mode sigma_acc' sigma1 sigma2' + + | Some (hpred1), Some (Sil.Hdllseg (_, _, iF2, _, _, iB2, _) as dllseg) + when Sil.exp_equal e2 iF2 -> + let iB_res = exp_partial_join e1 iB2 in + let sigma1' = cut_dllseg Rhs todo_curr iF2 dllseg (hpred1:: sigma1) in + let sigma_acc' = update_dllseg Rhs dllseg e iB_res :: sigma_acc in + CheckJoin.add Rhs iF2 iB2; (* add equality iF2=iB2 *) + sigma_partial_join' mode sigma_acc' sigma1' sigma2 + + | Some (hpred1), Some (Sil.Hdllseg (_, _, iF2, _, _, iB2, _) as dllseg) -> + let iF_res = exp_partial_join e1 iF2 in + let sigma1' = cut_dllseg Rhs todo_curr iB2 dllseg (hpred1:: sigma1) in + let sigma_acc' = update_dllseg Rhs dllseg iF_res e :: sigma_acc in + CheckJoin.add Rhs iF2 iB2; (* add equality iF2=iB2 *) + sigma_partial_join' mode sigma_acc' sigma1' sigma2 + + | Some (Sil.Hpointsto _), Some (Sil.Hpointsto _) -> + assert false (* Should be handled by a guarded case *) + + with Todo.Empty -> + match sigma1_in, sigma2_in with + | _:: _, _:: _ -> L.d_strln "todo is empty, but the sigmas are not"; raise Fail + | _ -> sigma_acc, sigma1_in, sigma2_in + +let sigma_partial_join mode (sigma1: sigma) (sigma2: sigma) : (sigma * sigma * sigma) = + CheckJoin.init mode sigma1 sigma2; + let lost_little = CheckJoin.lost_little in + let s1, s2, s3 = sigma_partial_join' mode [] sigma1 sigma2 in + try + if Rename.check lost_little then + (CheckJoin.final (); (s1, s2, s3)) + else begin + L.d_strln "failed Rename.check"; + CheckJoin.final (); + raise Fail + end + with + | exn -> (CheckJoin.final (); raise exn) + +let rec sigma_partial_meet' (sigma_acc: sigma) (sigma1_in: sigma) (sigma2_in: sigma) : sigma = + try + let todo_curr = Todo.pop () in + let e1, e2, e = todo_curr in + L.d_strln ".... sigma_partial_meet' ...."; + L.d_str "TODO: "; Sil.d_exp e1; L.d_str ","; Sil.d_exp e2; L.d_str ","; Sil.d_exp e; L.d_ln (); + L.d_str "PROP1="; Prop.d_sigma sigma1_in; L.d_ln (); + L.d_str "PROP2="; Prop.d_sigma sigma2_in; L.d_ln (); + L.d_ln (); + let hpred_opt1, sigma1 = find_hpred_by_address e1 sigma1_in in + let hpred_opt2, sigma2 = find_hpred_by_address e2 sigma2_in in + match hpred_opt1, hpred_opt2 with + | None, None -> + sigma_partial_meet' sigma_acc sigma1 sigma2 + + | Some hpred, None -> + let hpred' = hpred_construct_fresh Lhs hpred in + let sigma_acc' = hpred' :: sigma_acc in + sigma_partial_meet' sigma_acc' sigma1 sigma2 + + | None, Some hpred -> + let hpred' = hpred_construct_fresh Rhs hpred in + let sigma_acc' = hpred' :: sigma_acc in + sigma_partial_meet' sigma_acc' sigma1 sigma2 + + | Some (hpred1), Some (hpred2) when same_pred hpred1 hpred2 -> + let hpred' = hpred_partial_meet todo_curr hpred1 hpred2 in + sigma_partial_meet' (hpred':: sigma_acc) sigma1 sigma2 + + | Some _, Some _ -> + (L.d_strln "failure reason 65"; raise Fail) + + with Todo.Empty -> + match sigma1_in, sigma2_in with + | [], [] -> sigma_acc + | _, _ -> L.d_strln "todo is empty, but the sigmas are not"; raise Fail + +let sigma_partial_meet (sigma1: sigma) (sigma2: sigma) : sigma = + sigma_partial_meet' [] sigma1 sigma2 + +let widening_top = Sil.Int.of_int64 Int64.max_int -- Sil.Int.of_int 1000 (* nearly max_int but not so close to overflow *) +let widening_bottom = Sil.Int.of_int64 Int64.min_int ++ Sil.Int.of_int 1000 (* nearly min_int but not so close to underflow *) + +(** {2 Join and Meet for Pi} *) +let pi_partial_join mode + (ep1: Prop.exposed Prop.t) (ep2: Prop.exposed Prop.t) + (pi1: Sil.atom list) (pi2: Sil.atom list) : Sil.atom list += + let exp_is_const = function + (* | Sil.Var id -> is_normal id *) + | Sil.Const _ -> true + (* | Sil.Lvar _ -> true *) + | _ -> false in + let get_array_size prop = + (* find some array size in the prop, to be used as heuritic for upper bound in widening *) + let size_list = ref [] in + let do_hpred = function + | Sil.Hpointsto (_, Sil.Earray (Sil.Const (Sil.Cint n), _, _), _) -> + (if Sil.Int.geq n Sil.Int.one then size_list := n::!size_list) + | _ -> () in + list_iter do_hpred (Prop.get_sigma prop); + !size_list in + let bounds = + let bounds1 = get_array_size ep1 in + let bounds2 = get_array_size ep2 in + let bounds_sorted = list_sort Sil.Int.compare_value (bounds1@bounds2) in + list_rev (list_remove_duplicates Sil.Int.compare_value bounds_sorted) in + let widening_atom a = + (* widening heuristic for upper bound: take the size of some array, -2 and -1 *) + match Prop.atom_exp_le_const a, bounds with + | Some (e, n), size:: _ -> + let first_try = Sil.Int.sub size Sil.Int.one in + let second_try = Sil.Int.sub size Sil.Int.two in + let bound = + if Sil.Int.leq n first_try then + if Sil.Int.leq n second_try then second_try else first_try + else widening_top in + let a' = Prop.mk_inequality (Sil.BinOp(Sil.Le, e, Sil.exp_int bound)) in + Some a' + | Some (e, n), [] -> + let bound = widening_top in + let a' = Prop.mk_inequality (Sil.BinOp(Sil.Le, e, Sil.exp_int bound)) in + Some a' + | _ -> + begin + match Prop.atom_const_lt_exp a with + | None -> None + | Some (n, e) -> + let bound = if Sil.Int.leq Sil.Int.minus_one n then Sil.Int.minus_one else widening_bottom in + let a' = Prop.mk_inequality (Sil.BinOp(Sil.Lt, Sil.exp_int bound, e)) in + Some a' + end in + let is_stronger_le e n a = + match Prop.atom_exp_le_const a with + | None -> false + | Some (e', n') -> Sil.exp_equal e e' && Sil.Int.lt n' n in + let is_stronger_lt n e a = + match Prop.atom_const_lt_exp a with + | None -> false + | Some (n', e') -> Sil.exp_equal e e' && Sil.Int.lt n n' in + let join_atom_check_pre p a = + (* check for atoms in pre mode: fail if the negation is implied by the other side *) + let not_a = Prop.atom_negate a in + if (Prover.check_atom p not_a) then + (L.d_str "join_atom_check failed on "; Sil.d_atom a; L.d_ln (); raise Fail) in + let join_atom_check_attribute p a = + (* check for attribute: fail if the attribute is not in the other side *) + if not (Prover.check_atom p a) then + (L.d_str "join_atom_check_attribute failed on "; Sil.d_atom a; L.d_ln (); raise Fail) in + let join_atom side p_op pi_op a = + (* try to find the atom corresponding to a on the other side, and check if it is implied *) + match Rename.get_other_atoms side a with + | None -> None + | Some (a_res, a_op) -> + if mode = JoinState.Pre then join_atom_check_pre p_op a_op; + if Prop.atom_is_attribute a then join_atom_check_attribute p_op a_op; + if not (Prover.check_atom p_op a_op) then None + else begin + match Prop.atom_exp_le_const a_op with + | None -> + begin + match Prop.atom_const_lt_exp a_op with + | None -> Some a_res + | Some (n, e) -> if list_exists (is_stronger_lt n e) pi_op then (widening_atom a_res) else Some a_res + end + | Some (e, n) -> + if list_exists (is_stronger_le e n) pi_op then (widening_atom a_res) else Some a_res + end in + let handle_atom_with_widening size p_op pi_op atom_list a = + (* find a join for the atom, if it fails apply widening heuristing and try again *) + match join_atom size p_op pi_op a with + | None -> + (match widening_atom a with + | None -> atom_list + | Some a' -> + (match join_atom size p_op pi_op a' with + | None -> atom_list + | Some a' -> a' :: atom_list)) + | Some a' -> a' :: atom_list in + let filter_atom = function + | Sil.Aneq(e, e') | Sil.Aeq(e, e') + when (exp_is_const e && exp_is_const e') -> + true + | Sil.Aneq(Sil.Var id, e') | Sil.Aneq(e', Sil.Var id) + | Sil.Aeq(Sil.Var id, e') | Sil.Aeq(e', Sil.Var id) + when (exp_is_const e') -> + true + | Sil.Aneq _ -> false + | e -> Prop.atom_is_inequality e in + begin + if !Config.trace_join then begin + L.d_str "pi1: "; Prop.d_pi pi1; L.d_ln (); + L.d_str "pi2: "; Prop.d_pi pi2; L.d_ln () + end; + let atom_list1 = + let p2 = Prop.normalize ep2 in + list_fold_left (handle_atom_with_widening Lhs p2 pi2) [] pi1 in + if !Config.trace_join then (L.d_str "atom_list1: "; Prop.d_pi atom_list1; L.d_ln ()); + let atom_list_combined = + let p1 = Prop.normalize ep1 in + list_fold_left (handle_atom_with_widening Rhs p1 pi1) atom_list1 pi2 in + if !Config.trace_join then (L.d_str "atom_list_combined: "; Prop.d_pi atom_list_combined; L.d_ln ()); + let atom_list_filtered = + list_filter filter_atom atom_list_combined in + if !Config.trace_join then (L.d_str "atom_list_filtered: "; Prop.d_pi atom_list_filtered; L.d_ln ()); + let atom_list_res = + list_rev atom_list_filtered in + atom_list_res + end + +let pi_partial_meet (p: Prop.normal Prop.t) (ep1: 'a Prop.t) (ep2: 'b Prop.t) : Prop.normal Prop.t = + let sub1 = Rename.to_subst_emb Lhs in + let sub2 = Rename.to_subst_emb Rhs in + + let dom1 = Ident.idlist_to_idset (Sil.sub_domain sub1) in + let dom2 = Ident.idlist_to_idset (Sil.sub_domain sub2) in + + let handle_atom sub dom atom = + let fav_list = Sil.fav_to_list (Sil.atom_fav atom) in + if list_for_all (fun id -> Ident.IdentSet.mem id dom) fav_list then + Sil.atom_sub sub atom + else (L.d_str "handle_atom failed on "; Sil.d_atom atom; L.d_ln (); raise Fail) in + let f1 p' atom = + Prop.prop_atom_and p' (handle_atom sub1 dom1 atom) in + let f2 p' atom = + Prop.prop_atom_and p' (handle_atom sub2 dom2 atom) in + + let pi1 = Prop.get_pi ep1 in + let pi2 = Prop.get_pi ep2 in + + let p_pi1 = list_fold_left f1 p pi1 in + let p_pi2 = list_fold_left f2 p_pi1 pi2 in + if (Prover.check_inconsistency_base p_pi2) then (L.d_strln "check_inconsistency_base failed"; raise Fail) + else p_pi2 + +(** {2 Join and Meet for Prop} *) + +let eprop_partial_meet (ep1: 'a Prop.t) (ep2: 'b Prop.t) : 'c Prop.t = + SymOp.pay(); (* pay one symop *) + let sigma1 = Prop.get_sigma ep1 in + let sigma2 = Prop.get_sigma ep2 in + + let es1 = sigma_get_start_lexps_sort sigma1 in + let es2 = sigma_get_start_lexps_sort sigma2 in + let es = list_merge_sorted_nodup Sil.exp_compare [] es1 es2 in + + let sub_check _ = + let sub1 = Prop.get_sub ep1 in + let sub2 = Prop.get_sub ep2 in + let range1 = Sil.sub_range sub1 in + let f e = Sil.fav_for_all (Sil.exp_fav e) Ident.is_normal in + Sil.sub_equal sub1 sub2 && list_for_all f range1 in + + if not (sub_check ()) then + (L.d_strln "sub_check() failed"; raise Fail) + else begin + let todos = list_map (fun x -> (x, x, x)) es in + list_iter Todo.push todos; + let sigma_new = sigma_partial_meet sigma1 sigma2 in + let ep = Prop.replace_sigma sigma_new ep1 in + let ep' = Prop.replace_pi [] ep in + let p' = Prop.normalize ep' in + let p'' = pi_partial_meet p' ep1 ep2 in + let res = Prop.prop_rename_primed_footprint_vars p'' in + res + end + +let prop_partial_meet p1 p2 = + Rename.init (); FreshVarExp.init (); Todo.init (); + try + let res = eprop_partial_meet p1 p2 in + Rename.final (); FreshVarExp.final (); Todo.final (); + Some res + with exn -> + begin + Rename.final (); FreshVarExp.final (); Todo.final (); + match exn with + | Fail -> None + | _ -> raise exn + end + +let eprop_partial_join' mode (ep1: Prop.exposed Prop.t) (ep2: Prop.exposed Prop.t) : Prop.normal Prop.t = + SymOp.pay(); (* pay one symop *) + let sigma1 = Prop.get_sigma ep1 in + let sigma2 = Prop.get_sigma ep2 in + let es1 = sigma_get_start_lexps_sort sigma1 in + let es2 = sigma_get_start_lexps_sort sigma2 in + + let simple_check = list_length es1 = list_length es2 in + let rec expensive_check es1' es2' = + match (es1', es2') with + | [], [] -> true + | [], _:: _ | _:: _, [] -> false + | e1:: es1'', e2:: es2'' -> + Sil.exp_equal e1 e2 && expensive_check es1'' es2'' in + let sub_common, eqs_from_sub1, eqs_from_sub2 = + let sub1 = Prop.get_sub ep1 in + let sub2 = Prop.get_sub ep2 in + let sub_common, sub1_only, sub2_only = Sil.sub_symmetric_difference sub1 sub2 in + let sub_common_normal, sub_common_other = + let f e = Sil.fav_for_all (Sil.exp_fav e) Ident.is_normal in + Sil.sub_range_partition f sub_common in + let eqs1, eqs2 = + let sub_to_eqs sub = list_map (fun (id, e) -> Sil.Aeq(Sil.Var id, e)) (Sil.sub_to_list sub) in + let eqs1 = sub_to_eqs sub1_only @ sub_to_eqs sub_common_other in + let eqs2 = sub_to_eqs sub2_only in + (eqs1, eqs2) in + (sub_common_normal, eqs1, eqs2) in + + if not (simple_check && expensive_check es1 es2) then + begin + if not simple_check then L.d_strln "simple_check failed" + else L.d_strln "expensive_check failed"; + raise Fail + end; + let todos = list_map (fun x -> (x, x, x)) es1 in + list_iter Todo.push todos; + match sigma_partial_join mode sigma1 sigma2 with + | sigma_new, [], [] -> + L.d_strln "sigma_partial_join succeeded"; + let ep_sub = + let ep = Prop.replace_pi [] ep1 in + Prop.replace_sub sub_common ep in + let p_sub_sigma = + Prop.normalize (Prop.replace_sigma sigma_new ep_sub) in + let p_sub_sigma_pi = + let pi1 = (Prop.get_pi ep1) @ eqs_from_sub1 in + let pi2 = (Prop.get_pi ep2) @ eqs_from_sub2 in + let pi' = pi_partial_join mode ep1 ep2 pi1 pi2 in + L.d_strln "pi_partial_join succeeded"; + let pi_from_fresh_vars = FreshVarExp.get_induced_pi () in + let pi_all = pi' @ pi_from_fresh_vars in + list_fold_left Prop.prop_atom_and p_sub_sigma pi_all in + p_sub_sigma_pi + | _ -> + L.d_strln "leftovers not empty"; raise Fail + +let footprint_partial_join' (p1: Prop.normal Prop.t) (p2: Prop.normal Prop.t) : Prop.normal Prop.t * Prop.normal Prop.t = + if not !Config.footprint then p1, p2 + else begin + let fp1 = Prop.extract_footprint p1 in + let fp2 = Prop.extract_footprint p2 in + let efp = eprop_partial_join' JoinState.Pre fp1 fp2 in + let fp_pi = (* Prop.get_pure efp in *) + let fp_pi0 = Prop.get_pure efp in + let f a = Sil.fav_for_all (Sil.atom_fav a) Ident.is_footprint in + list_filter f fp_pi0 in + let fp_sigma = (* Prop.get_sigma efp in *) + let fp_sigma0 = Prop.get_sigma efp in + let f a = Sil.fav_exists (Sil.hpred_fav a) (fun a -> not (Ident.is_footprint a)) in + if list_exists f fp_sigma0 then (L.d_strln "failure reason 66"; raise Fail); + fp_sigma0 in + let ep1' = Prop.replace_sigma_footprint fp_sigma (Prop.replace_pi_footprint fp_pi p1) in + let ep2' = Prop.replace_sigma_footprint fp_sigma (Prop.replace_pi_footprint fp_pi p2) in + Prop.normalize ep1', Prop.normalize ep2' + end + +let prop_partial_join pname tenv mode p1 p2 = + let res_by_implication_only = + if !Config.footprint then None + else if Prover.check_implication pname tenv p1 (Prop.expose p2) then Some p2 + else if Prover.check_implication pname tenv p2 (Prop.expose p1) then Some p1 + else None in + match res_by_implication_only with + | None -> + begin + (if !Config.footprint then JoinState.set_footprint true); + Rename.init (); FreshVarExp.init (); Todo.init (); + try + let p1', p2' = footprint_partial_join' p1 p2 in + let rename_footprint = Rename.reset () in + Todo.reset rename_footprint; + let res = Some (eprop_partial_join' mode (Prop.expose p1') (Prop.expose p2')) in + (if !Config.footprint then JoinState.set_footprint false); + Rename.final (); FreshVarExp.final (); Todo.final (); + res + with exn -> + begin + Rename.final (); FreshVarExp.final (); Todo.final (); + (if !Config.footprint then JoinState.set_footprint false); + (match exn with Fail -> None | _ -> raise exn) + end + end + | Some _ -> res_by_implication_only + +let eprop_partial_join mode (ep1: Prop.exposed Prop.t) (ep2: Prop.exposed Prop.t) : Prop.normal Prop.t = + Rename.init (); FreshVarExp.init (); Todo.init (); + try + let res = eprop_partial_join' mode ep1 ep2 in + Rename.final (); FreshVarExp.final (); Todo.final (); + res + with exn -> (Rename.final (); FreshVarExp.final (); Todo.final (); raise exn) + +(** {2 Join and Meet for Propset} *) + +let list_reduce name dd f list = + let rec element_list_reduce acc (x, p1) = function + | [] -> ((x, p1), list_rev acc) + | (y, p2):: ys -> begin + L.d_strln ("COMBINE[" ^ name ^ "] ...."); + L.d_str "ENTRY1: "; L.d_ln (); dd x; L.d_ln (); + L.d_str "ENTRY2: "; L.d_ln (); dd y; L.d_ln (); + L.d_ln (); + match f x y with + | None -> + L.d_strln_color Red (".... COMBINE[" ^ name ^ "] FAILED ..."); + element_list_reduce ((y, p2):: acc) (x, p1) ys + | Some x' -> + L.d_strln_color Green (".... COMBINE[" ^ name ^ "] SUCCEEDED ...."); + L.d_strln "RESULT:"; dd x'; L.d_ln (); + element_list_reduce acc (x', p1) ys + end in + let rec reduce acc = function + | [] -> list_rev acc + | x:: xs -> + let (x', xs') = element_list_reduce [] x xs in + reduce (x':: acc) xs' in + reduce [] list + +let pathset_collapse_impl pname tenv pset = + let f x y = + if Prover.check_implication pname tenv x (Prop.expose y) then Some y + else if Prover.check_implication pname tenv y (Prop.expose x) then Some x + else None in + let plist = Paths.PathSet.elements pset in + let plist' = list_reduce "JOIN_IMPL" Prop.d_prop f plist in + Paths.PathSet.from_renamed_list plist' + +let jprop_partial_join mode jp1 jp2 = + let p1, p2 = Prop.expose (Specs.Jprop.to_prop jp1), Prop.expose (Specs.Jprop.to_prop jp2) in + try + let p = eprop_partial_join mode p1 p2 in + let p_renamed = Prop.prop_rename_primed_footprint_vars p in + Some (Specs.Jprop.Joined (0, p_renamed, jp1, jp2)) + with Fail -> None + +let jplist_collapse mode jplist = + let f = jprop_partial_join mode in + list_reduce "JOIN" Specs.Jprop.d_shallow f jplist + + +(** Add identifiers to a list of jprops *) +let jprop_list_add_ids jplist = + let seq_number = ref 0 in + let rec do_jprop = function + | Specs.Jprop.Prop (n, p) -> incr seq_number; Specs.Jprop.Prop (!seq_number, p) + | Specs.Jprop.Joined (n, p, jp1, jp2) -> + let jp1' = do_jprop jp1 in + let jp2' = do_jprop jp2 in + incr seq_number; + Specs.Jprop.Joined (!seq_number, p, jp1', jp2') in + list_map (fun (p, path) -> (do_jprop p, path)) jplist + +let proplist_collapse mode plist = + let jplist = list_map (fun (p, path) -> (Specs.Jprop.Prop (0, p), path)) plist in + let jplist_joined = jplist_collapse mode (jplist_collapse mode jplist) in + jprop_list_add_ids jplist_joined + +let proplist_collapse_pre plist = + let plist' = list_map (fun p -> (p, ())) plist in + list_map fst (proplist_collapse JoinState.Pre plist') + +let pathset_collapse pset = + let plist = Paths.PathSet.elements pset in + let plist' = proplist_collapse JoinState.Post plist in + Paths.PathSet.from_renamed_list (list_map (fun (p, path) -> (Specs.Jprop.to_prop p, path)) plist') + +let join_time = ref 0.0 + +let pathset_join + pname tenv (pset1: Paths.PathSet.t) (pset2: Paths.PathSet.t) +: Paths.PathSet.t * Paths.PathSet.t = + let mode = JoinState.Post in + let initial_time = Unix.gettimeofday () in + let pset_to_plist pset = + let f_list p pa acc = (p, pa) :: acc in + Paths.PathSet.fold f_list pset [] in + let ppalist1 = pset_to_plist pset1 in + let ppalist2 = pset_to_plist pset2 in + let rec join_proppath_plist ppalist2_acc ((p2, pa2) as ppa2) = function + | [] -> (ppa2, list_rev ppalist2_acc) + | ((p2', pa2') as ppa2') :: ppalist2_rest -> begin + L.d_strln ".... JOIN ...."; + L.d_strln "JOIN SYM HEAP1: "; Prop.d_prop p2; L.d_ln (); + L.d_strln "JOIN SYM HEAP2: "; Prop.d_prop p2'; L.d_ln (); L.d_ln (); + match prop_partial_join pname tenv mode p2 p2' with + | None -> + L.d_strln_color Red ".... JOIN FAILED ...."; L.d_ln (); + join_proppath_plist (ppa2':: ppalist2_acc) ppa2 ppalist2_rest + | Some p2'' -> + L.d_strln_color Green ".... JOIN SUCCEEDED ...."; + L.d_strln "RESULT SYM HEAP:"; Prop.d_prop p2''; L.d_ln (); L.d_ln (); + join_proppath_plist ppalist2_acc (p2'', Paths.Path.join pa2 pa2') ppalist2_rest + end in + let rec join ppalist1_cur ppalist2_acc = function + | [] -> (ppalist1_cur, ppalist2_acc) + | ppa2:: ppalist2_rest -> + let (ppa2', ppalist2_acc') = join_proppath_plist [] ppa2 ppalist2_acc in + let (ppa2'', ppalist2_rest') = join_proppath_plist [] ppa2' ppalist2_rest in + let (ppa2_new, ppalist1_cur') = join_proppath_plist [] ppa2'' ppalist1_cur in + join ppalist1_cur' (ppa2_new:: ppalist2_acc') ppalist2_rest' in + let _ppalist1_res, _ppalist2_res = join ppalist1 [] ppalist2 in + let ren l = list_map (fun (p, x) -> (Prop.prop_rename_primed_footprint_vars p, x)) l in + let ppalist1_res, ppalist2_res = ren _ppalist1_res, ren _ppalist2_res in + let res = (Paths.PathSet.from_renamed_list ppalist1_res, Paths.PathSet.from_renamed_list ppalist2_res) in + join_time := !join_time +. (Unix.gettimeofday () -. initial_time); + res + +(** +The meet operator does two things: +1) makes the result logically stronger (just like additive conjunction) +2) makes the result spatially larger (just like multiplicative conjunction). +Assuming that the meet operator forms a partial commutative monoid (soft assumption: it means +that the results are more predictable), try to combine every element of plist with any other element. +Return a list of the same lenght, with each element maximally combined. The algorithm is quadratic. +The operation is dependent on the order in which elements are combined; there is a straightforward +order - independent algorithm but it is exponential. +*) +let proplist_meet_generate plist = + let props_done = ref Propset.empty in + let combine p (porig, pcombined) = + SymOp.pay (); (* pay one symop *) + L.d_strln ".... MEET ...."; + L.d_strln "MEET SYM HEAP1: "; Prop.d_prop p; L.d_ln (); + L.d_strln "MEET SYM HEAP2: "; Prop.d_prop pcombined; L.d_ln (); + match prop_partial_meet p pcombined with + | None -> + L.d_strln_color Red ".... MEET FAILED ...."; L.d_ln (); + (porig, pcombined) + | Some pcombined' -> + L.d_strln_color Green ".... MEET SUCCEEDED ...."; + L.d_strln "RESULT SYM HEAP:"; Prop.d_prop pcombined'; L.d_ln (); L.d_ln (); + (porig, pcombined') in + let rec proplist_meet = function + | [] -> () + | (porig, pcombined) :: pplist -> + (* use porig instead of pcombined because it might be combinable with more othe props *) + (* e.g. porig might contain a global var to add to the ture branch of a conditional *) + (* but pcombined might have been combined with the false branch already *) + let pplist' = list_map (combine porig) pplist in + props_done := Propset.add pcombined !props_done; + proplist_meet pplist' in + proplist_meet (list_map (fun p -> (p, p)) plist); + !props_done + + +let propset_meet_generate_pre pset = + let plist = Propset.to_proplist pset in + if !Config.meet_level = 0 then plist + else + let pset1 = proplist_meet_generate plist in + let pset_new = Propset.diff pset1 pset in + let plist_old = Propset.to_proplist pset in + let plist_new = Propset.to_proplist pset_new in + plist_new @ plist_old diff --git a/infer/src/backend/dom.mli b/infer/src/backend/dom.mli new file mode 100644 index 000000000..7c8b5f997 --- /dev/null +++ b/infer/src/backend/dom.mli @@ -0,0 +1,32 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Join and Meet Operators *) + +open Utils + +(** {2 Join Operators} *) + +(** Join two pathsets *) +val pathset_join : +Procname.t -> Sil.tenv -> Paths.PathSet.t -> Paths.PathSet.t -> Paths.PathSet.t * Paths.PathSet.t + +val join_time : float ref + +val proplist_collapse_pre : Prop.normal Prop.t list -> Prop.normal Specs.Jprop.t list + +val pathset_collapse : Paths.PathSet.t -> Paths.PathSet.t + +(** reduce the pathset only based on implication checking. *) +val pathset_collapse_impl : Procname.t -> Sil.tenv -> Paths.PathSet.t -> Paths.PathSet.t + +(** {2 Meet Operators} *) + +(** [propset_meet_generate_pre] generates new symbolic heaps (i.e., props) +by applying the partial meet operator, adds the generated heaps +to the argument propset, and returns the resulting propset. This function +is tuned for combining preconditions. *) +val propset_meet_generate_pre : Propset.t -> Prop.normal Prop.t list diff --git a/infer/src/backend/dotty.ml b/infer/src/backend/dotty.ml new file mode 100644 index 000000000..28bf640eb --- /dev/null +++ b/infer/src/backend/dotty.ml @@ -0,0 +1,1351 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +module L = Logging +module F = Format +open Utils + +(** {1 Dotty} *) + +type kind_of_dotty_prop = + | Generic_proposition + | Spec_precondition + | Spec_postcondition of Prop.normal Prop.t (** the precondition associated with the post *) + | Lambda_pred of int * int * bool + +(* the kind of links between different kinds of nodes*) +type kind_of_links = + | LinkExpToExp + | LinkExpToStruct + | LinkStructToExp + | LinkStructToStruct + | LinkToArray + | LinkArrayToExp + | LinkArrayToStruct + | LinkToSSL + | LinkToDLL + +(* coordinate identifies a node using two dimension: id is an numerical identifier of the node,*) +(* lambda identifies in which hpred parameter id lays in*) +type coordinate = { + id: int; + lambda: int; +} + +(* define a link between two nodes. src_fld/trg_fld define the label of the src/trg field. It is*) +(* useful for having nodes from within a struct and/or to inside a struct *) +type link = { + kind: kind_of_links; + src: coordinate; + src_fld: string; + trg: coordinate; + trg_fld: string; +} + +(* type of the visualized boxes/nodes in the graph*) +type dotty_node = + | Dotnil of coordinate (* nil box *) + (* Dotdangling(coo,e,c): dangling box for expression e at coordinate coo and color c *) + | Dotdangling of coordinate * Sil.exp * string + (* Dotpointsto(coo,e,c): basic memory cell box for expression e at coordinate coo and color c *) + | Dotpointsto of coordinate * Sil.exp * string + (* Dotstruct(coo,e,l,c): struct box for expression e with field list l at coordinate coo and color c *) + | Dotstruct of coordinate * Sil.exp * (Ident.fieldname * Sil.strexp) list * string + (* Dotarray(coo,e1,e2,l,t,c): array box for expression e1 with field list l at coordinate coo and color c*) + (* e2 is the size and t is the type *) + | Dotarray of coordinate * Sil.exp * Sil.exp * (Sil.exp * Sil.strexp) list * Sil.typ * string + (* Dotlseg(coo,e1,e2,k,h,c): list box from e1 to e2 at coordinate coo and color c*) + | Dotlseg of coordinate * Sil.exp * Sil.exp * Sil.lseg_kind * Sil.hpred list * string + (* Dotlseg(coo,e1,e2,e3,e4,k,h,c): doubly linked-list box from with parameters (e1,e2,e3,e4) at coordinate coo and color c*) + | Dotdllseg of coordinate * Sil.exp * Sil.exp * Sil.exp * Sil.exp * Sil.lseg_kind * Sil.hpred list * string + +let mk_coordinate i l = { id = i; lambda = l } + +let mk_link k s sf t tf = { kind = k; src = s; src_fld = sf; trg = t; trg_fld = tf } + +(* list of dangling boxes*) +let dangling_dotboxes = ref [] + +(* list of nil boxes*) +let nil_dotboxes = ref [] + +let exps_neq_zero = ref [] + +(* list of fields in the structs *) +let fields_structs = ref [] +let struct_exp_nodes = ref [] + +(* general unique counter to assign a different number to boxex, *) +(* clusters,subgraphs etc. *) +let dotty_state_count = ref 0 + +let spec_counter = ref 0 +let post_counter = ref 0 +let lambda_counter = ref 0 +let proposition_counter = ref 0 +let target_invisible_arrow_pre = ref 0 +let current_pre = ref 0 +let spec_id = ref 0 +let invisible_arrows = ref false + +let print_stack_info = ref false + +let exp_is_neq_zero e = + list_exists (fun e' -> Sil.exp_equal e e') !exps_neq_zero + +(* replace a dollar sign in a name with a D. We need this because dotty get confused if there is*) +(* a dollar sign i a label*) +let strip_special_chars s = + let replace st c c' = + if String.contains st c then begin + let idx = String.index st c in + try + String.set st idx c'; + st + with Invalid_argument _ -> L.out "@\n@\n Invalid argument!!! @\n @.@.@."; assert false + end else st in + let s0 = replace s '(' 'B' in + let s1 = replace s0 '$' 'D' in + let s2 = replace s1 '#' 'H' in + let s3 = replace s2 '&' 'E' in + let s4 = replace s3 '@' 'A' in + let s5 = replace s4 ')' 'B' in + let s6 = replace s5 '+' 'P' in + let s7 = replace s6 '-' 'M' in + s7 + +let rec strexp_to_string pe coo f se = + match se with + | Sil.Eexp (e, inst) -> F.fprintf f "%a" (Sil.pp_exp pe) e + | Sil.Estruct (ls, _) -> F.fprintf f " STRUCT | { %a } " (struct_to_dotty_str pe coo) ls + | Sil.Earray(e, idx, _) -> F.fprintf f " ARRAY[%a] | { %a } " (Sil.pp_exp pe) e (get_contents pe coo) idx + +and struct_to_dotty_str pe coo f ls : unit = + match ls with + | [] -> () + | (fn, se)::[]-> F.fprintf f "{ <%s%iL%i> %s: %a } " (Ident.fieldname_to_string fn) coo.id coo.lambda (Ident.fieldname_to_string fn) (strexp_to_string pe coo) se + | (fn, se):: ls'-> F.fprintf f " { <%s%iL%i> %s: %a } | %a" (Ident.fieldname_to_string fn) coo.id coo.lambda (Ident.fieldname_to_string fn) (strexp_to_string pe coo) se (struct_to_dotty_str pe coo) ls' + +and get_contents_sexp pe coo f se = + match se with + | Sil.Eexp (e', inst') -> + F.fprintf f "%a" (Sil.pp_exp pe) e' + | Sil.Estruct (se', _) -> + F.fprintf f "| { %a }" (struct_to_dotty_str pe coo) se' + | Sil.Earray(e', [], _) -> + F.fprintf f "(ARRAY Size: %a) | { }" (Sil.pp_exp pe) e' + | Sil.Earray(e', ((idx, a):: linner), _) -> + F.fprintf f "(ARRAY Size: %a) | { %a: %a | %a }" (Sil.pp_exp pe) e' (Sil.pp_exp pe) idx + (strexp_to_string pe coo) a (get_contents pe coo) linner + +and get_contents_single pe coo f (e, se) = + let e_no_special_char = strip_special_chars (Sil.exp_to_string e) in + F.fprintf f "{ <%s> %a : %a }" + e_no_special_char (Sil.pp_exp pe) e (get_contents_sexp pe coo) se + +and get_contents pe coo f = function + | [] -> () + | [idx_se] -> + F.fprintf f "%a" (get_contents_single pe coo) idx_se + | idx_se:: l -> + F.fprintf f "%a | %a" (get_contents_single pe coo) idx_se (get_contents pe coo) l + +and get_contents_range_single pe coo f range_se = + let (e1, e2), se = range_se in + let e1_no_special_char = strip_special_chars (Sil.exp_to_string e1) in + F.fprintf f "{ <%s> [%a,%a] : %a }" + e1_no_special_char (Sil.pp_exp pe) e1 (Sil.pp_exp pe) e2 (get_contents_sexp pe coo) se + +and get_contents_range pe coo f = function + | [] -> () + | [range_se] -> + F.fprintf f "%a" (get_contents_range_single pe coo) range_se + | range_se:: l -> + F.fprintf f "%a | %a" (get_contents_range_single pe coo) range_se (get_contents_range pe coo) l + +(* true if node is the sorce node of the expression e*) +let is_source_node_of_exp e node = + match node with + | Dotpointsto (_, e', _) -> Sil.exp_compare e e' = 0 + | _ -> false + +(* given a node returns its coordinates and the expression. Return -1 in case the expressio doesn.t*) +(* make sense for that case *) +let get_coordinate_and_exp dotnode = + match dotnode with + | Dotnil(coo) -> (coo, Sil.exp_minus_one) + | Dotarray (coo, _, _, _, _, _) -> (coo, Sil.exp_minus_one) + | Dotpointsto (coo, b, _) + | Dotlseg (coo, b, _, _, _, _) + | Dotdllseg (coo, b, _, _, _, _, _, _) + | Dotstruct (coo, b, _, _) + | Dotdangling(coo, b, _) -> (coo, b) + +(* true if a node is of a Dotstruct *) +let is_not_struct node = + match node with + | Dotstruct _ -> false + | _ -> true + +(* returns the id field of the coordinate of node *) +let get_coordinate_id node = + let coo = fst (get_coordinate_and_exp node) in + coo.id + +let rec look_up_for_back_pointer e dotnodes lambda = + match dotnodes with + | [] -> [] + | Dotdllseg(coo, _, _, _, e4, _, _, _):: dotnodes' -> + if Sil.exp_compare e e4 = 0 && lambda = coo.lambda then [coo.id + 1] + else look_up_for_back_pointer e dotnodes' lambda + | _:: dotnodes' -> look_up_for_back_pointer e dotnodes' lambda + +(* get the nodes corresponding to an expression and a lambda*) +let rec select_nodes_exp_lambda dotnodes e lambda = + match dotnodes with + | [] -> [] + | node:: l' -> + let (coo, e') = get_coordinate_and_exp node in + if (Sil.exp_compare e e' = 0) && lambda = coo.lambda then node:: select_nodes_exp_lambda l' e lambda + else select_nodes_exp_lambda l' e lambda + +(* look-up the coordinate id in the list of dotnodes those nodes which correspond to expression e*) +(* this is written in this strange way for legacy reason. It should be changed a bit*) +let rec look_up dotnodes e lambda = + let r = select_nodes_exp_lambda dotnodes e lambda in + let r'= list_map get_coordinate_id r in + r' @ look_up_for_back_pointer e dotnodes lambda + +let pp_nesting fmt nesting = + if nesting > 1 then F.fprintf fmt "%d" nesting + +let reset_proposition_counter () = proposition_counter:= 0 + +let reset_dotty_spec_counter () = spec_counter:= 0 + +let max_map f l = + let curr_max = ref 0 in + list_iter (fun x -> curr_max := max !curr_max (f x)) l; + ! curr_max + +let rec sigma_nesting_level sigma = + max_map (function + | Sil.Hpointsto _ -> 0 + | Sil.Hlseg (_, hpara, _, _, _) -> hpara_nesting_level hpara + | Sil.Hdllseg (_, hpara_dll, _, _, _, _, _) -> hpara_dll_nesting_level hpara_dll) sigma + +and hpara_nesting_level hpara = + 1 + sigma_nesting_level hpara.Sil.body + +and hpara_dll_nesting_level hpara_dll = + 1 + sigma_nesting_level hpara_dll.Sil.body_dll + +let color_to_str c = + match c with + | Black -> "black" + | Blue -> "blue" + | Green -> "green" + | Orange -> "orange" + | Red -> "red" + +let make_dangling_boxes pe allocated_nodes (sigma_lambda: (Sil.hpred * int) list) = + let exp_color hpred (exp : Sil.exp) = + if pe.pe_cmap_norm (Obj.repr hpred) == Red then Red + else pe.pe_cmap_norm (Obj.repr exp) in + let get_rhs_predicate (hpred, lambda) = + let n = !dotty_state_count in + incr dotty_state_count; + let coo = mk_coordinate n lambda in + (match hpred with + | Sil.Hpointsto (_, Sil.Eexp (e, inst), _) when not (Sil.exp_equal e Sil.exp_zero) -> + let e_color_str = color_to_str (exp_color hpred e) in + [Dotdangling(coo, e, e_color_str)] + | Sil.Hlseg (k, hpara, _, e2, _) when not (Sil.exp_equal e2 Sil.exp_zero) -> + let e2_color_str = color_to_str (exp_color hpred e2) in + [Dotdangling(coo, e2, e2_color_str)] + | Sil.Hdllseg (k, hpara_dll, e1, e2, e3, e4, elist) -> + let e2_color_str = color_to_str (exp_color hpred e2) in + let e3_color_str = color_to_str (exp_color hpred e3) in + let ll = if not (Sil.exp_equal e2 Sil.exp_zero) then + [Dotdangling(coo, e2, e2_color_str)] + else [] in + if not (Sil.exp_equal e3 Sil.exp_zero) then Dotdangling(coo, e3, e3_color_str):: ll + else ll + | Sil.Hpointsto (_, _, _) + | _ -> [] (* arrays and struct do not give danglings*) + ) in + let is_allocated d = + match d with + | Dotdangling(_, e, _) -> + list_exists (fun a -> match a with + | Dotpointsto(_, e', _) + | Dotarray(_, _, e', _, _, _) + | Dotlseg(_, e', _, _, _, _) + | Dotdllseg(_, e', _, _, _, _, _, _) -> Sil.exp_equal e e' + | _ -> false + ) allocated_nodes + | _ -> false (*this should never happen since d must be a dangling node *) in + let rec filter_duplicate l seen_exp = + match l with + | [] -> [] + | Dotdangling(coo, e, color):: l' -> + if (list_exists (Sil.exp_equal e) seen_exp) then filter_duplicate l' seen_exp + else Dotdangling(coo, e, color):: filter_duplicate l' (e:: seen_exp) + | box:: l' -> box:: filter_duplicate l' seen_exp in (* this case cannot happen*) + let rec subtract_allocated candidate_dangling = + match candidate_dangling with + | [] -> [] + | d:: candidates -> + if (is_allocated d) then subtract_allocated candidates + else d:: subtract_allocated candidates in + let candidate_dangling = list_flatten (list_map get_rhs_predicate sigma_lambda) in + let candidate_dangling = filter_duplicate candidate_dangling [] in + let dangling = subtract_allocated candidate_dangling in + dangling_dotboxes:= dangling + +let rec dotty_mk_node pe sigma = + let n = !dotty_state_count in + incr dotty_state_count; + let do_hpred_lambda exp_color = function + | (Sil.Hpointsto (e, Sil.Earray(e', l, _), Sil.Sizeof(Sil.Tarray(t, s), _)), lambda) -> + incr dotty_state_count; (* increment once more n+1 is the box for the array *) + let e_color_str = color_to_str (exp_color e) in + let e_color_str'= color_to_str (exp_color e') in + [Dotpointsto((mk_coordinate n lambda), e, e_color_str); Dotarray((mk_coordinate (n + 1) lambda), e, e', l, t, e_color_str')] + | (Sil.Hpointsto (e, Sil.Estruct (l, _), _), lambda) -> + incr dotty_state_count; (* increment once more n+1 is the box for the struct *) + let e_color_str = color_to_str (exp_color e) in + (* [Dotpointsto((mk_coordinate n lambda), e, l, true, e_color_str)] *) + [Dotpointsto((mk_coordinate n lambda), e, e_color_str); Dotstruct((mk_coordinate (n + 1) lambda), e, l, e_color_str);] + | (Sil.Hpointsto (e, _, _), lambda) -> + let e_color_str = color_to_str (exp_color e) in + if list_mem Sil.exp_equal e !struct_exp_nodes then [] else + [Dotpointsto((mk_coordinate n lambda), e, e_color_str)] + | (Sil.Hlseg (k, hpara, e1, e2, elist), lambda) -> + incr dotty_state_count; (* increment once more n+1 is the box for last element of the list *) + let eq_color_str = color_to_str (exp_color e1) in + [Dotlseg((mk_coordinate n lambda), e1, e2, k, hpara.Sil.body, eq_color_str)] + | (Sil.Hdllseg (k, hpara_dll, e1, e2, e3, e4, elist), lambda) -> + let e1_color_str = color_to_str (exp_color e1) in + incr dotty_state_count; (* increment once more n+1 is the box for e4 *) + [Dotdllseg((mk_coordinate n lambda), e1, e2, e3, e4, k, hpara_dll.Sil.body_dll, e1_color_str)] in + match sigma with + | [] -> [] + | (hpred, lambda) :: sigma' -> + let exp_color (exp : Sil.exp) = + if pe.pe_cmap_norm (Obj.repr hpred) == Red then Red + else pe.pe_cmap_norm (Obj.repr exp) in + do_hpred_lambda exp_color (hpred, lambda) @ dotty_mk_node pe sigma' + +let set_exps_neq_zero pi = + let f = function + | Sil.Aneq (e, Sil.Const (Sil.Cint i)) when Sil.Int.iszero i -> exps_neq_zero := e :: !exps_neq_zero + | _ -> () in + exps_neq_zero := []; + list_iter f pi + +let box_dangling e = + let entry_e = list_filter (fun b -> match b with + | Dotdangling(_, e', _) -> Sil.exp_equal e e' | _ -> false ) !dangling_dotboxes in + match entry_e with + |[] -> None + | Dotdangling(coo, _, _):: _ -> Some coo.id + | _ -> None (* NOTE: this cannot be possible since entry_e can be composed only by Dotdangling, see def of entry_e*) + +let rec get_color_exp dot_nodes e = + match dot_nodes with + | [] ->"" + | Dotnil(_):: l' -> get_color_exp l' e + | Dotpointsto(_, e', c):: l' + | Dotdangling(_, e', c):: l' + | Dotarray(_, _, e', _, _, c):: l' + | Dotlseg(_, e', _, _, _, c):: l' + | Dotstruct(_, e', _, c):: l' + | Dotdllseg(_, e', _, _, _, _, _, c):: l' -> if (Sil.exp_equal e e') then c else get_color_exp l' e + +(* construct a Dotnil and returns it's id *) +let make_nil_node lambda = + let n = !dotty_state_count in + incr dotty_state_count; + nil_dotboxes:= Dotnil(mk_coordinate n lambda)::!nil_dotboxes; + n + +let compute_fields_struct sigma = + fields_structs:=[]; + let rec do_strexp se in_struct = + match se with + | Sil.Eexp (e, inst) -> if in_struct then fields_structs:= e ::!fields_structs else () + | Sil.Estruct (l, _) -> list_iter (fun e -> do_strexp e true) (snd (list_split l)) + | Sil.Earray (_, l, _) -> list_iter (fun e -> do_strexp e false) (snd (list_split l)) in + let rec fs s = + match s with + | [] -> () + | Sil.Hpointsto(_, se, _):: s' -> do_strexp se false; fs s' + | _:: s' -> fs s' in + fs sigma + +let compute_struct_exp_nodes sigma = + struct_exp_nodes:=[]; + let rec sen s = + match s with + | [] -> () + | Sil.Hpointsto(e, Sil.Estruct _, _):: s' -> struct_exp_nodes:= e::!struct_exp_nodes; sen s' + | _:: s' -> sen s' in + sen sigma + +(* returns the expression of a node*) +let get_node_exp n = snd (get_coordinate_and_exp n) + +let is_nil e prop = + (Sil.exp_equal e Sil.exp_zero) || (Prover.check_equal prop e Sil.exp_zero) + +(* compute a list of (kind of link, field name, coo.id target, name_target) *) +let rec compute_target_struct_fields dotnodes list_fld p f lambda = + let find_target_one_fld (fn, se) = + match se with + | Sil.Eexp (e, inst) -> + if is_nil e p then begin + let n'= make_nil_node lambda in + [(LinkStructToExp, Ident.fieldname_to_string fn, n',"")] + end else + let nodes_e = select_nodes_exp_lambda dotnodes e lambda in + (match nodes_e with + | [] -> + (match box_dangling e with + | None -> [] + | Some n' -> [(LinkStructToExp, Ident.fieldname_to_string fn, n',"")] + ) + | [node] | [Dotpointsto _ ; node] | [node; Dotpointsto _] -> + let n = get_coordinate_id node in + if list_mem Sil.exp_equal e !struct_exp_nodes then begin + let e_no_special_char = strip_special_chars (Sil.exp_to_string e) in + [(LinkStructToStruct, Ident.fieldname_to_string fn, n, e_no_special_char)] + end else + [(LinkStructToExp, Ident.fieldname_to_string fn, n,"")] + | _ -> (* by construction there must be at most 2 nodes for an expression*) + L.out "@\n Too many nodes! Error! @\n@.@."; assert false + ) + | Sil.Estruct (l, _) -> [] (* inner struct are printed by print_struc function *) + | Sil.Earray _ ->[] (* inner arrays are printed by print_array function *) + + in + match list_fld with + | [] -> [] + | a:: list_fld' -> + let targets_a = find_target_one_fld a in + targets_a @ compute_target_struct_fields dotnodes list_fld' p f lambda + +(* compute a list of (kind of link, field name, coo.id target, name_target) *) +let rec compute_target_array_elements dotnodes list_elements p f lambda = + let find_target_one_element (idx, se) = + match se with + | Sil.Eexp (e, inst) -> + if is_nil e p then begin + let n'= make_nil_node lambda in + [(LinkArrayToExp, Sil.exp_to_string idx, n',"")] + end else + let nodes_e = select_nodes_exp_lambda dotnodes e lambda in + (match nodes_e with + | [] -> + (match box_dangling e with + | None -> [] + | Some n' -> [(LinkArrayToExp, Sil.exp_to_string idx, n',"")] + ) + | [node] | [Dotpointsto _ ; node] | [node; Dotpointsto _] -> + let n = get_coordinate_id node in + if list_mem Sil.exp_equal e !struct_exp_nodes then begin + let e_no_special_char = strip_special_chars (Sil.exp_to_string e) in + [(LinkArrayToStruct, Sil.exp_to_string idx, n, e_no_special_char)] + end else + [(LinkArrayToExp, Sil.exp_to_string idx, n,"")] + | _ -> (* by construction there must be at most 2 nodes for an expression*) + L.out "@\n Too many nodes! Error! @\n@.@."; assert false + ) + | Sil.Estruct (l, _) -> [] (* inner struct are printed by print_struc function *) + | Sil.Earray _ ->[] (* inner arrays are printed by print_array function *) + in + match list_elements with + | [] -> [] + | a:: list_ele' -> + let targets_a = find_target_one_element a in + targets_a @ compute_target_array_elements dotnodes list_ele' p f lambda + +let rec compute_target_from_eexp dotnodes e p f lambda = + if is_nil e p then + let n'= make_nil_node lambda in + [(LinkExpToExp, n', "")] + else + let nodes_e = select_nodes_exp_lambda dotnodes e lambda in + let nodes_e_no_struct = list_filter is_not_struct nodes_e in + let trg = list_map get_coordinate_id nodes_e_no_struct in + (match trg with + | [] -> + (match box_dangling e with + | None -> [] + | Some n -> [(LinkExpToExp, n, "")] + ) + | _ -> list_map (fun n -> (LinkExpToExp, n, "")) trg + ) + +(* build the set of edges between nodes *) +let rec dotty_mk_set_links dotnodes sigma p f = + let make_links_for_arrays e lie lambda sigma' = (* used for both Earray and ENarray*) + let src = look_up dotnodes e lambda in + match src with + | [] -> assert false + | n:: nl -> + let target_list = compute_target_array_elements dotnodes lie p f lambda in + (* below it's n+1 because n is the address, n+1 is the actual array node*) + let ff n = list_map (fun (k, lab_src, m, lab_trg) -> mk_link k (mk_coordinate (n + 1) lambda) (strip_special_chars lab_src) (mk_coordinate m lambda) (strip_special_chars lab_trg)) target_list in + let links_from_elements = list_flatten (list_map ff (n:: nl)) in + + let trg_label = strip_special_chars (Sil.exp_to_string e) in + let lnk = mk_link (LinkToArray) (mk_coordinate n lambda) "" (mk_coordinate (n + 1) lambda) trg_label in + lnk:: links_from_elements @ dotty_mk_set_links dotnodes sigma' p f in + match sigma with + | [] -> [] + | (Sil.Hpointsto (e, Sil.Earray(_, lie, _), _), lambda):: sigma' -> + make_links_for_arrays e lie lambda sigma' + | (Sil.Hpointsto (e, Sil.Estruct (lfld, _), t), lambda):: sigma' -> + let src = look_up dotnodes e lambda in + (match src with + | [] -> assert false + | nl -> + (* L.out "@\n@\n List of nl= "; list_iter (L.out " %i ") nl; L.out "@.@.@."; *) + let target_list = compute_target_struct_fields dotnodes lfld p f lambda in + let ff n = list_map (fun (k, lab_src, m, lab_trg) -> mk_link k (mk_coordinate n lambda) lab_src (mk_coordinate m lambda) lab_trg) target_list in + let nodes_e = select_nodes_exp_lambda dotnodes e lambda in + let address_struct_id = + try get_coordinate_id (list_hd (list_filter (is_source_node_of_exp e) nodes_e)) + with exn when exn_not_timeout exn -> (* L.out "@\n@\n PROBLEMS!!!!!!!!!!@.@.@."; *) assert false in + (* we need to exclude the address node from the sorce of fields. no fields should start from there*) + let nl'= list_filter (fun id -> address_struct_id != id) nl in + let links_from_fields = list_flatten (list_map ff nl') in + + let trg_label = strip_special_chars (Sil.exp_to_string e) in + let lnk_from_address_struct = mk_link (LinkExpToStruct) (mk_coordinate address_struct_id lambda) "" (mk_coordinate (address_struct_id + 1) lambda) trg_label in + lnk_from_address_struct:: links_from_fields @ dotty_mk_set_links dotnodes sigma' p f + ) + + | (Sil.Hpointsto (e, Sil.Eexp (e', inst'), t), lambda):: sigma' -> + let src = look_up dotnodes e lambda in + (match src with + | [] -> assert false + | nl -> + let target_list = compute_target_from_eexp dotnodes e' p f lambda in + let ff n = list_map (fun (k, m, lab_target) -> mk_link k (mk_coordinate n lambda) "" (mk_coordinate m lambda) (strip_special_chars lab_target)) target_list in + let ll = list_flatten (list_map ff nl) in + ll @ dotty_mk_set_links dotnodes sigma' p f + ) + + | (Sil.Hlseg (_, pred, e1, e2, elist), lambda):: sigma' -> + let src = look_up dotnodes e1 lambda in + (match src with + | [] -> assert false + | n:: _ -> + let (_, m, lab) = list_hd (compute_target_from_eexp dotnodes e2 p f lambda) in + let lnk = mk_link LinkToSSL (mk_coordinate (n + 1) lambda) "" (mk_coordinate m lambda) lab in + lnk:: dotty_mk_set_links dotnodes sigma' p f + ) + | (Sil.Hdllseg (_, pred, e1, e2, e3, e4, elist), lambda):: sigma' -> + let src = look_up dotnodes e1 lambda in + (match src with + | [] -> assert false + | n:: _ -> (* n is e1's box and n+1 is e4's box *) + let targetF = look_up dotnodes e3 lambda in + let target_Flink = (match targetF with + | [] -> [] + | m:: _ -> [mk_link LinkToDLL (mk_coordinate (n + 1) lambda) "" (mk_coordinate m lambda) ""] + ) in + let targetB = look_up dotnodes e2 lambda in + let target_Blink = (match targetB with + | [] -> [] + | m:: _ -> [mk_link LinkToDLL (mk_coordinate n lambda) "" (mk_coordinate m lambda) ""] + ) in + target_Blink @ target_Flink @ dotty_mk_set_links dotnodes sigma' p f + ) + +let print_kind f kind = + incr dotty_state_count; + match kind with + | Spec_precondition -> + incr dotty_state_count; + current_pre:=!dotty_state_count; + F.fprintf f "\n PRE%iL0 [label=\"PRE %i \", style=filled, color= yellow]\n" !dotty_state_count !spec_counter; + print_stack_info:= true; + | Spec_postcondition pre -> + F.fprintf f "\n POST%iL0 [label=\"POST %i \", style=filled, color= yellow]\n" !dotty_state_count !post_counter; + print_stack_info:= true; + | Generic_proposition -> + F.fprintf f "\n HEAP%iL0 [label=\"HEAP %i \", style=filled, color= yellow]\n" !dotty_state_count !proposition_counter + | Lambda_pred (no, lev, array) -> + match array with + | false -> + F.fprintf f "style=dashed; color=blue \n state%iL%i [label=\"INTERNAL STRUCTURE %i \", style=filled, color= lightblue]\n" !dotty_state_count !lambda_counter !lambda_counter ; + F.fprintf f "state%iL%i -> state%iL%i [color=\"lightblue \" arrowhead=none] \n" !dotty_state_count !lambda_counter no lev; + | true -> + F.fprintf f "style=dashed; color=blue \n state%iL%i [label=\"INTERNAL STRUCTURE %i \", style=filled, color= lightblue]\n" !dotty_state_count !lambda_counter !lambda_counter ; + (* F.fprintf f "state%iL%i -> struct%iL%i:%s [color=\"lightblue \" arrowhead=none] \n" !dotty_state_count !lambda_counter no lev lab;*) + + incr dotty_state_count + +(* print a link between two noeds in the graph *) +let dotty_pp_link f link = + let n1 = link.src.id in + let lambda1 = link.src.lambda in + let n2 = link.trg.id in + let lambda2 = link.trg.lambda in + let src_fld = link.src_fld in + let trg_fld = link.trg_fld in + match n2, link.kind with + | 0, _ -> + F.fprintf f "state%iL%i -> state%iL%i[label=\"%s DANG\", color= red];\n" n1 lambda1 n2 lambda2 src_fld + | _, LinkToArray -> + F.fprintf f "state%iL%i -> struct%iL%i:%s%iL%i[label=\"\"]\n" n1 lambda1 n2 lambda2 trg_fld n2 lambda2 + | _, LinkExpToStruct -> + F.fprintf f "state%iL%i -> struct%iL%i:%s%iL%i[label=\"\"]\n" n1 lambda1 n2 lambda2 trg_fld n2 lambda2 + | _, LinkStructToExp -> + F.fprintf f "struct%iL%i:%s%iL%i -> state%iL%i[label=\"\"]\n" n1 lambda1 src_fld n1 lambda1 n2 lambda2 + | _, LinkStructToStruct -> + F.fprintf f "struct%iL%i:%s%iL%i -> struct%iL%i:%s%iL%i[label=\"\"]\n" n1 lambda1 src_fld n1 lambda1 n2 lambda2 trg_fld n2 lambda2 + | _, LinkArrayToExp -> + F.fprintf f "struct%iL%i:%s -> state%iL%i[label=\"\"]\n" n1 lambda1 src_fld n2 lambda2 + | _, LinkArrayToStruct -> + F.fprintf f "struct%iL%i:%s -> struct%iL%i[label=\"\"]\n" n1 lambda1 src_fld n2 lambda2 + | _, _ -> F.fprintf f "state%iL%i -> state%iL%i[label=\"%s\"];\n" n1 lambda1 n2 lambda2 src_fld + +(* given the list of nodes and links get rid of spec nodes that are not pointed to by anybody*) +let filter_useless_spec_dollar_box (nodes: dotty_node list) (links: link list) = + let tmp_nodes = ref nodes in + let tmp_links = ref links in + let remove_links_from ln = list_filter (fun n' -> not (list_mem Pervasives.(=) n' ln)) !tmp_links in + let remove_node n ns = + list_filter (fun n' -> match n' with + | Dotpointsto _ -> (get_coordinate_id n')!= (get_coordinate_id n) + | _ -> true + ) ns in + let rec boxes_pointed_by n lns = + match lns with + | [] -> [] + | l:: ln' -> let n_id = get_coordinate_id n in + if l.src.id = n_id && l.src_fld ="" then ( + (*L.out "@\n Found link (%i,%i)" l.src.id l.trg.id;*) + l:: boxes_pointed_by n ln' + ) + else boxes_pointed_by n ln' in + let rec boxes_pointing_at n lns = + match lns with + | [] -> [] + | l:: ln' -> let n_id = get_coordinate_id n in + if l.trg.id = n_id && l.trg_fld ="" then ( + (*L.out "@\n Found link (%i,%i)" l.src.id l.trg.id;*) + l:: boxes_pointing_at n ln' ) + else boxes_pointing_at n ln' in + let is_spec_variable = function + | Sil.Var id -> + Ident.is_normal id && Ident.name_equal (Ident.get_name id) Ident.name_spec + | _ -> false in + let handle_one_node node = + match node with + | Dotpointsto _ -> + let e = get_node_exp node in + if is_spec_variable e then begin + (*L.out "@\n Found a spec expression = %s @.@." (Sil.exp_to_string e); *) + let links_from_node = boxes_pointed_by node links in + let links_to_node = boxes_pointing_at node links in + (* L.out "@\n Size of links_from=%i links_to=%i @.@." (list_length links_from_node) (list_length links_to_node); *) + if links_to_node =[] then begin + tmp_links:= remove_links_from links_from_node ; + tmp_nodes:= remove_node node !tmp_nodes; + end + end + | _ -> () in + list_iter handle_one_node nodes; + (!tmp_nodes,!tmp_links) + +(* print a struct node *) +let rec print_struct f pe e l coo c = + let n = coo.id in + let lambda = coo.lambda in + let e_no_special_char = strip_special_chars (Sil.exp_to_string e) in + F.fprintf f "subgraph structs_%iL%i {\n" n lambda ; + F.fprintf f " node [shape=record]; \n struct%iL%i [label=\"{<%s%iL%i> STRUCT: %a } | %a\" ] fontcolor=%s\n" n lambda e_no_special_char n lambda (Sil.pp_exp pe) e (struct_to_dotty_str pe coo) l c; + F.fprintf f "}\n" + +and print_array f pe e1 e2 l ty coo c = + let n = coo.id in + let lambda = coo.lambda in + let e_no_special_char = strip_special_chars (Sil.exp_to_string e1) in + F.fprintf f "subgraph structs_%iL%i {\n" n lambda ; + F.fprintf f " node [shape=record]; \n struct%iL%i [label=\"{<%s%iL%i> ARRAY| SIZE: %a } | %a\" ] fontcolor=%s\n" n lambda e_no_special_char n lambda (Sil.pp_exp pe) e2 (get_contents pe coo) l c; + F.fprintf f "}\n" + +and print_sll f pe nesting k e1 e2 coo = + let n = coo.id in + let lambda = coo.lambda in + let n' = !dotty_state_count in + incr dotty_state_count; + begin + match k with + | Sil.Lseg_NE -> F.fprintf f "subgraph cluster_%iL%i { style=filled; color=lightgrey; node [style=filled,color=white]; label=\"list NE\";" n' lambda (*pp_nesting nesting*) + | Sil.Lseg_PE -> F.fprintf f "subgraph cluster_%iL%i { style=filled; color=lightgrey; node [style=filled,color=white]; label=\"list PE\";" n' lambda (*pp_nesting nesting *) + end; + F.fprintf f "state%iL%i [label=\"%a\"]\n" n lambda (Sil.pp_exp pe) e1; + let n' = !dotty_state_count in + incr dotty_state_count; + F.fprintf f "state%iL%i [label=\"... \" style=filled color=lightgrey] \n" n' lambda ; + F.fprintf f "state%iL%i -> state%iL%i [label=\" \"] \n" n lambda n' lambda ; + F.fprintf f "state%iL%i [label=\" \"] \n" (n + 1) lambda ; + F.fprintf f "state%iL%i -> state%iL%i [label=\" \"] }" n' lambda (n + 1) lambda ; + incr lambda_counter; + pp_dotty f (Lambda_pred(n + 1, lambda, false)) (Prop.normalize (Prop.from_sigma nesting)) + +and print_dll f pe nesting k e1 e2 e3 e4 coo = + let n = coo.id in + let lambda = coo.lambda in + let n' = !dotty_state_count in + incr dotty_state_count; + begin + match k with + | Sil.Lseg_NE -> F.fprintf f "subgraph cluster_%iL%i { style=filled; color=lightgrey; node [style=filled,color=white]; label=\"doubly-linked list NE\";" n' lambda (*pp_nesting nesting *) + | Sil.Lseg_PE -> F.fprintf f "subgraph cluster_%iL%i { style=filled; color=lightgrey; node [style=filled,color=white]; label=\"doubly-linked list PE\";" n' lambda (*pp_nesting nesting *) + end; + F.fprintf f "state%iL%i [label=\"%a\"]\n" n lambda (Sil.pp_exp pe) e1; + let n' = !dotty_state_count in + incr dotty_state_count; + F.fprintf f "state%iL%i [label=\"... \" style=filled color=lightgrey] \n" n' lambda; + F.fprintf f "state%iL%i -> state%iL%i [label=\" \"]\n" n lambda n' lambda; + F.fprintf f "state%iL%i -> state%iL%i [label=\" \"]\n" n' lambda n lambda; + F.fprintf f "state%iL%i [label=\"%a\"]\n" (n + 1) lambda (Sil.pp_exp pe) e4; + F.fprintf f "state%iL%i -> state%iL%i [label=\" \"]\n" (n + 1) lambda n' lambda; + F.fprintf f "state%iL%i -> state%iL%i [label=\" \"]}\n" n' lambda (n + 1) lambda ; + incr lambda_counter; + pp_dotty f (Lambda_pred(n', lambda, false)) (Prop.normalize (Prop.from_sigma nesting)) + +and dotty_pp_state f pe dotnode = + let dotty_exp coo e c is_dangling = + let n = coo.id in + let lambda = coo.lambda in + if is_dangling then + F.fprintf f "state%iL%i [label=\"%a \", color=red, style=dashed, fontcolor=%s]\n" n lambda (Sil.pp_exp pe) e c + else + F.fprintf f "state%iL%i [label=\"%a\" fontcolor=%s]\n" n lambda (Sil.pp_exp pe) e c in + match dotnode with + | Dotnil coo -> F.fprintf f "state%iL%i [label=\"NIL \", color=green, style=filled]\n" coo.id coo.lambda + | Dotdangling(coo, e, c) -> dotty_exp coo e c true + | Dotpointsto(coo, e1, c) -> dotty_exp coo e1 c false + | Dotstruct(coo, e1, l, c) -> print_struct f pe e1 l coo c + | Dotarray(coo, e1, e2, l, ty, c) -> print_array f pe e1 e2 l ty coo c + | Dotlseg(coo, e1, e2, Sil.Lseg_NE, nesting, c) -> + print_sll f pe nesting Sil.Lseg_NE e1 e2 coo + | Dotlseg(coo, e1, e2, Sil.Lseg_PE, nesting, c) -> + print_sll f pe nesting Sil.Lseg_PE e1 e2 coo + | Dotdllseg(coo, e1, e2, e3, e4, Sil.Lseg_NE, nesting, c) -> + print_dll f pe nesting Sil.Lseg_NE e1 e2 e3 e4 coo + | Dotdllseg(coo, e1, e2, e3, e4, Sil.Lseg_PE, nesting, c) -> + print_dll f pe nesting Sil.Lseg_PE e1 e2 e3 e4 coo + +(* Build the graph data structure to be printed *) +and build_visual_graph f pe p = + let sigma = Prop.get_sigma p in + compute_fields_struct sigma; + compute_struct_exp_nodes sigma; + (* L.out "@\n@\n Computed fields structs: "; + list_iter (fun e -> L.out " %a " (Sil.pp_exp pe) e) !fields_structs; + L.out "@\n@."; + L.out "@\n@\n Computed exp structs nodes: "; + list_iter (fun e -> L.out " %a " (Sil.pp_exp pe) e) !struct_exp_nodes; + L.out "@\n@."; *) + let sigma_lambda = list_map (fun hp -> (hp,!lambda_counter)) sigma in + let nodes = (dotty_mk_node pe) sigma_lambda in + make_dangling_boxes pe nodes sigma_lambda; + let links = dotty_mk_set_links nodes sigma_lambda p f in + filter_useless_spec_dollar_box nodes links + +and display_pure_info f pe prop = + let print_invisible_objects () = + for j = 1 to 4 do + F.fprintf f " inv_%i%i [style=invis]\n" !spec_counter j; + F.fprintf f " inv_%i%i%i [style=invis]\n" !spec_counter j j; + F.fprintf f " inv_%i%i%i%i [style=invis]\n" !spec_counter j j j; + done; + for j = 1 to 4 do + F.fprintf f " state_pi_%i -> inv_%i%i [style=invis]\n" !proposition_counter !spec_counter j; + F.fprintf f " inv_%i%i -> inv_%i%i%i [style=invis]\n" !spec_counter j !spec_counter j j; + F.fprintf f " inv_%i%i%i -> inv_%i%i%i%i [style=invis]\n" !spec_counter j j !spec_counter j j j; + done in + let pure = Prop.get_pure prop in + F.fprintf f "subgraph {\n"; + F.fprintf f " node [shape=box]; \n state_pi_%i [label=\"STACK \\n\\n %a\" color=orange style=filled]\n" !proposition_counter (Prop.pp_pi pe) pure; + if !invisible_arrows then print_invisible_objects (); + F.fprintf f "}\n" + +(** Pretty print a proposition in dotty format. *) +and pp_dotty f kind (_prop: Prop.normal Prop.t) = + incr proposition_counter; + let pe, prop = match kind with + | Spec_postcondition pre -> + target_invisible_arrow_pre:=!proposition_counter; + let diff = Propgraph.compute_diff Black (Propgraph.from_prop pre) (Propgraph.from_prop _prop) in + let cmap_norm = Propgraph.diff_get_colormap false diff in + let cmap_foot = Propgraph.diff_get_colormap true diff in + let pe = { (Prop.prop_update_obj_sub pe_text pre) with pe_cmap_norm = cmap_norm; pe_cmap_foot = cmap_foot } in + let pre_stack = fst (Prop.sigma_get_stack_nonstack true (Prop.get_sigma pre)) in (* add stack vars from pre *) + let prop = Prop.replace_sigma (pre_stack @ Prop.get_sigma _prop) _prop in + pe, Prop.normalize prop + | _ -> + let pe = Prop.prop_update_obj_sub pe_text _prop in + pe, _prop in + dangling_dotboxes := []; + nil_dotboxes :=[]; + set_exps_neq_zero (Prop.get_pi prop); + incr dotty_state_count; + F.fprintf f "\n subgraph cluster_prop_%i { color=black \n" !proposition_counter; + print_kind f kind; + if !print_stack_info then begin + display_pure_info f pe prop; + print_stack_info:= false + end; + (* F.fprintf f "\n subgraph cluster_%i { color=black \n" !dotty_state_count; *) + let (nodes, links) = build_visual_graph f pe prop in + list_iter (dotty_pp_state f pe) (nodes@ !dangling_dotboxes @ !nil_dotboxes); + list_iter (dotty_pp_link f) links; + (* F.fprintf f "\n } \n"; *) + F.fprintf f "\n } \n" + +let pp_dotty_one_spec f pre posts = + post_counter := 0; + incr spec_counter; + incr proposition_counter; + incr dotty_state_count; + F.fprintf f "\n subgraph cluster_%i { color=blue \n" !dotty_state_count; + incr dotty_state_count; + F.fprintf f "\n state%iL0 [label=\"SPEC %i \", style=filled, color= lightblue]\n" !dotty_state_count !spec_counter; + spec_id:=!dotty_state_count; + invisible_arrows:= true; + pp_dotty f (Spec_precondition) pre; + invisible_arrows:= false; + list_iter (fun (po, path) -> incr post_counter ; pp_dotty f (Spec_postcondition pre) po; + for j = 1 to 4 do + F.fprintf f " inv_%i%i%i%i -> state_pi_%i [style=invis]\n" !spec_counter j j j !target_invisible_arrow_pre; + done + ) posts; + F.fprintf f "\n } \n" + +(* this is used to print a list of proposition when considered in a path of nodes *) +let pp_dotty_prop_list_in_path f plist prev_n curr_n = + try + incr proposition_counter; + incr dotty_state_count; + F.fprintf f "\n subgraph cluster_%i { color=blue \n" !dotty_state_count; + incr dotty_state_count; + F.fprintf f "\n state%iN [label=\"NODE %i \", style=filled, color= lightblue]\n" curr_n curr_n; + list_iter (fun po -> incr proposition_counter ; pp_dotty f (Generic_proposition) po) plist; + if prev_n <> - 1 then F.fprintf f "\n state%iN ->state%iN\n" prev_n curr_n; + F.fprintf f "\n } \n" + with exn when exn_not_timeout exn -> + () + +(* create a dotty file with a single proposition *) +let dotty_prop_to_dotty_file fname prop = + try + let out_dot = open_out fname in + let fmt_dot = Format.formatter_of_out_channel out_dot in + reset_proposition_counter (); + Format.fprintf fmt_dot "@\n@\n@\ndigraph main { \nnode [shape=box]; @\n"; + Format.fprintf fmt_dot "@\n compound = true; @\n"; + pp_dotty fmt_dot Generic_proposition prop; + Format.fprintf fmt_dot "@\n}"; + close_out out_dot + with exn when exn_not_timeout exn -> + () + +(* this is used only to print a list of prop parsed with the external parser. Basically deprecated.*) +let pp_proplist_parsed2dotty_file filename plist = + try + let pp_list f plist = + reset_proposition_counter (); + F.fprintf f "\n\n\ndigraph main { \nnode [shape=box];\n"; + F.fprintf f "\n compound = true; \n"; + F.fprintf f "\n /* size=\"12,7\"; ratio=fill;*/ \n"; + ignore (list_map (pp_dotty f Generic_proposition) plist); + F.fprintf f "\n}" in + let outc = open_out filename in + let fmt = F.formatter_of_out_channel outc in + F.fprintf fmt "#### Dotty version: ####@.%a@.@." pp_list plist; + close_out outc + with exn when exn_not_timeout exn -> + () + +(********** START of Print interprocedural cfgs in dotty format *) +(********** Print control flow graph (in dot form) for fundec to *) +(* channel. You have to compute an interprocedural cfg first *) + +let pp_cfgnodename fmt (n : Cfg.Node.t) = + F.fprintf fmt "%d" (Cfg.Node.get_id n) + +let pp_etlist fmt etl = + list_iter (fun (id, ty) -> + Format.fprintf fmt " %s:%a" id (Sil.pp_typ_full pe_text) ty) etl + +let pp_local_list fmt etl = + list_iter (fun (id, ty) -> + Format.fprintf fmt " %a:%a" Mangled.pp id (Sil.pp_typ_full pe_text) ty) etl + +let pp_cfgnodelabel fmt (n : Cfg.Node.t) = + let pp_label fmt n = + match Cfg.Node.get_kind n with + | Cfg.Node.Start_node (pdesc) -> + (* let def = if Cfg.Procdesc.is_defined pdesc then "defined" else "declared" in *) + (* Format.fprintf fmt "Start %a (%s)" pp_id (Procname.to_string (Cfg.Procdesc.get_proc_name pdesc)) def *) + Format.fprintf fmt "Start %s\\nFormals: %a\\nLocals: %a" + (Procname.to_string (Cfg.Procdesc.get_proc_name pdesc)) + pp_etlist (Cfg.Procdesc.get_formals pdesc) + pp_local_list (Cfg.Procdesc.get_locals pdesc); + if list_length (Cfg.Procdesc.get_captured pdesc) <> 0 then + Format.fprintf fmt "\\nCaptured: %a" + pp_local_list (Cfg.Procdesc.get_captured pdesc) + | Cfg.Node.Exit_node (pdesc) -> + Format.fprintf fmt "Exit %s" (Procname.to_string (Cfg.Procdesc.get_proc_name pdesc)) + | Cfg.Node.Join_node -> + Format.fprintf fmt "+" + | Cfg.Node.Prune_node (is_true_branch, ik, s) -> Format.fprintf fmt "Prune (%b branch)" is_true_branch + | Cfg.Node.Stmt_node s -> Format.fprintf fmt " %s" s + | Cfg.Node.Skip_node s -> Format.fprintf fmt "Skip %s" s in + let instr_string i = + let pp f () = Sil.pp_instr pe_text f i in + let str = pp_to_string pp () in + Escape.escape_dotty str in + let pp_instrs fmt instrs = + list_iter (fun i -> F.fprintf fmt " %s\\n " (instr_string i)) instrs in + let instrs = Cfg.Node.get_instrs n in + F.fprintf fmt "%d: %a \\n %a" (Cfg.Node.get_id n) pp_label n pp_instrs instrs + +let pp_cfgnodeshape fmt (n: Cfg.Node.t) = + match Cfg.Node.get_kind n with + | Cfg.Node.Start_node _ | Cfg.Node.Exit_node _ -> F.fprintf fmt "color=yellow style=filled" + | Cfg.Node.Prune_node _ -> F.fprintf fmt "shape=\"invhouse\"" + | Cfg.Node.Skip_node _ -> F.fprintf fmt "color=\"gray\"" + | Cfg.Node.Stmt_node _ -> F.fprintf fmt "shape=\"box\"" + | _ -> F.fprintf fmt "" + +(* +let pp_cfgedge fmt src dest = +F.fprintf fmt "%a -> %a" +pp_cfgnodename src +pp_cfgnodename dest +*) + +let pp_cfgnode fmt (n: Cfg.Node.t) = + F.fprintf fmt "%a [label=\"%a\" %a]\n\t\n" pp_cfgnodename n pp_cfgnodelabel n pp_cfgnodeshape n; + let print_edge n1 n2 is_exn = + let color = if is_exn then "[color=\"red\" ]" else "" in + match Cfg.Node.get_kind n2 with + | Cfg.Node.Exit_node _ when is_exn = true -> (* don't print exception edges to the exit node *) + () + | _ -> + F.fprintf fmt "\n\t %d -> %d %s;" (Cfg.Node.get_id n1) (Cfg.Node.get_id n2) color in + list_iter (fun n' -> print_edge n n' false) (Cfg.Node.get_succs n); + list_iter (fun n' -> print_edge n n' true) (Cfg.Node.get_exn n) + +(* * print control flow graph (in dot form) for fundec to channel let *) +(* print_cfg_channel (chan : out_channel) (fd : fundec) = let pnode (s: *) +(* stmt) = fprintf chan "%a@\n" d_cfgnode s in forallStmts pnode fd * *) +(* Print control flow graph (in dot form) for fundec to file let *) +(* print_cfg_filename (filename : string) (fd : fundec) = let chan = *) +(* open_out filename in begin print_cfg_channel chan fd; close_out chan; *) +(* end *) + +(* Print the extra information related to the inteprocedural aspect, ie., *) +(* special node, and call / return edges *) +let print_icfg fmt cfg = + let print_node node = + let loc = Cfg.Node.get_loc node in + if (!Config.dotty_cfg_libs || DB.source_file_equal loc.Sil.file !DB.current_source) then + F.fprintf fmt "%a\n" pp_cfgnode node in + list_iter print_node (Cfg.Node.get_all_nodes cfg) + +let print_edges fmt edges = + let count = ref 0 in + let print_edge (n1, n2) = + incr count; + F.fprintf fmt "%a -> %a [color=\"red\" label=\"%d\" fontcolor=\"green\"];" pp_cfgnodename n1 pp_cfgnodename n2 !count in + list_iter print_edge edges + +let print_icfg_dotty cfg (extra_edges : (Cfg.Node.t * Cfg.Node.t) list) = + let chan = open_out (DB.filename_to_string (DB.Results_dir.path_to_filename DB.Results_dir.Abs_source_dir [!Config.dotty_output])) in + let fmt = Format.formatter_of_out_channel chan in + F.fprintf fmt "digraph iCFG {\n"; + print_icfg fmt cfg; + print_edges fmt extra_edges; + F.fprintf fmt "}\n"; + close_out chan + +let store_icfg_to_file filename cfg = + let chan = open_out ((Filename.chop_extension filename)^".dot") in + let fmt = Format.formatter_of_out_channel chan in + F.fprintf fmt "digraph iCFG {\n"; + print_icfg fmt cfg; + F.fprintf fmt "}\n"; + close_out chan + +(********** END of Printing dotty files ***********) + +(** Dotty printing for specs *) +let pp_speclist_dotty f (splist: Prop.normal Specs.spec list) = + let pp_simple_saved = !Config.pp_simple in + Config.pp_simple := true; + reset_proposition_counter (); + reset_dotty_spec_counter (); + F.fprintf f "@\n@\n\ndigraph main { \nnode [shape=box]; @\n"; + F.fprintf f "@\n compound = true; @\n"; + (* F.fprintf f "\n size=\"12,7\"; ratio=fill; \n"; *) + list_iter (fun s -> pp_dotty_one_spec f (Specs.Jprop.to_prop s.Specs.pre) s.Specs.posts) splist; + F.fprintf f "@\n}"; + Config.pp_simple := pp_simple_saved + +let pp_speclist_to_file (filename : DB.filename) spec_list = + let pp_simple_saved = !Config.pp_simple in + Config.pp_simple := true; + let outc = open_out (DB.filename_to_string (DB.filename_add_suffix filename ".dot")) in + let fmt = F.formatter_of_out_channel outc in + let () = F.fprintf fmt "#### Dotty version: ####@\n%a@\n@\n" pp_speclist_dotty spec_list in + close_out outc; + Config.pp_simple := pp_simple_saved + +let pp_speclist_dotty_file (filename : DB.filename) spec_list = + try pp_speclist_to_file filename spec_list + with exn when exn_not_timeout exn -> + () + +(**********************************************************************) +(* Code prodicing a xml version of a graph *) +(**********************************************************************) + +(* each node has an unique integer identifier *) +type visual_heap_node = + | VH_dangling of int * Sil.exp + | VH_pointsto of int * Sil.exp * Sil.strexp * Sil.exp (* VH_pointsto(id,address,content,type) *) + | VH_lseg of int * Sil.exp * Sil.exp * Sil.lseg_kind (*VH_lseg(id,address,content last cell, kind) *) + (*VH_dllseg(id, address, content first cell, content last cell, address last cell, kind) *) + | VH_dllseg of int * Sil.exp * Sil.exp * Sil.exp * Sil.exp * Sil.lseg_kind + +(* an edge is a pair of node identifiers*) +type visual_heap_edge = { + src: int; + trg: int; + lab: string +} + +let mk_visual_heap_edge s t l = { src = s; trg = t; lab = l } + +(* a visual heap has an integer identifier, a set of nodes and a set of edges*) +type visual_heap = + | VH of int * visual_heap_node list * visual_heap_edge list + +(* set of visual heaps used to represent a proposition. In general, since we have high-order *) +(* lists a proposition is not a single visual heap but a set of it. The parameter of a high-order*) +(* list is visualized with a distinct visual heap. Similarly for the parameter in new arrays*) +type visual_proposition = visual_heap list + +(* used to generate unique identifier for all the nodes in the set of visual graphs used to *) +(* represent a proposition*) +let global_node_counter = ref 0 + +let working_list = ref [] + +let set_dangling_nodes = ref [] + +(* convert an exp into a string which is xml friendly, ie. special character are replaced by*) +(* the proper xml way to visualize them*) +let exp_to_xml_string e = + pp_to_string (Sil.pp_exp (pe_html Black)) e + +(* convert an atom into an xml-friendly string without special characters *) +let atom_to_xml_string a = + pp_to_string (Sil.pp_atom (pe_html Black)) a + +(* return the dangling node corresponding to an expression it exists or None *) +let exp_dangling_node e = + let entry_e = list_filter (fun b -> match b with + | VH_dangling(_, e') -> Sil.exp_equal e e' | _ -> false ) !set_dangling_nodes in + match entry_e with + |[] -> None + | VH_dangling(n, e') :: _ -> Some (VH_dangling(n, e')) + | _ -> None (* NOTE: this cannot be possible since entry_e can be composed only by VH_dangling, see def of entry_e*) + +(* make nodes and when it finds a list records in the working list *) +(* to do (n, prop) where n is the integer identifier of the list node. *) +(* This allow to keep the connection between the list node and the graph *) +(* that displays its contents. *) +let rec make_visual_heap_nodes sigma = + let n = !global_node_counter in + incr global_node_counter; + match sigma with + | [] -> [] + | Sil.Hpointsto (e, se, t):: sigma' -> + VH_pointsto(n, e, se, t):: make_visual_heap_nodes sigma' + | Sil.Hlseg (k, hpara, e1, e2, elist):: sigma' -> + working_list:= (n, hpara.Sil.body)::!working_list; + VH_lseg(n, e1, e2, k):: make_visual_heap_nodes sigma' + | Sil.Hdllseg (k, hpara_dll, e1, e2, e3, e4, elist):: sigma'-> + working_list:= (n, hpara_dll.Sil.body_dll)::!working_list; + VH_dllseg(n, e1, e2, e3, e4, k):: make_visual_heap_nodes sigma' + +(* given a node returns its id and address*) +let get_node_id_and_addr node = + match node with + | VH_dangling(n, e) + | VH_pointsto(n, e, _, _) + | VH_lseg(n, e, _ , _) + | VH_dllseg(n, e, _, _, _, _) -> (n, e) + +(* return node's id*) +let get_node_id node = fst (get_node_id_and_addr node) + +(* return node's address*) +let get_node_addr node = snd (get_node_id_and_addr node) + +(* return the nodes corresponding to an address given by an expression *) +let rec select_node_at_address nodes e = + match nodes with + | [] -> None + | n:: l' -> + let e' = get_node_addr n in + if (Sil.exp_compare e e' = 0) then Some n + else select_node_at_address l' e + +(* look-up the ids in the list of nodes corresponding to expression e*) +(* let look_up_nodes_ids nodes e = +list_map get_node_id (select_nodes_exp nodes e) *) + +(* create a list of dangling nodes *) +let make_set_dangling_nodes allocated_nodes (sigma: Sil.hpred list) = + let make_new_dangling e = + let n = !global_node_counter in + incr global_node_counter; + VH_dangling(n, e) in + let get_rhs_predicate hpred = + (match hpred with + | Sil.Hpointsto (_, Sil.Eexp (e, inst), _) when not (Sil.exp_equal e Sil.exp_zero) -> [e] + | Sil.Hlseg (_, _, _, e2, _) when not (Sil.exp_equal e2 Sil.exp_zero) -> [e2] + | Sil.Hdllseg (_, _, e1, e2, e3, _, _) -> + if (Sil.exp_equal e2 Sil.exp_zero) then + if (Sil.exp_equal e3 Sil.exp_zero) then [] + else [e3] + else [e2; e3] + | Sil.Hpointsto (_, _, _) + | _ -> [] (* arrays and struct do not give danglings. CHECK THIS!*) + ) in + let is_not_allocated e = + let allocated = list_exists (fun a -> match a with + | VH_pointsto(_, e', _, _) + | VH_lseg(_, e', _ , _) + | VH_dllseg(_, e', _, _, _, _) -> Sil.exp_equal e e' + | _ -> false ) allocated_nodes in + not allocated in + let rec filter_duplicate l seen_exp = + match l with + | [] -> [] + | e:: l' -> + if (list_exists (Sil.exp_equal e) seen_exp) then filter_duplicate l' seen_exp + else e:: filter_duplicate l' (e:: seen_exp) in + let rhs_exp_list = list_flatten (list_map get_rhs_predicate sigma) in + let candidate_dangling_exps = filter_duplicate rhs_exp_list [] in + let dangling_exps = list_filter is_not_allocated candidate_dangling_exps in (* get rid of allocated ones*) + list_map make_new_dangling dangling_exps + +(* return a list of pairs (n,field_lab) where n is a target node*) +(* corresponding to se and is going to be used a target for and edge*) +(* field_lab is the name of the field which points to n (if any)*) +let rec compute_target_nodes_from_sexp nodes se prop field_lab = + match se with + | Sil.Eexp (e, inst) when is_nil e prop -> [] (* Nil is not represented by a node, it's just a value which should be printed*) + | Sil.Eexp (e, inst) -> + let e_node = select_node_at_address nodes e in + (match e_node with + | None -> + (match exp_dangling_node e with + | None -> [] + | Some dang_node -> [(dang_node, field_lab)] + ) + | Some n -> [(n, field_lab)] + ) + | Sil.Estruct (lfld, inst) -> + (match lfld with + | [] -> [] + | (fn, se2):: l' -> + compute_target_nodes_from_sexp nodes se2 prop (Ident.fieldname_to_string fn) @ + compute_target_nodes_from_sexp nodes (Sil.Estruct (l', inst)) prop "" + ) + | Sil.Earray(size, lie, inst) -> + (match lie with + | [] -> [] + | (idx, se2):: l' -> + let lab ="["^exp_to_xml_string idx^"]" in + compute_target_nodes_from_sexp nodes se2 prop lab @ + compute_target_nodes_from_sexp nodes (Sil.Earray(size, l', inst)) prop "" + ) + + +(* build the set of edges between nodes *) +let rec make_visual_heap_edges nodes sigma prop = + let combine_source_target_label n (m, lab) = + mk_visual_heap_edge (get_node_id n) (get_node_id m) lab in + match sigma with + | [] -> [] + | Sil.Hpointsto (e, se, t):: sigma' -> + let e_node = select_node_at_address nodes e in + (match e_node with + | None -> assert false + | Some n -> + let target_nodes = compute_target_nodes_from_sexp nodes se prop "" in + let ll = list_map (combine_source_target_label n) target_nodes in + ll @ make_visual_heap_edges nodes sigma' prop + ) + | Sil.Hlseg (_, pred, e1, e2, elist):: sigma' -> + let e1_node = select_node_at_address nodes e1 in + (match e1_node with + | None -> assert false + | Some n -> + let target_nodes = compute_target_nodes_from_sexp nodes (Sil.Eexp (e2, Sil.inst_none)) prop "" in + let ll = list_map (combine_source_target_label n) target_nodes in + ll @ make_visual_heap_edges nodes sigma' prop + ) + + | Sil.Hdllseg (_, pred, e1, e2, e3, e4, elist):: sigma' -> + let e1_node = select_node_at_address nodes e1 in + (match e1_node with + | None -> assert false + | Some n -> + let target_nodesF = compute_target_nodes_from_sexp nodes (Sil.Eexp (e3, Sil.inst_none)) prop "" in + let target_nodesB = compute_target_nodes_from_sexp nodes (Sil.Eexp (e2, Sil.inst_none)) prop "" in + let llF = list_map (combine_source_target_label n) target_nodesF in + let llB = list_map (combine_source_target_label n) target_nodesB in + llF @ llB @ make_visual_heap_edges nodes sigma' prop + ) + +(* from a prop generate and return visual proposition *) +let prop_to_set_of_visual_heaps prop = + let result = ref [] in + working_list:=[(!global_node_counter, Prop.get_sigma prop)]; + incr global_node_counter; + while (!working_list!=[]) do + set_dangling_nodes:=[]; + let (n, h) = list_hd !working_list in + working_list:= list_tl !working_list; + let nodes = make_visual_heap_nodes h in + set_dangling_nodes:= make_set_dangling_nodes nodes h; + let edges = make_visual_heap_edges nodes h prop in + result:= !result @ [(n, nodes @ !set_dangling_nodes, edges)]; + done; + !result + +let rec pointsto_contents_to_xml (co: Sil.strexp) : Io_infer.Xml.node = + match co with + | Sil.Eexp (e, inst) -> + Io_infer.Xml.create_tree "cell" [("content-value", exp_to_xml_string e)] [] + | Sil.Estruct (fel, _) -> + let f (fld, exp) = Io_infer.Xml.create_tree "struct-field" [("id", Ident.fieldname_to_string fld)] [(pointsto_contents_to_xml exp)] in + Io_infer.Xml.create_tree "struct" [] (list_map f fel) + | Sil.Earray (size, nel, _) -> + let f (e, se) = Io_infer.Xml.create_tree "array-element" [("index", exp_to_xml_string e)] [pointsto_contents_to_xml se] in + Io_infer.Xml.create_tree "array" [("size", exp_to_xml_string size)] (list_map f nel) + +(* Convert an atom to xml in a light version. Namely, the expressions are not fully blown-up into *) +(* xml tree but visualized as strings *) +let atom_to_xml_light (a: Sil.atom) : Io_infer.Xml.node = + let kind_info = match a with + | Sil.Aeq _ when Prop.atom_is_inequality a -> + "inequality" + | Sil.Aeq _ -> + "equality" + | Sil.Aneq _ -> + "disequality" in + Io_infer.Xml.create_tree "stack-variable" [("type", kind_info); ("instance", atom_to_xml_string a)] [] + +let xml_pure_info prop = + let pure = Prop.get_pure prop in + let xml_atom_list = list_map atom_to_xml_light pure in + Io_infer.Xml.create_tree "stack" [] xml_atom_list + +(** Return a string describing the kind of a pointsto address *) +let pointsto_addr_kind = function + | Sil.Lvar pv -> + if Sil.pvar_is_global pv + then "global" + else if Sil.pvar_is_local pv && Mangled.equal (Sil.pvar_get_name pv) Ident.name_return + then "return" + else if Sil.pvar_is_local pv + then "parameter" + else "other" + | _ -> "other" + +let heap_node_to_xml node = + match node with + | VH_dangling(id, addr) -> + let atts =[("id", string_of_int id); ("address", exp_to_xml_string addr); ("node-type","dangling"); ("memory-type", pointsto_addr_kind addr)] in + Io_infer.Xml.create_tree "node" atts [] + | VH_pointsto(id, addr, cont, t) -> + let atts =[("id", string_of_int id); ("address", exp_to_xml_string addr); ("node-type","allocated"); ("memory-type", pointsto_addr_kind addr)] in + let contents = pointsto_contents_to_xml cont in + Io_infer.Xml.create_tree "node" atts [contents] + | VH_lseg(id, addr, cont, Sil.Lseg_NE) -> + let atts =[("id", string_of_int id); ("address", exp_to_xml_string addr); ("node-type","single linked list"); ("list-type","non-empty"); ("memory-type", "other")] in + Io_infer.Xml.create_tree "node" atts [] + | VH_lseg(id, addr, cont, Sil.Lseg_PE) -> + let atts =[("id", string_of_int id); ("address", exp_to_xml_string addr); ("node-type","single linked list"); ("list-type","possibly empty"); ("memory-type", "other")] in + Io_infer.Xml.create_tree "node" atts [] + | VH_dllseg(id, addr1, cont1, cont2, addr2, k) -> + let contents1 = pointsto_contents_to_xml (Sil.Eexp (cont1, Sil.inst_none)) in + let contents2 = pointsto_contents_to_xml (Sil.Eexp (cont2, Sil.inst_none)) in + let atts =[("id", string_of_int id); ("addr-first", exp_to_xml_string addr1); ("addr-last", exp_to_xml_string addr2); ("node-type","double linked list"); ("memory-type", "other") ] in + Io_infer.Xml.create_tree "node" atts [contents1 ; contents2] + +let heap_edge_to_xml edge = + let atts =[("source", string_of_int edge.src); ("target", string_of_int edge.trg); ("label", edge.lab) ] in + Io_infer.Xml.create_tree "edge" atts [] + +let visual_heap_to_xml heap = + let (n, nodes, edges) = heap in + let xml_heap_nodes = list_map heap_node_to_xml nodes in + let xml_heap_edges = list_map heap_edge_to_xml edges in + Io_infer.Xml.create_tree "heap" [("id", string_of_int n)] (xml_heap_nodes @ xml_heap_edges) + +(** convert a proposition to xml with the given tag and id *) +let prop_to_xml prop tag_name id = + let visual_heaps = prop_to_set_of_visual_heaps prop in + let xml_visual_heaps = list_map visual_heap_to_xml visual_heaps in + let xml_pure_part = xml_pure_info prop in + let xml_graph = Io_infer.Xml.create_tree tag_name [("id", string_of_int id)] (xml_visual_heaps @ [xml_pure_part]) in + xml_graph + +(** reset the counter used for node and heap identifiers *) +let reset_node_counter () = + global_node_counter := 0 + +let print_specs_xml signature specs loc fmt = + reset_node_counter (); + let do_one_spec pre posts n = + let add_stack_to_prop _prop = + let pre_stack = fst (Prop.sigma_get_stack_nonstack true (Prop.get_sigma pre)) in (* add stack vars from pre *) + let _prop' = Prop.replace_sigma (pre_stack @ Prop.get_sigma _prop) _prop in + Prop.normalize _prop' in + let jj = ref 0 in + let xml_pre = prop_to_xml pre "precondition" !jj in + let xml_spec = xml_pre:: (list_map (fun (po, path) -> jj:=!jj + 1; prop_to_xml (add_stack_to_prop po) "postcondition" !jj) posts) in + Io_infer.Xml.create_tree "specification" [("id", string_of_int n)] xml_spec in + let j = ref 0 in + let list_of_specs_xml = + list_map + (fun s -> + j:=!j + 1; + do_one_spec (Specs.Jprop.to_prop s.Specs.pre) s.Specs.posts !j) + specs in + let xml_specifications = Io_infer.Xml.create_tree "specifications" [] list_of_specs_xml in + let xml_signature = Io_infer.Xml.create_tree "signature" [("name", signature)] [] in + let proc_summary = Io_infer.Xml.create_tree "procedure" [("file", DB.source_file_to_string loc.Sil.file); ("line", string_of_int loc.Sil.line)] [xml_signature; xml_specifications] in + Io_infer.Xml.pp_document true fmt proc_summary diff --git a/infer/src/backend/dotty.mli b/infer/src/backend/dotty.mli new file mode 100644 index 000000000..0ac2cbd67 --- /dev/null +++ b/infer/src/backend/dotty.mli @@ -0,0 +1,54 @@ +(* +* Copyright (c) 2009-2013 Monoidics ltd. +* Copyright (c) 2013- Facebook. +* All rights reserved. +*) + +(** Pretty printing functions in dot format. *) + +(** {2 Propositions} *) + +type kind_of_dotty_prop = + | Generic_proposition + | Spec_precondition + | Spec_postcondition of Prop.normal Prop.t (** the precondition associated with the post *) + | Lambda_pred of int * int * bool + +val reset_proposition_counter : unit -> unit + +val pp_dotty : Format.formatter -> kind_of_dotty_prop -> Prop.normal Prop.t -> unit + +(* create a dotty file with a single proposition *) +val dotty_prop_to_dotty_file: string -> Prop.normal Prop.t -> unit + +(** {2 Sets and lists of propositions} *) + +val pp_dotty_prop_list_in_path: Format.formatter -> Prop.normal Prop.t list -> int -> int -> unit + +val pp_proplist_parsed2dotty_file : string -> Prop.normal Prop.t list -> unit + +(** {2 Contol-Flow Graph} *) + +(** Print the cfg with extra edges *) +val print_icfg_dotty : Cfg.cfg -> ((Cfg.Node.t * Cfg.Node.t) list) -> unit + +(** [store_icfg_to_file f cfg] saves the dotty representation of the control flow graph [cfg] into the file [f] *) +val store_icfg_to_file: string -> Cfg.cfg -> unit + +(** {2 Specs} *) +val reset_dotty_spec_counter : unit -> unit + +(** Dotty printing for specs *) +val pp_speclist_dotty_file : DB.filename -> Prop.normal Specs.spec list -> unit + +(* create a dotty file with a single proposition *) +val dotty_prop_to_dotty_file : string -> Prop.normal Prop.t -> unit + +(** reset the counter used for node and heap identifiers *) +val reset_node_counter : unit -> unit + +(** convert a proposition to xml with the given tag and id *) +val prop_to_xml : Prop.normal Prop.t -> string -> int -> Io_infer.Xml.node + +(** Print a list of specs in XML format *) +val print_specs_xml : string -> Prop.normal Specs.spec list -> Sil.location -> Format.formatter -> unit diff --git a/infer/src/backend/errdesc.ml b/infer/src/backend/errdesc.ml new file mode 100644 index 000000000..054e5737d --- /dev/null +++ b/infer/src/backend/errdesc.ml @@ -0,0 +1,1020 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Create descriptions of analysis errors *) + +module L = Logging +module F = Format +open Utils + +let pvar_to_string pvar = + Mangled.to_string (Sil.pvar_get_name pvar) + +(** Check whether the hpred is a |-> representing a resource in the Racquire state *) +let hpred_is_open_resource prop = function + | Sil.Hpointsto(e, _, _) -> + (match Prop.get_resource_undef_attribute prop e with + | Some (Sil.Aresource { Sil.ra_kind = Sil.Racquire; Sil.ra_res = res }) -> Some res + | _ -> None) + | _ -> + None + +(** Explain a deallocate stack variable error *) +let explain_deallocate_stack_var pvar ra = + let pvar_str = pvar_to_string pvar in + Localise.desc_deallocate_stack_variable pvar_str ra.Sil.ra_pname ra.Sil.ra_loc + +(** Explain a deallocate constant string error *) +let explain_deallocate_constant_string s ra = + let const_str = + let pp fmt () = + Sil.pp_exp pe_text fmt (Sil.Const (Sil.Cstr s)) in + pp_to_string pp () in + Localise.desc_deallocate_static_memory const_str ra.Sil.ra_pname ra.Sil.ra_loc + +let verbose = Config.trace_error + +(** Find the Set instruction used to assign [id] to a program variable, if any *) +let find_variable_assigment node id : Sil.instr option = + let res = ref None in + let node_instrs = Cfg.Node.get_instrs node in + let find_set instr = match instr with + | Sil.Set (Sil.Lvar pv, _, e, _) when Sil.exp_equal (Sil.Var id) e -> + res := Some instr; + true + | _ -> false in + ignore (list_exists find_set node_instrs); + !res + +(** Check if a nullify instruction exists for the program variable after the given instruction *) +let find_nullify_after_instr node instr pvar : bool = + let node_instrs = Cfg.Node.get_instrs node in + let found_instr = ref false in + let find_nullify = function + | Sil.Nullify (pv, _, _) when !found_instr -> Sil.pvar_equal pv pvar + | _instr -> + if instr = _instr then found_instr := true; + false in + list_exists find_nullify node_instrs + +(** Find the other prune node of a conditional (e.g. the false branch given the true branch of a conditional) *) +let find_other_prune_node node = + match Cfg.Node.get_preds node with + | [n_pre] -> + (match Cfg.Node.get_succs n_pre with + | [n1; n2] -> + if Cfg.Node.equal n1 node then Some n2 else Some n1 + | _ -> None) + | _ -> None + +(** Return true if [id] is assigned to a program variable which is then nullified *) +let id_is_assigned_then_dead node id = + match find_variable_assigment node id with + | Some (Sil.Set (Sil.Lvar pvar, _, _, _) as instr) when Sil.pvar_is_local pvar || Sil.pvar_is_callee pvar -> + let is_prune = match Cfg.Node.get_kind node with + | Cfg.Node.Prune_node _ -> true + | _ -> false in + let prune_check = function (* if prune node, check that it's also nullified in the other branch *) + | Some node' -> + (match Cfg.Node.get_instrs node' with + | instr':: _ -> find_nullify_after_instr node' instr' pvar + | _ -> false) + | _ -> false in + find_nullify_after_instr node instr pvar + && (not is_prune || prune_check (find_other_prune_node node)) + | _ -> false + +(** Find the function call instruction used to initialize normal variable [id], +and return the function name and arguments *) +let find_normal_variable_funcall + (node: Cfg.Node.t) + (id: Ident.t): (Sil.exp * (Sil.exp list) * Sil.location * Sil.call_flags) option = + let res = ref None in + let node_instrs = Cfg.Node.get_instrs node in + let find_declaration = function + | Sil.Call ([id0], fun_exp, args, loc, call_flags) when Ident.equal id id0 -> + res := Some (fun_exp, list_map fst args, loc, call_flags); + true + | _ -> false in + ignore (list_exists find_declaration node_instrs); + if !verbose && !res == None then (L.d_str ("find_normal_variable_funcall could not find " ^ + Ident.to_string id ^ " in node " ^ string_of_int (Cfg.Node.get_id node)); L.d_ln ()); + !res + +(** Find a program variable assignment in the current node or predecessors. *) +let find_program_variable_assignment node pvar : (Cfg.Node.t * Ident.t) option = + let visited = ref Cfg.NodeSet.empty in + let rec find node = + if Cfg.NodeSet.mem node !visited then None + else + begin + visited := Cfg.NodeSet.add node !visited; + let res = ref None in + let find_instr = function + | Sil.Set (Sil.Lvar _pvar, _, Sil.Var id, _) when Sil.pvar_equal pvar _pvar && Ident.is_normal id -> + res := Some (node, id); + true + | _ -> false in + if list_exists find_instr (Cfg.Node.get_instrs node) + then !res + else match Cfg.Node.get_preds node with + | [pred_node] -> + find pred_node + | [pn1; pn2] -> + (match find pn1 with + | None -> find pn2 + | x -> x) + | _ -> None (* either 0 or >2 predecessors *) + end in + find node + +(** Find a program variable assignment to id in the current node or predecessors. *) +let find_ident_assignment node id : (Cfg.Node.t * Sil.exp) option = + let visited = ref Cfg.NodeSet.empty in + let rec find node = + if Cfg.NodeSet.mem node !visited then None + else + begin + visited := Cfg.NodeSet.add node !visited; + let res = ref None in + let find_instr = function + | Sil.Letderef(_id, e, _,_) when Ident.equal _id id -> + res := Some (node, e); + true + | _ -> false in + if list_exists find_instr (Cfg.Node.get_instrs node) + then !res + else match Cfg.Node.get_preds node with + | [pred_node] -> + find pred_node + | [pn1; pn2] -> + (match find pn1 with + | None -> find pn2 + | x -> x) + | _ -> None (* either 0 or >2 predecessors *) + end in + find node + +(** Find a boolean assignment to a temporary variable holding a boolean condition. +The boolean parameter indicates whether the true or false branch is required. *) +let rec find_boolean_assignment node pvar true_branch : Cfg.Node.t option = + let find_instr n = + let filter = function + | Sil.Set (Sil.Lvar _pvar, _, Sil.Const (Sil.Cint i), _) when Sil.pvar_equal pvar _pvar -> + Sil.Int.iszero i <> true_branch + | _ -> false in + list_exists filter (Cfg.Node.get_instrs n) in + match Cfg.Node.get_preds node with + | [pred_node] -> find_boolean_assignment pred_node pvar true_branch + | [n1; n2] -> + if find_instr n1 then (Some n1) + else if find_instr n2 then (Some n2) + else None + | _ -> None + +(** Check whether the program variable is a temporary one generated by CIL *) +let pvar_is_cil_tmp pvar = + Sil.pvar_is_local pvar && + let name = pvar_to_string pvar in + string_is_prefix "_tmp" name + +(** Check whether the program variable is a temporary one generated by EDG *) +let pvar_is_edg_tmp pvar = + Sil.pvar_is_local pvar && + let name = pvar_to_string pvar in + string_is_prefix "__T" name + +(** Check whether the program variable is a temporary one generated by sawja *) +let pvar_is_sawja_tmp pvar = + Sil.pvar_is_local pvar && + let name = pvar_to_string pvar in + string_is_prefix "$irvar" name || + string_is_prefix "$T" name || + string_is_prefix "$bc" name + +let edg_native_tmp_var_name_prefix = "__temp_var_" + +(** Check whether the program variable is a temporary one generated by EDG *) +let pvar_is_edg_tmp pvar = + Sil.pvar_is_local pvar && + let name = pvar_to_string pvar in + string_is_prefix edg_native_tmp_var_name_prefix name + +(** Check whether the program variable is a temporary generated by the front-end *) +let pvar_is_frontend_tmp pvar = + if !Sil.curr_language = Sil.Java then pvar_is_sawja_tmp pvar + else pvar_is_cil_tmp pvar || pvar_is_edg_tmp pvar + +(** Find the Letderef instruction used to declare normal variable [id], +and return the expression dereferenced to initialize [id] *) +let rec _find_normal_variable_letderef (seen : Sil.ExpSet.t) node id : Sil.dexp option = + let res = ref None in + let node_instrs = Cfg.Node.get_instrs node in + let rec find_declaration = function + | Sil.Letderef (id0, e, _, _) when Ident.equal id id0 -> + if !verbose then (L.d_str "find_normal_variable_letderef defining "; Sil.d_exp e; L.d_ln ()); + res := _exp_lv_dexp seen node e; + true + | Sil.Call ([id0], Sil.Const (Sil.Cfun pn), (e, _):: _, _, _) when Ident.equal id id0 && Procname.equal pn (Procname.from_string "__cast") -> + if !verbose then (L.d_str "find_normal_variable_letderef cast on "; Sil.d_exp e; L.d_ln ()); + res := _exp_rv_dexp seen node e; + true + | Sil.Call ([id0], (Sil.Const (Sil.Cfun pname) as fun_exp), args, loc, call_flags) + when Ident.equal id id0 -> + if !verbose then (L.d_str "find_normal_variable_letderef function call "; Sil.d_exp fun_exp; L.d_ln ()); + + let fun_dexp = Sil.Dconst (Sil.Cfun pname) in + let args_dexp = + let args_dexpo = list_map (fun (e, _) -> _exp_rv_dexp seen node e) args in + if list_exists (fun x -> x = None) args_dexpo + then [] + else + let unNone = function Some x -> x | None -> assert false in + list_map unNone args_dexpo in + + res := Some (Sil.Dretcall (fun_dexp, args_dexp, loc, call_flags)); + true + | _ -> false in + ignore (list_exists find_declaration node_instrs); + if !verbose && !res == None then (L.d_str ("find_normal_variable_letderef could not find " ^ + Ident.to_string id ^ " in node " ^ string_of_int (Cfg.Node.get_id node)); L.d_ln ()); + !res + +(** describe lvalue [e] as a dexp *) +and _exp_lv_dexp (_seen : Sil.ExpSet.t) node e : Sil.dexp option = + if Sil.ExpSet.mem e _seen then + (L.d_str "exp_lv_dexp: cycle detected"; Sil.d_exp e; L.d_ln (); None) + else + let seen = Sil.ExpSet.add e _seen in + match Prop.exp_normalize_noabs Sil.sub_empty e with + | Sil.Const c -> + if !verbose then (L.d_str "exp_lv_dexp: constant "; Sil.d_exp e; L.d_ln ()); + Some (Sil.Dderef (Sil.Dconst c)) + | Sil.BinOp(Sil.PlusPI, e1, e2) -> + if !verbose then (L.d_str "exp_lv_dexp: (e1 +PI e2) "; Sil.d_exp e; L.d_ln ()); + (match _exp_lv_dexp seen node e1, _exp_rv_dexp seen node e2 with + | Some de1, Some de2 -> Some (Sil.Dbinop(Sil.PlusPI, de1, de2)) + | _ -> None) + | Sil.Var id when Ident.is_normal id -> + if !verbose then (L.d_str "exp_lv_dexp: normal var "; Sil.d_exp e; L.d_ln ()); + (match _find_normal_variable_letderef seen node id with + | None -> None + | Some de -> Some (Sil.Dderef de)) + | Sil.Lvar pvar -> + if !verbose then (L.d_str "exp_lv_dexp: program var "; Sil.d_exp e; L.d_ln ()); + if pvar_is_frontend_tmp pvar then + begin + match find_program_variable_assignment node pvar with + | None -> + (* + L.err "exp_string_lv: Cannot find assignment of %s@." (pvar_to_string pvar); + exit(1) *) + None + | Some (node', id) -> + begin + match find_normal_variable_funcall node' id with + | Some (fun_exp, eargs, loc, call_flags) -> + let fun_dexpo = _exp_rv_dexp seen node' fun_exp in + let blame_args = list_map (_exp_rv_dexp seen node') eargs in + if list_exists (fun x -> x = None) (fun_dexpo:: blame_args) then None + else + let unNone = function Some x -> x | None -> assert false in + let args = list_map unNone blame_args in + Some (Sil.Dfcall (unNone fun_dexpo, args, loc, call_flags)) + | None -> + _exp_rv_dexp seen node' (Sil.Var id) + end + end + else Some (Sil.Dpvar pvar) + | Sil.Lfield (Sil.Var id, f, typ) when Ident.is_normal id -> + if !verbose then + begin + L.d_str "exp_lv_dexp: Lfield with var "; + Sil.d_exp (Sil.Var id); + L.d_str (" " ^ Ident.fieldname_to_string f); + L.d_ln () + end; + (match _find_normal_variable_letderef seen node id with + | None -> None + | Some de -> Some (Sil.Darrow (de, f))) + | Sil.Lfield (e1, f, typ) -> + if !verbose then + begin + L.d_str "exp_lv_dexp: Lfield "; + Sil.d_exp e1; + L.d_str (" " ^ Ident.fieldname_to_string f); + L.d_ln () + end; + (match _exp_lv_dexp seen node e1 with + | None -> None + | Some de -> Some (Sil.Ddot (de, f))) + | Sil.Lindex (e1, e2) -> + if !verbose then + begin + L.d_str "exp_lv_dexp: Lindex "; + Sil.d_exp e1; + L.d_str " "; + Sil.d_exp e2; + L.d_ln () + end; + (match _exp_lv_dexp seen node e1, _exp_rv_dexp seen node e2 with + | None, _ -> None + | Some de1, None -> + (* even if the index is unknown, the array info is useful for bound errors *) + Some (Sil.Darray (de1, Sil.Dunknown)) + | Some de1, Some de2 -> Some (Sil.Darray (de1, de2))) + | _ -> + if !verbose then (L.d_str "exp_lv_dexp: no match for "; Sil.d_exp e; L.d_ln ()); + None + +(** describe rvalue [e] as a dexp *) +and _exp_rv_dexp (_seen : Sil.ExpSet.t) node e : Sil.dexp option = + if Sil.ExpSet.mem e _seen then + (L.d_str "exp_rv_dexp: cycle detected"; Sil.d_exp e; L.d_ln (); None) + else + let seen = Sil.ExpSet.add e _seen in + match e with + | Sil.Const c -> + if !verbose then (L.d_str "exp_rv_dexp: constant "; Sil.d_exp e; L.d_ln ()); + Some (Sil.Dconst c) + | Sil.Lvar pv -> + if !verbose then (L.d_str "exp_rv_dexp: program var "; Sil.d_exp e; L.d_ln ()); + if pvar_is_frontend_tmp pv + then _exp_lv_dexp _seen (* avoid spurious cycle detection *) node e + else Some (Sil.Dpvaraddr pv) + | Sil.Var id when Ident.is_normal id -> + if !verbose then (L.d_str "exp_rv_dexp: normal var "; Sil.d_exp e; L.d_ln ()); + _find_normal_variable_letderef seen node id + | Sil.Lfield (e1, f, typ) -> + if !verbose then + begin + L.d_str "exp_rv_dexp: Lfield "; + Sil.d_exp e1; + L.d_str (" " ^ Ident.fieldname_to_string f); + L.d_ln () + end; + (match _exp_rv_dexp seen node e1 with + | None -> None + | Some de -> Some (Sil.Ddot(de, f))) + | Sil.Lindex (e1, e2) -> + if !verbose then + begin + L.d_str "exp_rv_dexp: Lindex "; + Sil.d_exp e1; + L.d_str " "; + Sil.d_exp e2; + L.d_ln () + end; + (match _exp_rv_dexp seen node e1, _exp_rv_dexp seen node e2 with + | None, _ | _, None -> None + | Some de1, Some de2 -> Some (Sil.Darray(de1, de2))) + | Sil.BinOp (op, e1, e2) -> + if !verbose then (L.d_str "exp_rv_dexp: BinOp "; Sil.d_exp e; L.d_ln ()); + (match _exp_rv_dexp seen node e1, _exp_rv_dexp seen node e2 with + | None, _ | _, None -> None + | Some de1, Some de2 -> Some (Sil.Dbinop (op, de1, de2))) + | Sil.UnOp (op, e1, _) -> + if !verbose then (L.d_str "exp_rv_dexp: UnOp "; Sil.d_exp e; L.d_ln ()); + (match _exp_rv_dexp seen node e1 with + | None -> None + | Some de1 -> Some (Sil.Dunop (op, de1))) + | Sil.Cast (_, e1) -> + if !verbose then (L.d_str "exp_rv_dexp: Cast "; Sil.d_exp e; L.d_ln ()); + _exp_rv_dexp seen node e1 + | Sil.Sizeof (typ, sub) -> + if !verbose then (L.d_str "exp_rv_dexp: type "; Sil.d_exp e; L.d_ln ()); + Some (Sil.Dsizeof (typ, sub)) + | _ -> + if !verbose then (L.d_str "exp_rv_dexp: no match for "; Sil.d_exp e; L.d_ln ()); + None + +let find_normal_variable_letderef = _find_normal_variable_letderef Sil.ExpSet.empty +let exp_lv_dexp = _exp_lv_dexp Sil.ExpSet.empty +let exp_rv_dexp = _exp_rv_dexp Sil.ExpSet.empty + +(** Produce a description of a mismatch between an allocation function and a deallocation function *) +let explain_allocation_mismatch ra_alloc ra_dealloc = + let get_primitive_called is_alloc ra = + (* primitive alloc/dealloc function ultimately used, and function actually called *) + (* e.g. malloc and my_malloc *) + let primitive = match ra.Sil.ra_res with + | Sil.Rmemory mk_alloc -> (if is_alloc then Sil.mem_alloc_pname else Sil.mem_dealloc_pname) mk_alloc + | _ -> ra_alloc.Sil.ra_pname in + let called = ra.Sil.ra_pname in + (primitive, called, ra.Sil.ra_loc) in + Localise.desc_allocation_mismatch (get_primitive_called true ra_alloc) (get_primitive_called false ra_dealloc) + +(** check whether the type of leaked [hpred] appears as a predicate in an inductive predicate in [prop] *) +let leak_from_list_abstraction hpred prop = + let hpred_type = function + | Sil.Hpointsto (_, _, texp) -> + Some texp + | Sil.Hlseg (_, { Sil.body =[Sil.Hpointsto (_, _, texp)]}, _, _, _) -> + Some texp + | Sil.Hdllseg (_, { Sil.body_dll =[Sil.Hpointsto (_, _, texp)]}, _, _, _, _, _) -> + Some texp + | _ -> None in + let found = ref false in + let check_hpred texp hp = match hpred_type hp with + | Some texp' when Sil.exp_equal texp texp' -> found := true + | _ -> () in + let check_hpara texp n hpara = + list_iter (check_hpred texp) hpara.Sil.body in + let check_hpara_dll texp n hpara = + list_iter (check_hpred texp) hpara.Sil.body_dll in + match hpred_type hpred with + | Some texp -> + let env = Prop.prop_pred_env prop in + Sil.Predicates.iter env (check_hpara texp) (check_hpara_dll texp); + if !found then (L.d_str "leak_from_list_abstraction of predicate of type "; Sil.d_texp_full texp; L.d_ln()); + !found + | None -> false + +(** find the type of hpred, if any *) +let find_hpred_typ hpred = match hpred with + | Sil.Hpointsto (_, _, texp) -> Some texp + | _ -> None + +(** find the type of pvar and remove the pointer, if any *) +let find_pvar_typ_without_ptr tenv prop pvar = + let res = ref None in + let do_hpred = function + | Sil.Hpointsto (e, _, te) when Sil.exp_equal e (Sil.Lvar pvar) -> + res := Some te + | _ -> () in + list_iter do_hpred (Prop.get_sigma prop); + !res + +(** Produce a description of a leak by looking at the current state. +If the current instruction is a variable nullify, blame the variable. +If it is an abstraction, blame any variable nullify at the current node. +If there is an alloc attribute, print the function call and line number. *) +let explain_leak tenv hpred prop alloc_att_opt bucket = + let instro = State.get_instr () in + let loc = State.get_loc () in + let node = State.get_node () in + let node_instrs = Cfg.Node.get_instrs node in + let hpred_typ_opt = find_hpred_typ hpred in + let value_str_from_pvars_vpath pvars vpath = + if pvars <> [] then + begin + let pp = pp_seq (Sil.pp_pvar_value pe_text) in + let desc_string = pp_to_string pp pvars in + Some desc_string + end + else match vpath with + | Some de -> + Some (Sil.dexp_to_string de) + | None -> None in + let res_action_opt, resource_opt, vpath = match alloc_att_opt with + | Some (Sil.Aresource ({ Sil.ra_kind = Sil.Racquire } as ra)) -> + Some ra, Some ra.Sil.ra_res, ra.Sil.ra_vpath + | _ -> (None, None, None) in + let is_file = match resource_opt with + | Some Sil.Rfile -> true + | _ -> false in + let check_pvar pvar = (* check that pvar is local or global and has the same type as the leaked hpred *) + (Sil.pvar_is_local pvar || Sil.pvar_is_global pvar) && + not (pvar_is_frontend_tmp pvar) && + match hpred_typ_opt, find_pvar_typ_without_ptr tenv prop pvar with + | Some (Sil.Sizeof (t1, st1)), Some (Sil.Sizeof (Sil.Tptr (_t2, _), st2)) -> + (try + let t2 = Sil.expand_type tenv _t2 in + Sil.typ_equal t1 t2 + with exn when exn_not_timeout exn -> false) + | Some (Sil.Sizeof (Sil.Tint _, _)), Some (Sil.Sizeof (Sil.Tint _, _)) when is_file -> (* must be a file opened with "open" *) + true + | _ -> false in + let value_str = match instro with + | None -> + if !verbose then (L.d_str "explain_leak: no current instruction"; L.d_ln ()); + value_str_from_pvars_vpath [] vpath + | Some (Sil.Nullify (pvar, loc, _)) when check_pvar pvar -> + if !verbose then (L.d_str "explain_leak: current instruction is Nullify for pvar "; Sil.d_pvar pvar; L.d_ln ()); + (match exp_lv_dexp (State.get_node ()) (Sil.Lvar pvar) with + | None -> None + | Some de -> Some (Sil.dexp_to_string de)) + | Some (Sil.Abstract _) -> + if !verbose then (L.d_str "explain_leak: current instruction is Abstract"; L.d_ln ()); + let get_nullify = function + | Sil.Nullify (pvar, _, _) when check_pvar pvar -> + if !verbose then (L.d_str "explain_leak: found nullify before Abstract for pvar "; Sil.d_pvar pvar; L.d_ln ()); + [pvar] + | _ -> [] in + let nullify_pvars = list_flatten (list_map get_nullify node_instrs) in + let nullify_pvars_notmp = list_filter (fun pvar -> not (pvar_is_frontend_tmp pvar)) nullify_pvars in + value_str_from_pvars_vpath nullify_pvars_notmp vpath + | Some (Sil.Set (lexp, _, _, _)) when vpath = None -> + if !verbose then (L.d_str "explain_leak: current instruction Set for "; Sil.d_exp lexp; L.d_ln ()); + (match exp_lv_dexp node lexp with + | Some dexp -> Some (Sil.dexp_to_string dexp) + | None -> None) + | Some instr -> + if !verbose then (L.d_str "explain_leak: case not matched in instr "; Sil.d_instr instr; L.d_ln()); + value_str_from_pvars_vpath [] vpath in + let exn_cat = (* decide whether Exn_user or Exn_developer *) + match resource_opt with + | Some _ -> (* we know it has been allocated *) + Exceptions.Exn_user + | None -> + if leak_from_list_abstraction hpred prop && value_str != None + then Exceptions.Exn_user (* we don't know it's been allocated, but it's coming from list abstraction and we have a name *) + else Exceptions.Exn_developer in + exn_cat, Localise.desc_leak value_str resource_opt res_action_opt loc bucket + +(** find the dexp, if any, where the given value is stored +also return the type of the value if found *) +let vpath_find prop _exp : Sil.dexp option * Sil.typ option = + if !verbose then (L.d_str "in vpath_find exp:"; Sil.d_exp _exp; L.d_ln ()); + let rec find sigma_acc sigma_todo exp = + let do_fse res sigma_acc' sigma_todo' lexp texp (f, se) = match se with + | Sil.Eexp (e, _) when Sil.exp_equal exp e -> + let sigma' = (list_rev_append sigma_acc' sigma_todo') in + (match lexp with + | Sil.Lvar pv -> + let typo = match texp with + | Sil.Sizeof (Sil.Tstruct (ftl, ftal, _, _, _, _, _), _) -> + (try + let _, t, _ = list_find (fun (_f, _t, _) -> Ident.fieldname_equal _f f) ftl in + Some t + with Not_found -> None) + | _ -> None in + res := Some (Sil.Ddot (Sil.Dpvar pv, f)), typo + | Sil.Var id -> + (match find [] sigma' (Sil.Var id) with + | None, _ -> () + | Some de, typo -> res := Some (Sil.Darrow (de, f)), typo) + | lexp -> + if !verbose then (L.d_str "vpath_find do_fse: no match on Eexp "; Sil.d_exp lexp; L.d_ln ())) + | _ -> () in + let do_sexp sigma_acc' sigma_todo' lexp sexp texp = match sexp with + | Sil.Eexp (e, _) when Sil.exp_equal exp e -> + let sigma' = (list_rev_append sigma_acc' sigma_todo') in + (match lexp with + | Sil.Lvar pv when not (pvar_is_frontend_tmp pv) -> + let typo = match texp with + | Sil.Sizeof (typ, _) -> Some typ + | _ -> None in + Some (Sil.Dpvar pv), typo + | Sil.Var id -> + (match find [] sigma' (Sil.Var id) with + | None, typo -> None, typo + | Some de, typo -> Some (Sil.Dderef de), typo) + | lexp -> + if !verbose then (L.d_str "vpath_find do_sexp: no match on Eexp "; Sil.d_exp lexp; L.d_ln ()); + None, None) + | Sil.Estruct (fsel, _) -> + let res = ref (None, None) in + list_iter (do_fse res sigma_acc' sigma_todo' lexp texp) fsel; + !res + | sexp -> + None, None in + let do_hpred sigma_acc' sigma_todo' = + let substituted_from_normal id = + let filter = function + | (ni, Sil.Var id') -> Ident.is_normal ni && Ident.equal id' id + | _ -> false in + list_exists filter (Sil.sub_to_list (Prop.get_sub prop)) in + function + | Sil.Hpointsto (Sil.Lvar pv, sexp, texp) when (Sil.pvar_is_local pv || Sil.pvar_is_global pv || Sil.pvar_is_seed pv) -> + do_sexp sigma_acc' sigma_todo' (Sil.Lvar pv) sexp texp + | Sil.Hpointsto (Sil.Var id, sexp, texp) when Ident.is_normal id || (Ident.is_footprint id && substituted_from_normal id) -> + do_sexp sigma_acc' sigma_todo' (Sil.Var id) sexp texp + | hpred -> + (* if !verbose then (L.d_str "vpath_find do_hpred: no match "; Sil.d_hpred hpred; L.d_ln ()); *) + None, None in + match sigma_todo with + | [] -> None, None + | hpred:: sigma_todo' -> + (match do_hpred sigma_acc sigma_todo' hpred with + | Some de, typo -> Some de, typo + | None, _ -> find (hpred:: sigma_acc) sigma_todo' exp) in + let res = find [] (Prop.get_sigma prop) _exp in + if !verbose then begin + match res with + | None, _ -> L.d_str "vpath_find: cannot find "; Sil.d_exp _exp; L.d_ln () + | Some de, typo -> L.d_str "vpath_find: found "; L.d_str (Sil.dexp_to_string de); L.d_str " : "; + match typo with + | None -> L.d_str " No type" + | Some typ -> Sil.d_typ_full typ; + L.d_ln () + end; + res + +(** produce a description of the access from the instrumentation at position [dexp] in [prop] *) +let explain_dexp_access prop dexp is_nullable = + let sigma = Prop.get_sigma prop in + let sexpo_to_inst = function + | None -> None + | Some (Sil.Eexp (_, inst)) -> Some inst + | Some se -> + if !verbose then (L.d_str "sexpo_to_inst: can't find inst "; Sil.d_sexp se; L.d_ln()); + None in + let find_ptsto (e : Sil.exp) : Sil.strexp option = + let res = ref None in + let do_hpred = function + | Sil.Hpointsto (e', se, _) when Sil.exp_equal e e' -> + res := Some se + | _ -> () in + list_iter do_hpred sigma; + !res in + let rec lookup_fld fsel f = match fsel with + | [] -> + if !verbose then (L.d_strln ("lookup_fld: can't find field " ^ Ident.fieldname_to_string f)); + None + | (f1, se):: fsel' -> + if Ident.fieldname_equal f1 f then Some se + else lookup_fld fsel' f in + let rec lookup_esel esel e = match esel with + | [] -> + if !verbose then (L.d_str "lookup_esel: can't find index "; Sil.d_exp e; L.d_ln ()); + None + | (e1, se):: esel' -> + if Sil.exp_equal e1 e then Some se + else lookup_esel esel' e in + let rec lookup : Sil.dexp -> Sil.strexp option = function + | Sil.Dconst c -> + Some (Sil.Eexp (Sil.Const c, Sil.inst_none)) + | Sil.Darray (de1, de2) -> + (match lookup de1, lookup de2 with + | None, _ | _, None -> None + | Some Sil.Earray (_, esel, _), Some Sil.Eexp (e, _) -> + lookup_esel esel e + | Some se1, Some se2 -> + if !verbose then (L.d_str "lookup: case not matched on Darray "; Sil.d_sexp se1; L.d_str " "; Sil.d_sexp se2; L.d_ln()); + None) + | Sil.Darrow (de1, f) -> + (match lookup (Sil.Dderef de1) with + | None -> None + | Some Sil.Estruct (fsel, _) -> + lookup_fld fsel f + | Some _ -> + if !verbose then (L.d_str "lookup: case not matched on Darrow "; L.d_ln ()); + None) + | Sil.Ddot (de1, f) -> + (match lookup de1 with + | None -> None + | Some Sil.Estruct (fsel, _) -> + lookup_fld fsel f + | Some _ -> + if !verbose then (L.d_str "lookup: case not matched on Ddot "; L.d_ln ()); + None) + | Sil.Dpvar pvar -> + if !verbose then (L.d_str "lookup: found Dpvar "; L.d_ln ()); + (find_ptsto (Sil.Lvar pvar)) + | Sil.Dderef de -> + (match lookup de with + | None -> None + | Some (Sil.Eexp (e, _)) -> find_ptsto e + | Some _ -> None) + | (Sil.Dbinop(Sil.PlusPI, Sil.Dpvar pvar, Sil.Dconst c) as de) -> + if !verbose then (L.d_strln ("lookup: case )pvar + constant) " ^ Sil.dexp_to_string de)); + None + | Sil.Dfcall (Sil.Dconst c, _, loc, _) -> + if !verbose then (L.d_strln "lookup: found Dfcall "); + (match c with + | Sil.Cfun pn -> (* Treat function as an update *) + Some (Sil.Eexp (Sil.Const c, Sil.Ireturn_from_call loc.Sil.line)) + | _ -> None) + | de -> + if !verbose then (L.d_strln ("lookup: unknown case not matched " ^ Sil.dexp_to_string de)); + None in + let access_opt = match sexpo_to_inst (lookup dexp) with + | None -> + if !verbose then (L.d_strln ("explain_dexp_access: cannot find inst of " ^ Sil.dexp_to_string dexp)); + None + | Some (Sil.Iupdate (_, ncf, n, pos)) -> + Some (Localise.Last_assigned (n, ncf)) + | Some (Sil.Irearrange (_, _, n, pos)) -> + Some (Localise.Last_accessed (n, is_nullable)) + | Some (Sil.Ireturn_from_call n) -> + Some (Localise.Returned_from_call n) + | Some Sil.Ialloc when !Sil.curr_language = Sil.Java -> + Some Localise.Initialized_automatically + | Some inst -> + if !verbose then (L.d_strln ("explain_dexp_access: inst is not an update " ^ Sil.inst_to_string inst)); + None in + access_opt + +let explain_dereference_access outermost_array is_nullable _de_opt prop = + let de_opt = + let rec remove_outermost_array_access = function (* remove outermost array access from [de] *) + | Sil.Dbinop(Sil.PlusPI, de1, de2) -> (* remove pointer arithmetic before array access *) + remove_outermost_array_access de1 + | Sil.Darray(Sil.Dderef de1, de2) -> (* array access is a deref already: remove both *) + de1 + | Sil.Darray(de1, de2) -> (* remove array access *) + de1 + | Sil.Dderef de -> (* remove implicit array access *) + de + | Sil.Ddot (de, _) -> (* remove field access before array access *) + remove_outermost_array_access de + | de -> de in + match _de_opt with + | None -> None + | Some de -> + Some (if outermost_array then remove_outermost_array_access de else de) in + let value_str = match de_opt with + | Some de -> + Sil.dexp_to_string de + | None -> "" in + let access_opt = match de_opt with + | Some de -> explain_dexp_access prop de is_nullable + | None -> None in + (value_str, access_opt) + +(** Create a description of a dereference operation *) +let create_dereference_desc + ?use_buckets:(use_buckets=false) + ?outermost_array:(outermost_array=false) + ?is_nullable:(is_nullable=false) + ?is_premature_nil:(is_premature_nil=false) + _de_opt deref_str prop loc = + let value_str, access_opt = + explain_dereference_access outermost_array is_nullable _de_opt prop in + let access_opt' = match access_opt with + | Some (Localise.Last_accessed _) when outermost_array -> None (* don't report last accessed for arrays *) + | _ -> access_opt in + let desc = Localise.dereference_string deref_str value_str access_opt' loc in + let desc = + if !Sil.curr_language = Sil.C_CPP && not is_premature_nil then + match _de_opt with + | Some (Sil.Dpvar pvar) + | Some (Sil.Dpvaraddr pvar) -> + (match Prop.get_objc_null_attribute prop (Sil.Lvar pvar) with + | Some (Sil.Aobjc_null info) -> Localise.parameter_field_not_null_checked_desc desc info + | _ -> desc) + | _ -> desc + else desc in + if use_buckets then Buckets.classify_access desc access_opt' + else desc + +(** explain memory access performed by the current instruction +if outermost_array is true, the outermost array access is removed +if outermost_dereference is true, stop at the outermost dereference +(skipping e.g. outermost field access) *) +let _explain_access + ?use_buckets:(use_buckets=false) + ?outermost_array:(outermost_array=false) + ?outermost_dereference:(outermost_dereference=false) + ?is_nullable:(is_nullable=false) + ?is_premature_nil:(is_premature_nil=false) + deref_str prop loc = + let rec find_outermost_dereference node e = match e with + | Sil.Const c -> + if !verbose then (L.d_str "find_outermost_dereference: constant "; Sil.d_exp e; L.d_ln ()); + exp_lv_dexp node e + | Sil.Var id when Ident.is_normal id -> (* look up the normal variable declaration *) + if !verbose then (L.d_str "find_outermost_dereference: normal var "; Sil.d_exp e; L.d_ln ()); + find_normal_variable_letderef node id + | Sil.Lfield (e', f, t) -> + if !verbose then (L.d_str "find_outermost_dereference: Lfield "; Sil.d_exp e; L.d_ln ()); + find_outermost_dereference node e' + | Sil.Lindex(e', e2) -> + if !verbose then (L.d_str "find_outermost_dereference: Lindex "; Sil.d_exp e; L.d_ln ()); + find_outermost_dereference node e' + | Sil.Lvar _ -> + if !verbose then (L.d_str "find_outermost_dereference: Lvar "; Sil.d_exp e; L.d_ln ()); + exp_lv_dexp node e + | Sil.BinOp(Sil.PlusPI, Sil.Lvar _, _) -> + if !verbose then (L.d_str "find_outermost_dereference: Lvar+index "; Sil.d_exp e; L.d_ln ()); + exp_lv_dexp node e + | Sil.Cast (_, e') -> + if !verbose then (L.d_str "find_outermost_dereference: cast "; Sil.d_exp e; L.d_ln ()); + find_outermost_dereference node e' + | Sil.BinOp(Sil.PtrFld, _, e') -> + if !verbose then (L.d_str "find_outermost_dereference: PtrFld "; Sil.d_exp e; L.d_ln ()); + find_outermost_dereference node e' + | _ -> + if !verbose then (L.d_str "find_outermost_dereference: no match for "; Sil.d_exp e; L.d_ln ()); + None in + let find_exp_dereferenced node = match State.get_instr () with + | Some Sil.Set (e, _, _, _) -> + if !verbose then (L.d_str "explain_dereference Sil.Set "; Sil.d_exp e; L.d_ln ()); + Some e + | Some Sil.Letderef (_, e, _, _) -> + if !verbose then (L.d_str "explain_dereference Sil.Leteref "; Sil.d_exp e; L.d_ln ()); + Some e + | Some Sil.Call (_, Sil.Const (Sil.Cfun fn), [(e, typ)], loc, _) when Procname.to_string fn = "free" -> + if !verbose then (L.d_str "explain_dereference Sil.Call "; Sil.d_exp e; L.d_ln ()); + Some e + | Some Sil.Call (_, (Sil.Var id as e), _, loc, _) -> + if !verbose then (L.d_str "explain_dereference Sil.Call "; Sil.d_exp e; L.d_ln ()); + Some e + | _ -> None in + let node = State.get_node () in + match find_exp_dereferenced node with + | None -> + if !verbose then L.d_strln "_explain_access: find_exp_dereferenced returned None"; + Localise.no_desc + | Some e -> + L.d_strln "Finding deref'd exp"; + let de_opt = + if outermost_dereference then find_outermost_dereference node e + else exp_lv_dexp node e in + create_dereference_desc + ~use_buckets ~outermost_array ~is_nullable ~is_premature_nil + de_opt deref_str prop loc + +(** Produce a description of which expression is dereferenced in the current instruction, if any. +The subexpression to focus on is obtained by removing field and index accesses. *) +let explain_dereference + ?use_buckets:(use_buckets=false) + ?is_nullable:(is_nullable=false) + ?is_premature_nil:(is_premature_nil=false) + deref_str prop loc = + _explain_access + ~use_buckets ~outermost_array:false ~outermost_dereference:true ~is_nullable ~is_premature_nil + deref_str prop loc + +(** Produce a description of the array access performed in the current instruction, if any. +The subexpression to focus on is obtained by removing the outermost array access. *) +let explain_array_access deref_str prop loc = + _explain_access ~outermost_array:true deref_str prop loc + +(** Produce a description of the memory access performed in the current instruction, if any. *) +let explain_memory_access deref_str prop loc = + _explain_access deref_str prop loc + +(* offset of an expression found following a program variable *) +type pvar_off = + | Fpvar (* value of a pvar *) + | Fstruct of Ident.fieldname list (* value obtained by dereferencing the pvar and following a sequence of fields *) + +let dexp_apply_pvar_off dexp pvar_off = + let rec add_ddot de = function + | [] -> de + | f:: fl -> + add_ddot (Sil.Ddot (de, f)) fl in + match pvar_off with + | Fpvar -> dexp + | Fstruct (f:: fl) -> add_ddot (Sil.Darrow (dexp, f)) fl + | Fstruct [] -> dexp (* case should not happen *) + +(** Produce a description of the nth parameter of the function call, if the current instruction +is a function call with that parameter *) +let explain_nth_function_parameter use_buckets deref_str prop n pvar_off = + let node = State.get_node () in + let loc = State.get_loc () in + match State.get_instr () with + | Some Sil.Call (_, _, args, _, _) -> + (try + let arg = fst (list_nth args (n - 1)) in + let dexp_opt = exp_rv_dexp node arg in + let dexp_opt' = match dexp_opt with + | Some de -> Some (dexp_apply_pvar_off de pvar_off) + | None -> None in + create_dereference_desc ~use_buckets dexp_opt' deref_str prop loc + with exn when exn_not_timeout exn -> Localise.no_desc) + | _ -> Localise.no_desc + +(** Find a program variable whose value is [exp] or pointing to a struct containing [exp] *) +let find_pvar_with_exp prop exp = + let res = ref None in + let found_in_pvar pv = + res := Some (pv, Fpvar) in + let found_in_struct pv fld_lst = (* found_in_pvar has priority *) + if !res = None then res := Some (pv, Fstruct (list_rev fld_lst)) in + let rec search_struct pv fld_lst = function + | Sil.Eexp (e, _) -> + if Sil.exp_equal e exp then found_in_struct pv fld_lst + | Sil.Estruct (fsel, _) -> + list_iter (fun (f, se) -> search_struct pv (f:: fld_lst) se) fsel + | _ -> () in + let do_hpred_pointed_by_pvar pv e = function + | Sil.Hpointsto(e1, se, _) -> + if Sil.exp_equal e e1 then search_struct pv [] se + | _ -> () in + let do_hpred = function + | Sil.Hpointsto(Sil.Lvar pv, Sil.Eexp (e, _), _) -> + if Sil.exp_equal e exp then found_in_pvar pv + else list_iter (do_hpred_pointed_by_pvar pv e) (Prop.get_sigma prop) + | _ -> () in + list_iter do_hpred (Prop.get_sigma prop); + !res + +(** return a description explaining value [exp] in [prop] in terms of a source expression +using the formal parameters of the call *) +let explain_dereference_as_caller_expression + ?use_buckets:(use_buckets=false) + deref_str actual_pre spec_pre exp node loc formal_params = + let find_formal_param_number name = + let rec find n = function + | [] -> 0 + | v :: pars -> + if Mangled.equal (Sil.pvar_get_name v) name then n + else find (n + 1) pars in + find 1 formal_params in + match find_pvar_with_exp spec_pre exp with + | Some (pv, pvar_off) -> + if !verbose then L.d_strln ("pvar: " ^ (pvar_to_string pv)); + let pv_name = Sil.pvar_get_name pv in + if Sil.pvar_is_global pv + then + let dexp = exp_lv_dexp node (Sil.Lvar pv) in + create_dereference_desc ~use_buckets dexp deref_str actual_pre loc + else if Sil.pvar_is_callee pv then + let position = find_formal_param_number pv_name in + if !verbose then L.d_strln ("parameter number: " ^ string_of_int position); + explain_nth_function_parameter use_buckets deref_str actual_pre position pvar_off + else Localise.no_desc + | None -> + if !verbose then (L.d_str "explain_dereference_as_caller_expression "; Sil.d_exp exp; L.d_str ": cannot explain None "; L.d_ln ()); + Localise.no_desc + +(** explain a class cast exception *) +let explain_class_cast_exception pname_opt typ1 typ2 exp node loc = + let exp_str_opt = match exp_rv_dexp node exp with + | Some dexp -> Some (Sil.dexp_to_string dexp) + | None -> None in + match exp_rv_dexp node typ1, exp_rv_dexp node typ2 with + | Some de1, Some de2 -> + let typ_str1 = Sil.dexp_to_string de1 in + let typ_str2 = Sil.dexp_to_string de2 in + Localise.desc_class_cast_exception pname_opt typ_str1 typ_str2 exp_str_opt loc + | _ -> Localise.no_desc + +(** explain a division by zero *) +let explain_divide_by_zero exp node loc = + match exp_rv_dexp node exp with + | Some de -> + let exp_str = Sil.dexp_to_string de in + Localise.desc_divide_by_zero exp_str loc + | None -> Localise.no_desc + +(** explain a return expression required *) +let explain_return_expression_required loc typ = + let typ_str = + let pp fmt () = Sil.pp_typ_full pe_text fmt typ in + pp_to_string pp () in + Localise.desc_return_expression_required typ_str loc + +(** Explain a tainted value error *) +let explain_retain_cycle prop cycle loc = + Localise.desc_retain_cycle prop cycle loc + +(** Explain a tainted value error *) +let explain_tainted_value_reaching_sensitive_function e loc = + Localise.desc_tainted_value_reaching_sensitive_function (Sil.exp_to_string e) loc + +(** explain a return statement missing *) +let explain_return_statement_missing loc = + Localise.desc_return_statement_missing loc + +(** explain a comparing floats for equality *) +let explain_comparing_floats_for_equality loc = + Localise.desc_comparing_floats_for_equality loc + +(** explain a condition is an assignment *) +let explain_condition_is_assignment loc = + Localise.desc_condition_is_assignment loc + +(** explain a condition which is always true or false *) +let explain_condition_always_true_false i cond node loc = + let cond_str_opt = match exp_rv_dexp node cond with + | Some de -> + Some (Sil.dexp_to_string de) + | None -> None in + Localise.desc_condition_always_true_false i cond_str_opt loc + +(** explain the escape of a stack variable address from its scope *) +let explain_stack_variable_address_escape loc pvar addr_dexp_opt = + let addr_dexp_str = match addr_dexp_opt with + | Some (Sil.Dpvar pv) when Sil.pvar_is_local pv && Mangled.equal (Sil.pvar_get_name pv) Ident.name_return -> + Some "the caller via a return" + | Some dexp -> Some (Sil.dexp_to_string dexp) + | None -> None in + Localise.desc_stack_variable_address_escape (pvar_to_string pvar) addr_dexp_str loc + +(** explain unary minus applied to unsigned expression *) +let explain_unary_minus_applied_to_unsigned_expression exp typ node loc = + let exp_str_opt = match exp_rv_dexp node exp with + | Some de -> Some (Sil.dexp_to_string de) + | None -> None in + let typ_str = + let pp fmt () = Sil.pp_typ_full pe_text fmt typ in + pp_to_string pp () in + Localise.desc_unary_minus_applied_to_unsigned_expression exp_str_opt typ_str loc + +(** explain a test for NULL of a dereferenced pointer *) +let explain_null_test_after_dereference exp node line loc = + match exp_rv_dexp node exp with + | Some de -> + let expr_str = Sil.dexp_to_string de in + Localise.desc_null_test_after_dereference expr_str line loc + | None -> Localise.no_desc + +let _warning loc fmt fmt_string = + F.fprintf fmt "%s:%d: Warning: " (DB.source_file_to_string !DB.current_source) loc.Sil.line; + F.fprintf fmt fmt_string + +(** Print a warning to the out stream, at the given location *) +let warning_out loc fmt_string = + _warning loc (Logging.get_out_formatter ()) fmt_string + +(** Print a warning to the err stream, at the given location *) +let warning_err loc fmt_string = + _warning loc (Logging.get_err_formatter ()) fmt_string diff --git a/infer/src/backend/errdesc.mli b/infer/src/backend/errdesc.mli new file mode 100644 index 000000000..1fb14c6ba --- /dev/null +++ b/infer/src/backend/errdesc.mli @@ -0,0 +1,132 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Create descriptions of analysis errors *) + +open Utils + +(** find the dexp, if any, where the given value is stored +also return the type of the value if found *) +val vpath_find : 'a Prop.t -> Sil.exp -> Sil.vpath * Sil.typ option + +(** Return true if [id] is assigned to a program variable which is then nullified *) +val id_is_assigned_then_dead : Cfg.Node.t -> Ident.t -> bool + +(** Check whether the hpred is a |-> representing a resource in the Racquire state *) +val hpred_is_open_resource : 'a Prop.t -> Sil.hpred -> Sil.resource option + +(** Find the function call instruction used to initialize normal variable [id], +and return the function name and arguments *) +val find_normal_variable_funcall : +Cfg.Node.t -> Ident.t -> (Sil.exp * (Sil.exp list) * Sil.location * Sil.call_flags) option + +(** Find a program variable assignment in the current node or straightline predecessor. *) +val find_program_variable_assignment : Cfg.Node.t -> Sil.pvar -> (Cfg.Node.t * Ident.t) option + +(** Find a program variable assignment to id in the current node or predecessors. *) +val find_ident_assignment : Cfg.Node.t -> Ident.t -> (Cfg.Node.t * Sil.exp) option + +(** Find a boolean assignment to a temporary variable holding a boolean condition. +The boolean parameter indicates whether the true or false branch is required. *) +val find_boolean_assignment : Cfg.Node.t -> Sil.pvar -> bool -> Cfg.Node.t option + +(** describe rvalue [e] as a dexp *) +val exp_rv_dexp : Cfg.Node.t -> Sil.exp -> Sil.dexp option + +(** Produce a description of a mismatch between an allocation function and a deallocation function *) +val explain_allocation_mismatch : Sil.res_action -> Sil.res_action -> Localise.error_desc + +(** Produce a description of the array access performed in the current instruction, if any. *) +val explain_array_access : Localise.deref_str -> 'a Prop.t -> Sil.location -> Localise.error_desc + +(** explain a class cast exception *) +val explain_class_cast_exception : Procname.t option -> Sil.exp -> Sil.exp -> Sil.exp -> Cfg.Node.t -> Sil.location -> Localise.error_desc + +(** Explain a deallocate stack variable error *) +val explain_deallocate_stack_var : Sil.pvar -> Sil.res_action -> Localise.error_desc + +(** Explain a deallocate constant string error *) +val explain_deallocate_constant_string : string -> Sil.res_action -> Localise.error_desc + +(** Produce a description of which expression is dereferenced in the current instruction, if any. *) +val explain_dereference : +?use_buckets:bool -> ?is_nullable:bool -> ?is_premature_nil:bool -> +Localise.deref_str -> 'a Prop.t -> Sil.location -> Localise.error_desc + +(** return a description explaining value [exp] in [prop] in terms of a source expression +using the formal parameters of the call *) +val explain_dereference_as_caller_expression : +?use_buckets:bool -> +Localise.deref_str -> 'a Prop.t -> 'b Prop.t -> Sil.exp -> +Cfg.Node.t -> Sil.location -> Sil.pvar list -> Localise.error_desc + +(** explain a division by zero *) +val explain_divide_by_zero : Sil.exp -> Cfg.Node.t -> Sil.location -> Localise.error_desc + +(** explain a return expression required *) +val explain_return_expression_required : Sil.location -> Sil.typ -> Localise.error_desc + +(** explain a comparing floats for equality *) +val explain_comparing_floats_for_equality : Sil.location -> Localise.error_desc + +(** explain a condition is an assignment *) +val explain_condition_is_assignment : Sil.location -> Localise.error_desc + +(** explain a condition which is always true or false *) +val explain_condition_always_true_false : Sil.Int.t -> Sil.exp -> Cfg.Node.t -> Sil.location -> Localise.error_desc + +(** explain the escape of a stack variable address from its scope *) +val explain_stack_variable_address_escape : Sil.location -> Sil.pvar -> Sil.dexp option -> Localise.error_desc + +(** explain a return statement missing *) +val explain_return_statement_missing : Sil.location -> Localise.error_desc + +(** explain a retain cycle *) +val explain_retain_cycle : Prop.normal Prop.t -> ((Sil.strexp * Sil.typ) * Ident.fieldname * Sil.strexp) list -> Sil.location -> Localise.error_desc + +(** explain unary minus applied to unsigned expression *) +val explain_unary_minus_applied_to_unsigned_expression : Sil.exp -> Sil.typ -> Cfg.Node.t -> Sil.location -> Localise.error_desc + +(** Explain a tainted value error *) +val explain_tainted_value_reaching_sensitive_function : Sil.exp -> Sil.location -> Localise.error_desc + +(** Produce a description of a leak by looking at the current state. +If the current instruction is a variable nullify, blame the variable. +If it is an abstraction, blame any variable nullify at the current node. +If there is an alloc attribute, print the function call and line number. *) +val explain_leak : Sil.tenv -> Sil.hpred -> 'a Prop.t -> Sil.attribute option -> string option -> Exceptions.exception_visibility * Localise.error_desc + +(** Produce a description of the memory access performed in the current instruction, if any. *) +val explain_memory_access : Localise.deref_str -> 'a Prop.t -> Sil.location -> Localise.error_desc + +(** explain a test for NULL of a dereferenced pointer *) +val explain_null_test_after_dereference : Sil.exp -> Cfg.Node.t -> int -> Sil.location -> Localise.error_desc + +(** temporary variable name which is used to create edg native temp variables (see Trans_edg) *) +val edg_native_tmp_var_name_prefix : string + +(** Check whether the program variable is a temporary one generated by CIL *) +val pvar_is_cil_tmp : Sil.pvar -> bool + +(** Check whether the program variable is a temporary one generated by EDG *) +val pvar_is_edg_tmp : Sil.pvar -> bool + +(** Check whether the program variable is a temporary generated by the front-end *) +val pvar_is_frontend_tmp : Sil.pvar -> bool + +(** Print a warning to the out stream, at the given location *) +val warning_out : Sil.location -> ('a, Format.formatter, unit) format -> 'a + +(** Print a warning to the err stream, at the given location *) +val warning_err : Sil.location -> ('a, Format.formatter, unit) format -> 'a + +(* offset of an expression found following a program variable *) +type pvar_off = + | Fpvar (* value of a pvar *) + | Fstruct of Ident.fieldname list (* value obtained by dereferencing the pvar and following a sequence of fields *) + +(** Find a program variable whose value is [exp] or pointing to a struct containing [exp] *) +val find_pvar_with_exp : 'a Prop.t -> Sil.exp -> (Sil.pvar * pvar_off) option diff --git a/infer/src/backend/errlog.ml b/infer/src/backend/errlog.ml new file mode 100644 index 000000000..29b5c06cb --- /dev/null +++ b/infer/src/backend/errlog.ml @@ -0,0 +1,277 @@ +(* +* Copyright (c) 2015 - Facebook. +* All rights reserved. +*) + +open Utils +module L = Logging +module F = Format + +(** Element of a loc trace *) +type loc_trace_elem = { + lt_level : int; (** nesting level of procedure calls *) + lt_loc : Sil.location; (** source location at the current step in the trace *) + lt_description : string; (** description of the current step in the trace *) + lt_node_tags : (string * string) list (** tags describing the node at the current location *) +} + +(** Trace of locations *) +type loc_trace = loc_trace_elem list + +(** Data associated to a specific error *) +type err_data = + (int * int) * int * Sil.location * ml_location option * loc_trace * + Prop.normal Prop.t option * Exceptions.err_class + +let err_data_compare + ((nodeid1, key1), session1, loc1, mloco1, ltr1, po1, ec1) + ((nodeid2, key2), session2, loc2, mloco2, ltr2, po2, ec2) = + Sil.loc_compare loc1 loc2 + +module ErrDataSet = (* set err_data with no repeated loc *) + Set.Make(struct + type t = err_data + let compare = err_data_compare + end) + +(** Hash table to implement error logs *) +module ErrLogHash = Hashtbl.Make (struct + + type t = Exceptions.err_kind * bool * Localise.t * Localise.error_desc * string + + let hash (ekind, in_footprint, err_name, desc, severity) = + Hashtbl.hash (ekind, in_footprint, err_name, Localise.error_desc_hash desc) + + let equal + (ekind1, in_footprint1, err_name1, desc1, severity1) + (ekind2, in_footprint2, err_name2, desc2, severity2) = + (ekind1, in_footprint1, err_name1) = (ekind2, in_footprint2, err_name2) && + Localise.error_desc_equal desc1 desc2 + + end) + +(** Type of the error log, to be reset once per function. +Map err_kind, fotprint / re - execution flag, error name, +error description, severity, to set of err_data. *) +type t = ErrDataSet.t ErrLogHash.t + +(** Empty error log *) +let empty () = ErrLogHash.create 13 + +(** type of the function to be passed to iter *) +type iter_fun = + (int * int) -> Sil.location -> Exceptions.err_kind -> bool -> Localise.t -> Localise.error_desc -> + string -> loc_trace -> Prop.normal Prop.t option -> Exceptions.err_class -> unit + +(** Apply f to nodes and error names *) +let iter (f: iter_fun) (err_log: t) = + ErrLogHash.iter (fun (ekind, in_footprint, err_name, desc, severity) set -> + ErrDataSet.iter + (fun (node_id_key, section, loc, mloco, ltr, pre_opt, eclass) -> + f + node_id_key loc ekind in_footprint err_name + desc severity ltr pre_opt eclass) + set) + err_log + +(** Return the number of elements in the error log which satisfy [filter] *) +let size filter (err_log: t) = + let count = ref 0 in + ErrLogHash.iter (fun (ekind, in_footprint, _, _, _) eds -> + if filter ekind in_footprint then count := !count + (ErrDataSet.cardinal eds)) err_log; + !count + +(** Print an error log *) +let pp fmt (errlog : t) = + let f (ekind, infp, ename, desc, severity) locs = + if ekind == Exceptions.Kerror then F.fprintf fmt "%a@ " Localise.pp ename in + ErrLogHash.iter f errlog + +(** Print an error log in html format *) +let pp_html path_to_root fmt (errlog: t) = + let pp_eds fmt eds = + let pp_nodeid_session_loc fmt ((nodeid, nodekey), session, loc, mloco, ltr, pre_opt, eclass) = + Io_infer.Html.pp_session_link path_to_root fmt (nodeid, session, loc.Sil.line) in + ErrDataSet.iter (pp_nodeid_session_loc fmt) eds in + let f do_fp ek (ekind, infp, err_name, desc, severity) eds = + if ekind == ek && do_fp == infp + then + F.fprintf fmt "
%a %a %a" + Localise.pp err_name + Localise.pp_error_desc desc + pp_eds eds in + F.fprintf fmt "%aERRORS DURING FOOTPRINT@\n" Io_infer.Html.pp_hline (); + ErrLogHash.iter (f true Exceptions.Kerror) errlog; + F.fprintf fmt "%aERRORS DURING RE-EXECUTION@\n" Io_infer.Html.pp_hline (); + ErrLogHash.iter (f false Exceptions.Kerror) errlog; + F.fprintf fmt "%aWARNINGS DURING FOOTPRINT@\n" Io_infer.Html.pp_hline (); + ErrLogHash.iter (f true Exceptions.Kwarning) errlog; + F.fprintf fmt "%aWARNINGS DURING RE-EXECUTION@\n" Io_infer.Html.pp_hline (); + ErrLogHash.iter (f false Exceptions.Kwarning) errlog; + F.fprintf fmt "%aINFOS DURING FOOTPRINT@\n" Io_infer.Html.pp_hline (); + ErrLogHash.iter (f true Exceptions.Kinfo) errlog; + F.fprintf fmt "%aINFOS DURING RE-EXECUTION@\n" Io_infer.Html.pp_hline (); + ErrLogHash.iter (f false Exceptions.Kinfo) errlog + + +(* I use string in case we want to display a different name to the user*) +let severity_to_str severity = match severity with + | Exceptions.High -> "HIGH" + | Exceptions.Medium -> "MEDIUM" + | Exceptions.Low -> "LOW" + +(** Add an error description to the error log unless there is +one already at the same node + session; return true if added *) +let add_issue tbl (ekind, in_footprint, err_name, desc, severity) (eds: ErrDataSet.t) : bool = + try + let current_eds = ErrLogHash.find tbl (ekind, in_footprint, err_name, desc, severity) in + if ErrDataSet.subset eds current_eds then false + else + begin + ErrLogHash.replace tbl + (ekind, in_footprint, err_name, desc, severity) + (ErrDataSet.union eds current_eds); + true + end + with Not_found -> + begin + ErrLogHash.add tbl (ekind, in_footprint, err_name, desc, severity) eds; + true + end + +(** Update an old error log with a new one *) +let update errlog_old errlog_new = + ErrLogHash.iter + (fun (ekind, infp, s, desc, severity) l -> + ignore (add_issue errlog_old (ekind, infp, s, desc, severity) l)) errlog_new + + +let log_issue _ekind err_log loc node_id_key session ltr pre_opt exn = + let err_name, desc, mloco, visibility, severity, force_kind, eclass = + Exceptions.recognize_exception exn in + let ekind = match force_kind with + | Some ekind -> ekind + | _ -> _ekind in + let hide_java_loc_zero = (* hide java errors at location zero unless in -developer_mode *) + !Config.developer_mode = false && !Sil.curr_language = Sil.Java && loc.Sil.line = 0 in + let log_it = + visibility == Exceptions.Exn_user || + (!Config.developer_mode && visibility == Exceptions.Exn_developer) in + if log_it && not hide_java_loc_zero then begin + let added = + add_issue err_log + (ekind, !Config.footprint, err_name, desc, severity_to_str severity) + (ErrDataSet.singleton (node_id_key, session, loc, mloco, ltr, pre_opt, eclass)) in + let should_print_now = + match exn with + | Exceptions.Internal_error _ -> true + | _ -> added in + let print_now () = + let ex_name, desc, mloco, _, _, _, _ = Exceptions.recognize_exception exn in + L.err "%a@?" (Exceptions.pp_err node_id_key loc ekind ex_name desc mloco) (); + if _ekind <> Exceptions.Kerror then begin + let warn_str = + let pp fmt () = + Format.fprintf fmt "%s %a" + (Localise.to_string err_name) + Localise.pp_error_desc desc in + pp_to_string pp () in + let d = match ekind with + | Exceptions.Kerror -> L.d_error + | Exceptions.Kwarning -> L.d_warning + | Exceptions.Kinfo -> L.d_info in + d warn_str; L.d_ln(); + end in + if should_print_now then print_now () + end + +type err_log = t + +(** Global per-file error table *) +module Err_table = struct + type t = err_log + let create = empty + + let count_err err_table err_name locs = + ignore (add_issue err_table err_name locs) + + let table_size filter (err_table: t) = + size filter err_table + + let pp_stats_footprint ekind fmt (err_table: err_log) = + let err_name_map = ref StringMap.empty in (* map error name to count *) + let count_err (err_name: Localise.t) n = + let err_string = Localise.to_string err_name in + let count = try StringMap.find err_string !err_name_map with Not_found -> 0 in + err_name_map := StringMap.add err_string (count + n) !err_name_map in + let count (ekind', in_footprint, err_name, desc, severity) eds = + if ekind = ekind' && in_footprint then count_err err_name (ErrDataSet.cardinal eds) in + ErrLogHash.iter count err_table; + let pp err_string count = F.fprintf fmt " %s:%d" err_string count in + StringMap.iter pp !err_name_map + + module LocMap = + Map.Make(struct + type t = ErrDataSet.elt + let compare = err_data_compare + end) + + let print_err_table_details fmt err_table = + let map_err_fp = ref LocMap.empty in + let map_err_re = ref LocMap.empty in + let map_warn_fp = ref LocMap.empty in + let map_warn_re = ref LocMap.empty in + let map_info = ref LocMap.empty in + let add_err nslm (ekind , in_fp, err_name, desc, severity) = + let map = match in_fp, ekind with + | true, Exceptions.Kerror -> map_err_fp + | false, Exceptions.Kerror -> map_err_re + | true, Exceptions.Kwarning -> map_warn_fp + | false, Exceptions.Kwarning -> map_warn_re + | _, Exceptions.Kinfo -> map_info in + try + let err_list = LocMap.find nslm !map in + map := LocMap.add nslm ((err_name, desc) :: err_list) !map + with Not_found -> + map := LocMap.add nslm [(err_name, desc)] !map in + let f err_name eds = + ErrDataSet.iter (fun loc -> add_err loc err_name) eds in + ErrLogHash.iter f err_table; + + let pp ekind (nodeidkey, session, loc, mloco, ltr, pre_opt, eclass) fmt err_names = + list_iter (fun (err_name, desc) -> + Exceptions.pp_err nodeidkey loc ekind err_name desc mloco fmt ()) err_names in + F.fprintf fmt "@.Detailed errors during footprint phase:@."; + LocMap.iter (fun nslm err_names -> + F.fprintf fmt "%a" (pp Exceptions.Kerror nslm) err_names) !map_err_fp; + F.fprintf fmt "@.Detailed errors during re-execution phase:@."; + LocMap.iter (fun nslm err_names -> + F.fprintf fmt "%a" (pp Exceptions.Kerror nslm) err_names) !map_err_re; + F.fprintf fmt "@.Detailed warnings during footprint phase:@."; + LocMap.iter (fun nslm err_names -> + F.fprintf fmt "%a" (pp Exceptions.Kwarning nslm) err_names) !map_warn_fp; + F.fprintf fmt "@.Detailed warnings during re-execution phase:@."; + LocMap.iter (fun nslm err_names -> + F.fprintf fmt "%a" (pp Exceptions.Kwarning nslm) err_names) !map_warn_re +end + +type err_table = Err_table.t + +(** Create an error table *) +let create_err_table = Err_table.create + +(** Print an error log and add it to the global per-file table *) +let extend_table err_table err_log = + ErrLogHash.iter (Err_table.count_err err_table) err_log + +(** Size of the global per-file error table for the footprint phase *) +let err_table_size_footprint ekind = + let filter ekind' in_footprint = ekind = ekind' && in_footprint in + Err_table.table_size filter + +(** Print stats for the global per-file error table *) +let pp_err_table_stats ekind = Err_table.pp_stats_footprint ekind + +(** Print details of the global per-file error table *) +let print_err_table_details = + Err_table.print_err_table_details diff --git a/infer/src/backend/errlog.mli b/infer/src/backend/errlog.mli new file mode 100644 index 000000000..aa0fca24d --- /dev/null +++ b/infer/src/backend/errlog.mli @@ -0,0 +1,68 @@ +(* +* Copyright (c) 2015 - Facebook. +* All rights reserved. +*) + +(** Module for error logs. *) + +(** Element of a loc trace *) +type loc_trace_elem = { + lt_level : int; (** nesting level of procedure calls *) + lt_loc : Sil.location; (** source location at the current step in the trace *) + lt_description : string; (** description of the current step in the trace *) + lt_node_tags : (string * string) list (** tags describing the node at the current location *) +} + +(** Trace of locations *) +type loc_trace = loc_trace_elem list + +(** Type of the error log *) +type t + +(** Empty error log *) +val empty : unit -> t + +(** type of the function to be passed to iter *) +type iter_fun = + (int * int) -> Sil.location -> Exceptions.err_kind -> bool -> Localise.t -> Localise.error_desc -> + string -> loc_trace -> Prop.normal Prop.t option -> Exceptions.err_class -> unit + +(** Apply f to nodes and error names *) +val iter : iter_fun -> t -> unit + +(** Print an error log *) +val pp : Format.formatter -> t -> unit + +(** Print an error log in html format *) +val pp_html : DB.Results_dir.path -> Format.formatter -> t -> unit + +(** Return the number of elements in the error log which satisfy the filter. *) +val size : (Exceptions.err_kind -> bool -> bool) -> t -> int + +(** Update an old error log with a new one *) +val update : t -> t -> unit + +val log_issue : +Exceptions.err_kind -> +t -> Sil.location -> (int * int) -> int -> loc_trace -> +(Prop.normal Prop.t) option -> exn -> unit + +(** {2 Functions for manipulating per-file error tables} *) + +(** Type for per-file error tables *) +type err_table + +(** Create an error table *) +val create_err_table : unit -> err_table + +(** Add an error log to the global per-file table *) +val extend_table : err_table -> t -> unit + +(** Size of the global per-file error table for the footprint phase *) +val err_table_size_footprint : Exceptions.err_kind -> err_table -> int + +(** Print stats for the global per-file error table *) +val pp_err_table_stats : Exceptions.err_kind -> Format.formatter -> err_table -> unit + +(** Print details of the global per-file error table *) +val print_err_table_details : Format.formatter -> err_table -> unit diff --git a/infer/src/backend/exceptions.ml b/infer/src/backend/exceptions.ml new file mode 100644 index 000000000..04fd931cf --- /dev/null +++ b/infer/src/backend/exceptions.ml @@ -0,0 +1,262 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +module L = Logging +module F = Format +open Utils + +type exception_visibility = (** visibility of the exception *) + | Exn_user (** always add to error log *) + | Exn_developer (** only add to error log in developer mode *) + | Exn_system (** never add to error log *) + +type exception_severity = (** severity of bugs *) + | High (* high severity bug *) + | Medium (* medium severity bug *) + | Low (* low severity bug *) + +(** class of error *) +type err_class = Checker | Prover | Nocat + +(** kind of error/warning *) +type err_kind = + Kwarning | Kerror | Kinfo + +exception Abduction_case_not_implemented of ml_location +exception Analysis_stops of Localise.error_desc * ml_location option +exception Array_out_of_bounds_l1 of Localise.error_desc * ml_location +exception Array_out_of_bounds_l2 of Localise.error_desc * ml_location +exception Array_out_of_bounds_l3 of Localise.error_desc * ml_location +exception Array_of_pointsto of ml_location +exception Assertion_failure of string * Localise.error_desc +exception Bad_footprint of ml_location +exception Class_cast_exception of Localise.error_desc * ml_location +exception Codequery of Localise.error_desc +exception Comparing_floats_for_equality of Localise.error_desc * ml_location +exception Condition_is_assignment of Localise.error_desc * ml_location +exception Condition_always_true_false of Localise.error_desc * bool * ml_location +exception Dangling_pointer_dereference of Sil.dangling_kind option * Localise.error_desc * ml_location +exception Deallocate_stack_variable of Localise.error_desc +exception Deallocate_static_memory of Localise.error_desc +exception Deallocation_mismatch of Localise.error_desc * ml_location +exception Divide_by_zero of Localise.error_desc * ml_location +exception Eradicate of string * Localise.error_desc +exception Field_not_null_checked of Localise.error_desc * ml_location +exception Checkers of string * Localise.error_desc +exception Inherently_dangerous_function of Localise.error_desc +exception Internal_error of Localise.error_desc +exception Java_runtime_exception of Mangled.t * string * Localise.error_desc +exception Leak of bool * Prop.normal Prop.t * Sil.hpred * (exception_visibility * Localise.error_desc) * bool * Sil.resource * ml_location +exception Missing_fld of Ident.fieldname * ml_location +exception Premature_nil_termination of Localise.error_desc * ml_location +exception Null_dereference of Localise.error_desc * ml_location +exception Null_test_after_dereference of Localise.error_desc * ml_location +exception Parameter_not_null_checked of Localise.error_desc * ml_location +exception Pointer_size_mismatch of Localise.error_desc * ml_location +exception Precondition_not_found of Localise.error_desc * ml_location +exception Precondition_not_met of Localise.error_desc * ml_location +exception Retain_cycle of Prop.normal Prop.t * Sil.hpred *Localise.error_desc * ml_location +exception Return_expression_required of Localise.error_desc * ml_location +exception Return_statement_missing of Localise.error_desc * ml_location +exception Return_value_ignored of Localise.error_desc * ml_location +exception Skip_function of Localise.error_desc +exception Skip_pointer_dereference of Localise.error_desc * ml_location +exception Stack_variable_address_escape of Localise.error_desc * ml_location +exception Symexec_memory_error of ml_location +exception Tainted_value_reaching_sensitive_function of Localise.error_desc * ml_location +exception Unary_minus_applied_to_unsigned_expression of Localise.error_desc * ml_location +exception Uninitialized_value of Localise.error_desc * ml_location +exception Unknown_proc +exception Use_after_free of Localise.error_desc * ml_location +exception Wrong_argument_number of ml_location + +(** Turn an exception into a descriptive string, error description, location in ml source, and category *) +let recognize_exception exn = + let filter_out_bucket desc = + !Config.filter_buckets && + match Localise.error_desc_get_bucket desc with + | None -> false + | Some bucket -> bucket <> Localise.BucketLevel.b1 in + let err_name, desc, mloco, visibility, severity, force_kind, eclass = match exn with (* all the names of Exn_user errors must be defined in Localise *) + | Abduction_case_not_implemented mloc -> + (Localise.from_string "Abduction_case_not_implemented", Localise.no_desc, Some mloc, Exn_developer, Low, None, Nocat) + | Analysis_stops (desc, mloco) -> + let visibility = if !Config.analysis_stops then Exn_user else Exn_developer in + (Localise.analysis_stops, desc, mloco, visibility, Medium, None, Nocat) + | Array_of_pointsto mloc -> + (Localise.from_string "Array_of_pointsto", Localise.no_desc, Some mloc, Exn_developer, Low, None, Nocat) + | Array_out_of_bounds_l1 (desc, mloc) -> + (Localise.array_out_of_bounds_l1, desc, Some mloc, Exn_user, High, Some Kerror, Checker) + | Array_out_of_bounds_l2 (desc, mloc) -> + (Localise.array_out_of_bounds_l2, desc, Some mloc, Exn_user, Medium, None, Nocat) + | Array_out_of_bounds_l3 (desc, mloc) -> + (Localise.array_out_of_bounds_l3, desc, Some mloc, Exn_developer, Medium, None, Nocat) + | Assert_failure mloc -> + (Localise.from_string "Assert_failure", Localise.no_desc, Some mloc, Exn_developer, High, None, Nocat) + | Assertion_failure (error_msg, desc) -> + (Localise.from_string error_msg, desc, None, Exn_user, High, None, Checker) + | Bad_footprint mloc -> + (Localise.from_string "Bad_footprint", Localise.no_desc, Some mloc, Exn_developer, Low, None, Nocat) + | Prop.Cannot_star mloc -> + (Localise.from_string "Cannot_star", Localise.no_desc, Some mloc, Exn_developer, Low, None, Nocat) + | Class_cast_exception (desc, mloc) -> + (Localise.class_cast_exception, desc, Some mloc, Exn_user, High, None, Prover) + | Codequery desc -> + (Localise.from_string "Codequery", desc, None, Exn_user, High, None, Prover) + | Comparing_floats_for_equality(desc, mloc) -> + (Localise.comparing_floats_for_equality, desc, Some mloc, Exn_user, Medium, None, Nocat) + | Condition_always_true_false (desc, b, mloc) -> + let name = if b then Localise.condition_always_true else Localise.condition_always_false in + (name, desc, Some mloc, Exn_user, Medium, None, Nocat) + | Condition_is_assignment(desc, mloc) -> + (Localise.condition_is_assignment, desc, Some mloc, Exn_user, Medium, None, Nocat) + | Dangling_pointer_dereference (dko, desc, mloc) -> + let visibility = match dko with + | Some dk -> Exn_user (* only show to the user if the category was identified *) + | None -> Exn_developer in + (Localise.dangling_pointer_dereference, desc, Some mloc, visibility, High, None, Prover) + | Deallocate_stack_variable desc -> + (Localise.deallocate_stack_variable, desc, None, Exn_user, High, None, Prover) + | Deallocate_static_memory desc -> + (Localise.deallocate_static_memory, desc, None, Exn_user, High, None, Prover) + | Deallocation_mismatch (desc, mloc) -> + (Localise.deallocation_mismatch, desc, Some mloc, Exn_user, High, None, Prover) + | Divide_by_zero (desc, mloc) -> + (Localise.divide_by_zero, desc, Some mloc, Exn_user, High, Some Kerror, Checker) + | Eradicate (kind_s, desc) -> + (Localise.from_string kind_s, desc, None, Exn_user, High, None, Prover) + | Field_not_null_checked (desc, mloc) -> + (Localise.field_not_null_checked, desc, Some mloc, Exn_user, Medium, Some Kwarning, Nocat) + | Checkers (kind_s, desc) -> + (Localise.from_string kind_s, desc, None, Exn_user, High, None, Prover) + | Null_dereference (desc, mloc) -> + (Localise.null_dereference, desc, Some mloc, Exn_user, High, None, Prover) + | Null_test_after_dereference (desc, mloc) -> + (Localise.null_test_after_dereference, desc, Some mloc, Exn_user, High, None, Nocat) + | Pointer_size_mismatch (desc, mloc) -> + (Localise.pointer_size_mismatch, desc, Some mloc, Exn_user, High, Some Kerror, Checker) + | Inherently_dangerous_function desc -> + (Localise.inherently_dangerous_function, desc, None, Exn_developer, Medium, None, Nocat) + | Internal_error desc -> + (Localise.from_string "Internal_error", desc, None, Exn_developer, High, None, Nocat) + | Invalid_argument s -> + let desc = Localise.verbatim_desc s in + (Localise.from_string "Invalid_argument", desc, None, Exn_system, Low, None, Nocat) + | Java_runtime_exception (exn_name, pre_str, desc) -> + let exn_str = Mangled.to_string exn_name in + (Localise.from_string exn_str, desc, None, Exn_user, High, None, Prover) + | Leak (fp_part, _, _, (exn_vis, error_desc), done_array_abstraction, resource, mloc) -> + if done_array_abstraction + then (Localise.from_string "Leak_after_array_abstraction", error_desc, Some mloc, Exn_developer, High, None, Prover) + else if fp_part + then (Localise.from_string "Leak_in_footprint", error_desc, Some mloc, Exn_developer, High, None, Prover) + else + let loc_str = match resource with + | Sil.Rmemory _ -> Localise.memory_leak + | Sil.Rfile -> Localise.resource_leak + | Sil.Rlock -> Localise.resource_leak + | Sil.Rignore -> Localise.memory_leak in + (loc_str, error_desc, Some mloc, exn_vis, High, None, Prover) + | Match_failure mloc -> + (Localise.from_string "Match failure", Localise.no_desc, Some mloc, Exn_developer, High, None, Nocat) + | Missing_fld (fld, mloc) -> + let desc = Localise.verbatim_desc (Ident.fieldname_to_string fld) in + (Localise.from_string "Missing_fld", desc, Some mloc, Exn_developer, Medium, None, Nocat) + | Premature_nil_termination (desc, mloc) -> + (Localise.premature_nil_termination, desc, Some mloc, Exn_user, High, None, Prover) + | Not_found -> + (Localise.from_string "Not_found", Localise.no_desc, None, Exn_system, Low, None, Nocat) + | Parameter_not_null_checked (desc, mloc) -> + (Localise.parameter_not_null_checked, desc, Some mloc, Exn_user, Medium, Some Kwarning, Nocat) + | Precondition_not_found (desc, mloc) -> + (Localise.precondition_not_found, desc, Some mloc, Exn_developer, Low, None, Nocat) + | Precondition_not_met (desc, mloc) -> + (Localise.precondition_not_met, desc, Some mloc, Exn_user, Medium, Some Kwarning, Nocat) (** always a warning *) + | Retain_cycle (prop, hpred, desc, mloc) -> + (Localise.retain_cycle, desc, Some mloc, Exn_user, High, None, Prover) + | Return_expression_required (desc, mloc) -> + (Localise.return_expression_required, desc, Some mloc, Exn_user, Medium, None, Nocat) + | Stack_variable_address_escape (desc, mloc) -> + (Localise.stack_variable_address_escape, desc, Some mloc, Exn_user, High, Some Kerror, Nocat) + | Return_statement_missing (desc, mloc) -> + (Localise.return_statement_missing, desc, Some mloc, Exn_user, Medium, None, Nocat) + | Return_value_ignored (desc, mloc) -> + (Localise.return_value_ignored, desc, Some mloc, Exn_user, Medium, None, Nocat) + | Timeout_exe _ -> + (Localise.from_string "Timeout_exe", Localise.no_desc, None, Exn_system, Low, None, Nocat) + | Skip_function desc -> + (Localise.skip_function, desc, None, Exn_user, Low, None, Nocat) + | Skip_pointer_dereference (desc, mloc) -> + (Localise.skip_pointer_dereference, desc, Some mloc, Exn_user, Medium, Some Kinfo, Nocat) (** always an info *) + | Symexec_memory_error mloc -> + (Localise.from_string "Symexec_memory_error", Localise.no_desc, Some mloc, Exn_developer, Low, None, Nocat) + | Sys_error s -> + let desc = Localise.verbatim_desc s in + (Localise.from_string "Sys_error", desc, None, Exn_system, Low, None, Nocat) + | Tainted_value_reaching_sensitive_function (desc, mloc) -> + (Localise.tainted_value_reaching_sensitive_function, desc, Some mloc, Exn_developer, Medium, None, Nocat) + | Unix.Unix_error (_, s1, s2) -> + let desc = Localise.verbatim_desc (s1 ^ s2) in + (Localise.from_string "Unix_error", desc, None, Exn_system, Low, None, Nocat) + | Uninitialized_value (desc, mloc) -> + (Localise.uninitialized_value, desc, Some mloc, Exn_user, Medium, None, Nocat) + | Unary_minus_applied_to_unsigned_expression(desc, mloc) -> + (Localise.unary_minus_applied_to_unsigned_expression, desc, Some mloc, Exn_user, Medium, None, Nocat) + | Unknown_proc -> + (Localise.from_string "Unknown_proc", Localise.no_desc, None, Exn_developer, Low, None, Nocat) + | Use_after_free (desc, mloc) -> + (Localise.use_after_free, desc, Some mloc, Exn_user, High, None, Prover) + | Wrong_argument_number mloc -> + (Localise.from_string "Wrong_argument_number", Localise.no_desc, Some mloc, Exn_developer, Low, None, Nocat) + | exn -> + let exn_name = Printexc.to_string exn in + (Localise.from_string exn_name, Localise.no_desc, None, Exn_developer, Low, None, Nocat) in + let visibility' = + if visibility = Exn_user && filter_out_bucket desc then Exn_developer else visibility in + (err_name, desc, mloco, visibility', severity, force_kind, eclass) + +(** print a description of the exception to the html output *) +let print_exception_html s exn = + let err_name, desc, mloco, _, _, _, _ = recognize_exception exn in + let mloc_string = match mloco with + | None -> "" + | Some mloc -> " " ^ ml_location_string mloc in + let desc_str = pp_to_string Localise.pp_error_desc desc in + (L.d_strln_color Red) (s ^ (Localise.to_string err_name) ^ " " ^ desc_str ^ mloc_string) + +(** string describing an error kind *) +let err_kind_string = function + | Kwarning -> "WARNING" + | Kerror -> "ERROR" + | Kinfo -> "INFO" + +(** string describing an error class *) +let err_class_string = function + | Checker -> "CHECKER" + | Prover -> "PROVER" + | Nocat -> "" + +(** wether to print the bug key together with the error message *) +let print_key = false + +(** pretty print an error given its (id,key), location, kind, name, description, and optional ml location *) +let pp_err (node_id, node_key) loc ekind ex_name desc mloco fmt () = + let kind = err_kind_string (if ekind = Kinfo then Kwarning else ekind) (* eclipse does not know about infos: treat as warning *) in + let pp_key fmt k = if print_key then F.fprintf fmt " key: %d " k else () in + F.fprintf fmt "%s:%d: %s: %a %a%a%a@\n" + (DB.source_file_to_string loc.Sil.file) + loc.Sil.line + kind + Localise.pp ex_name + Localise.pp_error_desc desc + pp_key node_key + pp_ml_location_opt mloco + +(** Return true if the exception is not serious and should be handled in timeout mode *) +let handle_exception exn = + let _, _, _, visibility, _, _, _ = recognize_exception exn in + visibility == Exn_user || visibility == Exn_developer + diff --git a/infer/src/backend/exceptions.mli b/infer/src/backend/exceptions.mli new file mode 100644 index 000000000..6a901de07 --- /dev/null +++ b/infer/src/backend/exceptions.mli @@ -0,0 +1,96 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +open Utils + +(** Functions for logging and printing exceptions *) + +type exception_visibility = (** visibility of the exception *) + | Exn_user (** always add to error log *) + | Exn_developer (** only add to error log in developer mode *) + | Exn_system (** never add to error log *) + +type exception_severity = (** severity of bugs *) + | High (* high severity bug *) + | Medium (* medium severity bug *) + | Low (* low severity bug *) + +(** kind of error/warning *) +type err_kind = + Kwarning | Kerror | Kinfo + +(** class of error *) +type err_class = Checker | Prover | Nocat + +exception Abduction_case_not_implemented of ml_location +exception Analysis_stops of Localise.error_desc * ml_location option +exception Array_of_pointsto of ml_location +exception Array_out_of_bounds_l1 of Localise.error_desc * ml_location +exception Array_out_of_bounds_l2 of Localise.error_desc * ml_location +exception Array_out_of_bounds_l3 of Localise.error_desc * ml_location +exception Assertion_failure of string * Localise.error_desc +exception Bad_footprint of ml_location +exception Class_cast_exception of Localise.error_desc * ml_location +exception Codequery of Localise.error_desc +exception Comparing_floats_for_equality of Localise.error_desc * ml_location +exception Condition_always_true_false of Localise.error_desc * bool * ml_location +exception Condition_is_assignment of Localise.error_desc * ml_location +exception Dangling_pointer_dereference of Sil.dangling_kind option * Localise.error_desc * ml_location +exception Deallocate_stack_variable of Localise.error_desc +exception Deallocate_static_memory of Localise.error_desc +exception Deallocation_mismatch of Localise.error_desc * ml_location +exception Divide_by_zero of Localise.error_desc * ml_location +exception Field_not_null_checked of Localise.error_desc * ml_location +exception Eradicate of string * Localise.error_desc +exception Checkers of string * Localise.error_desc +exception Inherently_dangerous_function of Localise.error_desc +exception Internal_error of Localise.error_desc +exception Java_runtime_exception of Mangled.t * string * Localise.error_desc +exception Leak of bool * Prop.normal Prop.t * Sil.hpred * (exception_visibility * Localise.error_desc) * bool * Sil.resource * ml_location +exception Missing_fld of Ident.fieldname * ml_location +exception Premature_nil_termination of Localise.error_desc * ml_location +exception Null_dereference of Localise.error_desc * ml_location +exception Null_test_after_dereference of Localise.error_desc * ml_location +exception Parameter_not_null_checked of Localise.error_desc * ml_location +exception Pointer_size_mismatch of Localise.error_desc * ml_location +exception Precondition_not_found of Localise.error_desc * ml_location +exception Precondition_not_met of Localise.error_desc * ml_location +exception Retain_cycle of Prop.normal Prop.t * Sil.hpred * Localise.error_desc * ml_location +exception Return_expression_required of Localise.error_desc * ml_location +exception Return_statement_missing of Localise.error_desc * ml_location +exception Return_value_ignored of Localise.error_desc * ml_location +exception Skip_function of Localise.error_desc +exception Skip_pointer_dereference of Localise.error_desc * ml_location +exception Stack_variable_address_escape of Localise.error_desc * ml_location +exception Symexec_memory_error of ml_location +exception Tainted_value_reaching_sensitive_function of Localise.error_desc * ml_location +exception Unary_minus_applied_to_unsigned_expression of Localise.error_desc * ml_location +exception Uninitialized_value of Localise.error_desc * ml_location +exception Unknown_proc +exception Use_after_free of Localise.error_desc * ml_location +exception Wrong_argument_number of ml_location + +(** string describing an error class *) +val err_class_string : err_class -> string + +(** string describing an error kind *) +val err_kind_string : err_kind -> string + +(** Return true if the exception is not serious and should be handled in timeout mode *) +val handle_exception : exn -> bool + +(** print a description of the exception to the html output *) +val print_exception_html : string -> exn -> unit + +(** pretty print an error given its (id,key), location, kind, name, description, and optional ml location *) +val pp_err : int * int -> Sil.location -> err_kind -> Localise.t -> Localise.error_desc -> +Utils.ml_location option -> Format.formatter -> unit -> unit + +(** Turn an exception into an error name, error description, +location in ml source, and category *) +val recognize_exception : exn -> +(Localise.t * Localise.error_desc * (ml_location option) * exception_visibility * +exception_severity * err_kind option * err_class) diff --git a/infer/src/backend/exe_env.ml b/infer/src/backend/exe_env.ml new file mode 100644 index 000000000..9c0d51e1a --- /dev/null +++ b/infer/src/backend/exe_env.ml @@ -0,0 +1,227 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Support for Execution environments *) + +open Utils +module L = Logging + +(** per-file data: type environment and cfg *) +type file_data = + { source: DB.source_file; + nLOC : int; + tenv_file: DB.filename; + mutable tenv: Sil.tenv option; + cfg_file: DB.filename; + mutable cfg: Cfg.cfg option; + } + + +(** get the path to the tenv file, which either one tenv file per source file or a global tenv file *) +let tenv_filename file_base = + let per_source_tenv_filename = DB.filename_add_suffix file_base ".tenv" in + if Sys.file_exists (DB.filename_to_string per_source_tenv_filename) then + per_source_tenv_filename + else + DB.global_tenv_fname () + +(** create a new file_data *) +let new_file_data source nLOC cg_fname = + let file_base = DB.chop_extension cg_fname in + let tenv_file = tenv_filename file_base in + let cfg_file = DB.filename_add_suffix file_base ".cfg" in + { source = source; + nLOC = nLOC; + tenv_file = tenv_file; + tenv = None; (* Sil.load_tenv_from_file tenv_file *) + cfg_file = cfg_file; + cfg = None; (* Cfg.load_cfg_from_file cfg_file *) + } + + +(** execution environment *) +type t = + { cg: Cg.t; (** global call graph *) + proc_map: file_data Procname.Hash.t; (** map from procedure name to file data *) + file_map: (DB.source_file, file_data) Hashtbl.t; (** map from filaname to file data *) + mutable active_opt : Procname.Set.t option; (** if not None, restrict the active procedures to the given set *) + mutable callees : Procname.Set.t; (** callees of active procedures *) + mutable procs_defined_in_several_files : Procname.Set.t; (** Procedures defined in more than one file *) + } + +(** initial state, used to add cg's *) +type initial = t + +(** freeze the execution environment, so it can be queried *) +let freeze exe_env = exe_env (* TODO: unclear what this function is used for *) + +(** create a new execution environment *) +let create procset_opt = + { cg = Cg.create (); + proc_map = Procname.Hash.create 17; + file_map = Hashtbl.create 17; + active_opt = procset_opt; + callees = Procname.Set.empty; + procs_defined_in_several_files = Procname.Set.empty; + } + +(** check if a procedure is marked as active *) +let proc_is_active exe_env proc_name = + match exe_env.active_opt with + | None -> true + | Some procset -> Procname.Set.mem proc_name procset + +(** add a procedure to the set of active procedures *) +let add_active_proc exe_env proc_name = + match exe_env.active_opt with + | None -> () + | Some procset -> exe_env.active_opt <- Some (Procname.Set.add proc_name procset) + +(** Add a callee to the exe_env, and extend the file_map and proc_map. *) +let add_callee (exe_env: t) (source_file : DB.source_file) (pname: Procname.t) = + exe_env.callees <- Procname.Set.add pname exe_env.callees; + let file_data_opt = + try Some (Hashtbl.find exe_env.file_map source_file) + with Not_found -> + let source_dir = DB.source_dir_from_source_file source_file in + let cg_fname = DB.source_dir_get_internal_file source_dir ".cg" in + (match Cg.load_from_file cg_fname with + | None -> None + | Some cg -> + let nLOC = Cg.get_nLOC cg in + let file_data = new_file_data source_file nLOC cg_fname in + Some file_data) in + match file_data_opt with + | None -> () + | Some file_data -> + if (not (Procname.Hash.mem exe_env.proc_map pname)) + then Procname.Hash.replace exe_env.proc_map pname file_data + +(** like add_cg, but use exclude_fun to determine files to be excluded *) +let add_cg_exclude_fun (exe_env: t) (source_dir : DB.source_dir) exclude_fun = + let cg_fname = DB.source_dir_get_internal_file source_dir ".cg" in + let cg = match Cg.load_from_file cg_fname with + | None -> (L.err "cannot load %s@." (DB.filename_to_string cg_fname); assert false) + | Some cg -> + Cg.restrict_defined cg exe_env.active_opt; + cg in + let source = Cg.get_source cg in + if exclude_fun source then None + else + let nLOC = Cg.get_nLOC cg in + Cg.extend exe_env.cg cg; + let file_data = new_file_data source nLOC cg_fname in + let defined_procs = Cg.get_defined_nodes cg in + list_iter (fun pname -> + let should_update = + if Procname.Hash.mem exe_env.proc_map pname then + let old_source = (Procname.Hash.find exe_env.proc_map pname).source in + exe_env.procs_defined_in_several_files <- Procname.Set.add pname exe_env.procs_defined_in_several_files; + (* L.err "Warning: procedure %a is defined in both %s and %s@." Procname.pp pname (DB.source_file_to_string source) (DB.source_file_to_string old_source); *) + source < old_source (* when a procedure is defined in several files, map to the first alphabetically *) + else true in + if should_update then Procname.Hash.replace exe_env.proc_map pname file_data) defined_procs; + Hashtbl.add exe_env.file_map source file_data; + Some cg + +(** add call graph from fname in the spec db, with relative tenv and cfg, to the execution environment *) +let add_cg exe_env (source_dir : DB.source_dir) = + add_cg_exclude_fun exe_env source_dir (fun _ -> false) + +(** add a new source file -> file data mapping. arguments are the components of the file_data + * record *) +let add_file_mapping exe_env source nLOC tenv_file tenv cfg_file cfg = + let file_data = + { source = source; + nLOC = nLOC; + tenv_file = tenv_file; + tenv = tenv; + cfg_file = cfg_file; + cfg = cfg; + } in + Hashtbl.add exe_env.file_map source file_data + +(** get the procedures defined in more than one file *) +let get_procs_defined_in_several_files exe_env = + exe_env.procs_defined_in_several_files + +(** get the global call graph *) +let get_cg exe_env = + exe_env.cg + +let get_file_data exe_env pname = + try + Procname.Hash.find exe_env.proc_map pname + with Not_found -> + L.err "can't find tenv_cfg_object for %a@." Procname.pp pname; + raise Not_found + +(** return the source file associated to the procedure *) +let get_source exe_env pname = + (get_file_data exe_env pname).source + +let file_data_to_tenv file_data = + if file_data.tenv == None then file_data.tenv <- Sil.load_tenv_from_file file_data.tenv_file; + match file_data.tenv with + | None -> + L.err "Cannot find tenv for %s@." (DB.filename_to_string file_data.tenv_file); + assert false + | Some tenv -> tenv + +(** return the procs enabled: active and not shadowed, plus the procs they call directly *) +let procs_enabled exe_env source = + let is_not_shadowed proc_name = (* not shadowed by a definition in another file *) + DB.source_file_equal (get_source exe_env proc_name) source in + match exe_env.active_opt with + | Some pset -> + let res = ref Procname.Set.empty in + let do_pname proc_name = (* add any proc which is not shadowed, and all the procs it calls *) + if is_not_shadowed proc_name then + let pset' = Cg.get_all_children exe_env.cg proc_name in + let pset'' = Procname.Set.add proc_name pset' in + res := Procname.Set.union pset'' !res in + Procname.Set.iter do_pname pset; + Some (Procname.Set.union !res exe_env.callees) (* keep callees in the cfg *) + | None -> None + +let file_data_to_cfg exe_env file_data = + match file_data.cfg with + | None -> + let cfg = match Cfg.load_cfg_from_file file_data.cfg_file with + | None -> + L.err "Cannot find cfg for %s@." (DB.filename_to_string file_data.tenv_file); + assert false + | Some cfg -> cfg in + Cfg.Node.cfg_restrict_enabled cfg file_data.source (procs_enabled exe_env file_data.source); + file_data.cfg <- Some cfg; + cfg + | Some cfg -> cfg + +(** return the type environment associated to the procedure *) +let get_tenv exe_env pname = + let file_data = get_file_data exe_env pname in + file_data_to_tenv file_data + +(** return the cfg associated to the procedure *) +let get_cfg exe_env pname = + let file_data = get_file_data exe_env pname in + file_data_to_cfg exe_env file_data + +(** [iter_files f exe_env] applies [f] to the filename and tenv and cfg for each file in [exe_env] *) +let iter_files f exe_env = + let do_file fname file_data = + DB.current_source := fname; + Config.nLOC := file_data.nLOC; + f fname (file_data_to_tenv file_data) (file_data_to_cfg exe_env file_data) in + Hashtbl.iter do_file exe_env.file_map + +(** [fold_files f exe_env] folds f through the source file, tenv, and cfg for each file in [exe_env] *) +let fold_files f acc exe_env = + let fold_file fname file_data acc = + DB.current_source := fname; + Config.nLOC := file_data.nLOC; + f fname (file_data_to_tenv file_data) (file_data_to_cfg exe_env file_data) acc in + Hashtbl.fold fold_file exe_env.file_map acc diff --git a/infer/src/backend/exe_env.mli b/infer/src/backend/exe_env.mli new file mode 100644 index 000000000..a089fd16f --- /dev/null +++ b/infer/src/backend/exe_env.mli @@ -0,0 +1,62 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +open Utils + +(** Support for Execution environments *) + +(** initial state, used to add cg's *) +type initial + +(** execution environment: a global call graph, and map from procedure names to cfg and tenv *) +type t + +(** freeze the execution environment, so it can be queried *) +val freeze : initial -> t + +(** create a new execution environment, given an optional set restricting the active procedures *) +val create : Procname.Set.t option -> initial + +(** Add a callee to the exe_env, and extend the file_map and proc_map. *) +val add_callee : t -> DB.source_file -> Procname.t -> unit + +(** add call graph from the source dir in the spec db, with relative tenv and cfg, to the execution environment *) +val add_cg : initial -> DB.source_dir -> Cg.t option + +(** like add_cg, but use exclude_fun to determine files to be excluded *) +val add_cg_exclude_fun : initial -> DB.source_dir -> (DB.source_file -> bool) -> Cg.t option + +(** add a new source file -> file data mapping. arguments are the components of the file_data + * record *) +val add_file_mapping : t -> DB.source_file -> int -> DB.filename -> Sil.tenv option -> DB.filename + -> Cfg.cfg option -> unit + +(** get the global call graph *) +val get_cg : t -> Cg.t + +(** get the procedures defined in more than one file *) +val get_procs_defined_in_several_files : t -> Procname.Set.t + +(** return the source file associated to the procedure *) +val get_source : t -> Procname.t -> DB.source_file + +(** return the type environment associated to the procedure *) +val get_tenv : t -> Procname.t -> Sil.tenv + +(** return the cfg associated to the procedure *) +val get_cfg : t -> Procname.t -> Cfg.cfg + +(** [iter_files f exe_env] applies [f] to the source file and tenv and cfg for each file in [exe_env] *) +val iter_files : (DB.source_file -> Sil.tenv -> Cfg.cfg -> unit) -> t -> unit + +(** [fold_files f exe_env] folds f through the source file, tenv, and cfg for each file in [exe_env] *) +val fold_files : (DB.source_file -> Sil.tenv -> Cfg.cfg -> 'a -> 'a) -> 'a -> t -> 'a + +(** check if a procedure is marked as active *) +val proc_is_active : t -> Procname.t -> bool + +(** add a procedure to the set of active procedures *) +val add_active_proc : t -> Procname.t -> unit diff --git a/infer/src/backend/fork.ml b/infer/src/backend/fork.ml new file mode 100644 index 000000000..115f331aa --- /dev/null +++ b/infer/src/backend/fork.ml @@ -0,0 +1,692 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +module L = Logging +module F = Format +open Utils + +(* =============== START of module Process =============== *) + +module type Process_signature = +sig + type t + type val_t = (Procname.t * Cg.in_out_calls) (** type of values sent to children *) + val get_remaining_processes : unit -> t list (** return the list of remaining processes *) + val kill_remaining_processes : unit -> unit (** kill the remaining processes *) + val kill_process : t -> unit + val get_node_calls : t -> val_t option + val spawn_fun : (val_t -> Specs.summary) -> t + val send_to_child : t -> val_t -> unit + val receive_from_child : unit -> t * Specs.summary + val get_last_input : t -> val_t +end + +(** Implementation of the process interface for the simulator (processes are implemented just as functions) *) +module Process_simulate : Process_signature = struct + type t = int + type val_t = (Procname.t * Cg.in_out_calls) + let count = ref 0 + let funs = Hashtbl.create 17 + + let spawn_fun f = + incr count; + Hashtbl.add funs !count (f, None); + !count + + let get_remaining_processes () = + let ids = ref [] in + Hashtbl.iter (fun id _ -> ids := id :: !ids) funs; + list_rev !ids + + let kill_remaining_processes () = + Hashtbl.clear funs + + let kill_process id = + Hashtbl.remove funs id + + let get_node_calls p = + try + match Hashtbl.find funs p with + | (f, Some (x, res)) -> Some x + | _ -> None + with Not_found -> None + + let send_to_child id (x : val_t) = + let (f, _) = Hashtbl.find funs id in + Hashtbl.replace funs id (f, Some (x, f x)) + + let receive_from_child () : t * Specs.summary = + let some_id = ref (- 1) in + Hashtbl.iter (fun id _ -> some_id := id) funs; + match Hashtbl.find funs !some_id with + | (f, Some (x, res)) -> (!some_id, res) + | _ -> assert false + + let get_last_input id = + match Hashtbl.find funs id with + | (f, Some (x, res)) -> x + | _ -> assert false +end + +(** Implementation of the process interface using fork and pipes *) +module Process_fork : Process_signature = struct + + let shared_in, shared_out = (* shared channel from children to parent *) + let (read, write) = Unix.pipe () + in Unix.in_channel_of_descr read, Unix.out_channel_of_descr write + + type val_t = Procname.t * Cg.in_out_calls + + type pipe_str = + { p2c_in : in_channel; + p2c_out : out_channel; + c2p_in : in_channel; + c2p_out : out_channel; + mutable input : val_t option; + mutable pid : int } + + type t = pipe_str + + let processes = ref [] + + let get_node_calls p_str = + p_str.input + + let send_to_child p_str (v : val_t) = + p_str.input <- Some v; + Marshal.to_channel p_str.p2c_out v []; + flush p_str.p2c_out + + let incr_process_count p_str = + processes := p_str :: !processes + (* ; F.printf "@.Number of processes: %d@." (list_length !processes) *) + + let decr_process_count pid = + processes := list_filter (fun p_str' -> pid != p_str'.pid) !processes + (* ; F.printf "@.Number of processes: %d@." (list_length !processes) *) + + let kill_process p_str = + L.out "killing process %d@." p_str.pid; + Unix.kill p_str.pid Sys.sigkill; + Unix.close (Unix.descr_of_in_channel p_str.p2c_in); + Unix.close (Unix.descr_of_out_channel p_str.p2c_out); + Unix.close (Unix.descr_of_in_channel p_str.c2p_in); + Unix.close (Unix.descr_of_out_channel p_str.c2p_out); + ignore (Unix.waitpid [] p_str.pid); + decr_process_count p_str.pid + + let get_remaining_processes () = + !processes + + let kill_remaining_processes () = + L.out "@.%d remaining processes@." (list_length !processes); + list_iter kill_process !processes + + let rec receive_from_child () : t * Specs.summary = + let sender_pid = input_binary_int shared_in in + try + let p_str = (* find which process sent its pid on the shared channel *) + list_find (fun p_str -> p_str.pid = sender_pid) !processes in + let (summ : Specs.summary) = Marshal.from_channel p_str.c2p_in in + (p_str, summ) + with Not_found -> + L.err "@.ERROR: process %d was killed while trying to communicate with the parent@." sender_pid; + receive_from_child () (* wait for communication from the next process *) + + let receive_from_parent p_str : val_t = + Marshal.from_channel p_str.p2c_in + + let send_to_parent (p_str: t) (summ: Specs.summary) = + output_binary_int shared_out p_str.pid; (* tell parent I'm sending the result *) + flush shared_out; + Marshal.to_channel p_str.c2p_out summ []; + flush p_str.c2p_out + + let get_last_input p_str = + match p_str.input with + | None -> assert false + | Some x -> x + + let spawn_fun (service_f : val_t -> Specs.summary) = + let p_str = + let (p2c_read, p2c_write) = Unix.pipe () in + let (c2p_read, c2p_write) = Unix.pipe () in + (* Unix.set_nonblock c2p_read; *) + { p2c_in = Unix.in_channel_of_descr p2c_read; + p2c_out = Unix.out_channel_of_descr p2c_write; + c2p_in = Unix.in_channel_of_descr c2p_read; + c2p_out = Unix.out_channel_of_descr c2p_write; + input = None; + pid = 0 } in + let colour = L.next_colour () in + match Unix.fork () with + | 0 -> + Config.in_child_process := true; + p_str.pid <- Unix.getpid (); + L.change_terminal_colour colour; + L.out "@.STARTING PROCESS %d@." p_str.pid; + let rec loop () = + let n = receive_from_parent p_str in + let res = service_f n in + send_to_parent p_str res; + loop () in + loop () + | cid -> + p_str.pid <- cid; + incr_process_count p_str; + p_str +end + +(* =============== END of module Process =============== *) + +let parallel_mode = ref false + +(* =============== START of module Timing_log =============== *) +module Timing_log : sig + val event_start : string -> unit + val event_finish : string -> unit + val print_timing : unit -> unit +end = struct + type ev_kind = START | FINISH + type event = { time : float; kind : ev_kind; name : string } + + let active = ref [] + let log = ref [] + let bogus_time = - 1000.0 + let bogus_event = { time = bogus_time; kind = START; name ="" } + let last_event = ref bogus_event + let initial_time = ref bogus_time + let total_procs_time = ref 0.0 + let total_cores_time = ref 0.0 + + let reset () = + active := []; + log := []; + last_event := bogus_event; + initial_time := bogus_time; + total_procs_time := 0.0; + total_cores_time := 0.0 + + let expand_log event = + let elapsed = event.time -. !last_event.time in + let num_procs = list_length !active in + let num_cores = min num_procs !Config.num_cores in + match Pervasives.(=) !last_event bogus_event with + | true -> + last_event := event; + initial_time := event.time; + | false -> + let label = + list_fold_left (fun s name -> "\\n" ^ name ^s) "" (list_rev !active) in + if !Config.write_dotty then log := (!last_event, label, event)::!log; + total_procs_time := (float_of_int num_procs) *. elapsed +. !total_procs_time; + total_cores_time := (float_of_int num_cores) *. elapsed +. !total_cores_time; + last_event := event + + let event_start s = + expand_log { time = (Unix.gettimeofday ()); kind = START; name = s }; + active := s :: !active; + L.err " %d cores active@." (list_length !active) + + let event_finish s = + expand_log { time = (Unix.gettimeofday ()); kind = FINISH; name = s }; + active := list_filter (fun s' -> s' <> s) !active + + let print_timing () = + let total_time = !last_event.time -. !initial_time in + (* + let avg_num_proc = !total_procs_time /. total_time in + let avg_num_cores = !total_cores_time /. total_time in + *) + if !Config.write_dotty then begin + let pp_event fmt event = match event.kind with + | START -> F.fprintf fmt "\"%fs START %s\"" event.time event.name + | FINISH -> F.fprintf fmt "\"%fs FINISH %s\"" event.time event.name in + let pp_edge fmt (event1, label, event2) = + let color = match event1.kind with + | START -> "green" + | FINISH -> "red" in + F.fprintf fmt "%a -> %a [label=\"{%fs}%s\",color=\"%s\"]\n" pp_event event1 pp_event event2 (event2.time -. event1.time) label color in + let outc = open_out (DB.filename_to_string (DB.Results_dir.path_to_filename DB.Results_dir.Abs_root ["timing.dot"])) in + let fmt = F.formatter_of_out_channel outc in + F.fprintf fmt "digraph {\n"; + list_iter (pp_edge fmt) !log; + F.fprintf fmt "}@."; + close_out outc; + end; + reset (); + L.err "Analysis time: %fs@." total_time +end +(* =============== END of module Timing_log =============== *) + +(** print the timing stats, and generate timing.dot if in dotty mode *) +let print_timing () = + Timing_log.print_timing () + +module WeightedPnameSet = + Set.Make(struct + type t = (Procname.t * Cg.in_out_calls) + let compare ((pn1: Procname.t), (calls1: Cg.in_out_calls)) ((pn2: Procname.t), (calls2: Cg.in_out_calls)) = + let n = int_compare calls1.Cg.in_calls calls2.Cg.in_calls in if n != 0 then n + else Procname.compare pn1 pn2 + end) + +let pp_weightedpnameset fmt set = + let f (pname, weight) = F.fprintf fmt "%a@ " Procname.pp pname in + WeightedPnameSet.iter f set + +let compute_weighed_pnameset gr = + let pnameset = ref WeightedPnameSet.empty in + let add_pname_calls (pn, calls) = + pnameset := WeightedPnameSet.add (pn, calls) !pnameset in + list_iter add_pname_calls (Cg.get_nodes_and_calls gr); + !pnameset + +(* Return true if there are no children of [pname] whose specs +have changed since [pname] was last analyzed. *) +let proc_is_up_to_date gr pname = + match Specs.get_summary pname with + | None -> false + | Some summary -> + let filter dependent_proc = Specs.get_timestamp summary = + Procname.Map.find dependent_proc summary.Specs.dependency_map in + let res = + Specs.is_inactive pname && + Procname.Set.for_all filter (Cg.get_defined_children gr pname) in + res + +(** Return the list of procedures which should perform a phase +transition from [FOOTPRINT] to [RE_EXECUTION] *) +let should_perform_transition gr proc_name : Procname.t list = + let recursive_dependents = Cg.get_recursive_dependents gr proc_name in + let recursive_dependents_plus_self = Procname.Set.add proc_name recursive_dependents in + let should_transition = + Specs.get_phase proc_name == Specs.FOOTPRINT && + Procname.Set.for_all (fun pn -> Specs.is_inactive pn) recursive_dependents && + Procname.Set.for_all (proc_is_up_to_date gr) recursive_dependents in + if should_transition then Procname.Set.elements recursive_dependents_plus_self + else [] + +(** Perform the transition from [FOOTPRINT] to [RE_EXECUTION] in spec table *) +let transition_footprint_re_exe proc_name joined_pres = + L.out "Transition %a from footprint to re-exe@." Procname.pp proc_name; + let summary = Specs.get_summary_unsafe proc_name in + let summary' = + if !Config.only_footprint then + { summary with + Specs.phase = Specs.RE_EXECUTION; + } + else + { summary with + Specs.timestamp = 0; + Specs.phase = Specs.RE_EXECUTION; + Specs.dependency_map = Specs.re_initialize_dependency_map summary.Specs.dependency_map; + Specs.payload = + let specs = + list_map + (fun jp -> + Specs.spec_normalize + { Specs.pre = jp; + Specs.posts = []; + Specs.visited = Specs.Visitedset.empty }) + joined_pres in + Specs.PrePosts specs + } in + Specs.add_summary proc_name summary' + +module SpecMap = Map.Make (struct + type t = Prop.normal Specs.Jprop.t + let compare = Specs.Jprop.compare + end) + +(** Update the specs of the current proc after the execution of one phase *) +let update_specs proc_name (new_specs : Specs.NormSpec.t list) : Specs.NormSpec.t list * bool = + let new_specs = Specs.normalized_specs_to_specs new_specs in + let phase = Specs.get_phase proc_name in + let old_specs = Specs.get_specs proc_name in + let changed = ref false in + let current_specs = + ref + (list_fold_left + (fun map spec -> + SpecMap.add + spec.Specs.pre + (Paths.PathSet.from_renamed_list spec.Specs.posts, spec.Specs.visited) map) + SpecMap.empty old_specs) in + let re_exe_filter old_spec = (* filter out pres which failed re-exe *) + if phase == Specs.RE_EXECUTION && not (list_exists (fun new_spec -> Specs.Jprop.equal new_spec.Specs.pre old_spec.Specs.pre) new_specs) + then begin + changed:= true; + L.out "Specs changed: removing pre of spec@\n%a@." (Specs.pp_spec pe_text None) old_spec; + current_specs := SpecMap.remove old_spec.Specs.pre !current_specs end + else () in + let add_spec spec = (* add a new spec by doing union of the posts *) + try + let old_post, old_visited = SpecMap.find spec.Specs.pre !current_specs in + let new_post, new_visited = + Paths.PathSet.union + old_post + (Paths.PathSet.from_renamed_list spec.Specs.posts), + Specs.Visitedset.union old_visited spec.Specs.visited in + if not (Paths.PathSet.equal old_post new_post) then begin + changed := true; + L.out "Specs changed: added new post@\n%a@." (Propset.pp pe_text (Specs.Jprop.to_prop spec.Specs.pre)) (Paths.PathSet.to_propset new_post); + current_specs := SpecMap.add spec.Specs.pre (new_post, new_visited) (SpecMap.remove spec.Specs.pre !current_specs) end + + with Not_found -> + changed := true; + L.out "Specs changed: added new pre@\n%a@." (Specs.Jprop.pp_short pe_text) spec.Specs.pre; + current_specs := + SpecMap.add + spec.Specs.pre + ((Paths.PathSet.from_renamed_list spec.Specs.posts), spec.Specs.visited) + !current_specs in + let res = ref [] in + let convert pre (post_set, visited) = + res := + Specs.spec_normalize + { Specs.pre = pre; + Specs.posts = Paths.PathSet.elements post_set; + Specs.visited = visited }:: !res in + list_iter re_exe_filter old_specs; (* filter out pre's which failed re-exe *) + list_iter add_spec new_specs; (* add new specs *) + SpecMap.iter convert !current_specs; + !res,!changed + +let tot_procs = ref 0 (** Total number of procedures to analyze *) +let num_procs_done = ref 0 (** Number of procedures done *) +let wpnames_todo = ref WeightedPnameSet.empty (** Weighted pnames (procedure names with weight) to do *) +let tot_files = ref 1 (** Total number of files in all the clusters *) +let tot_files_done = ref 0 (** Total number of files done so far *) +let this_cluster_files = ref 1 (** Number of files in the current cluster *) + +(** Return true if [pname] is done and requires no further analysis *) +let proc_is_done gr pname = + not (WeightedPnameSet.mem (pname, Cg.get_calls gr pname) !wpnames_todo) + +(** flag to activate tracing of the parallel algorithm *) +let trace = ref false + +(** Return true if [pname] has just become done *) +let procs_become_done gr pname : Procname.t list = + let recursive_dependents = Cg.get_recursive_dependents gr pname in + let nonrecursive_dependents = Cg.get_nonrecursive_dependents gr pname in + let summary = Specs.get_summary_unsafe pname in + let is_done = Specs.get_timestamp summary <> 0 && + Specs.is_inactive pname && + (!Config.only_footprint || Specs.get_phase pname == Specs.RE_EXECUTION) && + Procname.Set.for_all (proc_is_done gr) nonrecursive_dependents && + Procname.Set.for_all (proc_is_up_to_date gr) recursive_dependents in + if !trace then L.err "proc is%s done@." (if is_done then "" else " not"); + if is_done + then + let procs_to_remove = + (* the proc itself if not recursive, otherwise all the recursive circle *) + Procname.Set.add pname recursive_dependents in + Procname.Set.elements procs_to_remove + else [] + +let post_process_procs exe_env procs_done = + let check_no_specs pn = + if Specs.get_specs pn = [] then begin + Errdesc.warning_err + (Specs.get_summary_unsafe pn).Specs.loc "No specs found for %a@." Procname.pp pn + end in + let cg = Exe_env.get_cg exe_env in + list_iter (fun pn -> + let elem = (pn, Cg.get_calls cg pn) in + if WeightedPnameSet.mem elem !wpnames_todo then + begin + incr num_procs_done; + wpnames_todo := WeightedPnameSet.remove (pn, Cg.get_calls cg pn) !wpnames_todo; + let whole_seconds = false in + check_no_specs pn; + Printer.proc_write_log whole_seconds (Exe_env.get_cfg exe_env pn) pn + end + ) procs_done + +(** Activate a check which ensures that multi-core mode gives the same result as one-core. +If true, detect when a dependent proc is active (analyzed concurrently) +and in that case wait for a process to terminate next *) +let one_core_compatibility_mode = ref true + +(** Find the max string in the [set] which satisfies [filter], and count the number of attempts. +Precedence is given to strings in [priority_set] *) +let filter_max exe_env cg filter set priority_set = + let rec find_max n filter set = + let elem = WeightedPnameSet.max_elt set in + let check_one_core_compatibility () = + if !one_core_compatibility_mode && + Procname.Set.exists (fun child -> Specs.is_active child) (Cg.get_dependents cg (fst elem)) + then raise Not_found in + check_one_core_compatibility (); + if filter elem then + begin + let proc_name = fst elem in + Config.footprint := Specs.get_phase proc_name = Specs.FOOTPRINT; + let file_name = Exe_env.get_source exe_env proc_name in + let action = if !Config.footprint then "Discovering" else "Verifying" in + let pp_cluster_info fmt () = + let files_done_previous_clusters = float_of_int !tot_files_done in + let ratio_procs_this_cluster = (float_of_int !num_procs_done) /. (float_of_int !tot_procs) in + let files_done_this_cluster = (float_of_int !this_cluster_files) *. ratio_procs_this_cluster in + let files_done = files_done_previous_clusters +. files_done_this_cluster in + let perc_total = 100. *. files_done /. (float_of_int !tot_files) in + F.fprintf fmt " (%3.2f%% total)" perc_total in + L.err "@\n**%s specs: %a file: %s@\n" action Procname.pp proc_name (DB.source_file_to_string file_name); + L.err " %d/%d procedures done%a@." !num_procs_done !tot_procs pp_cluster_info (); + elem + end + else + begin + find_max (n + 1) filter (WeightedPnameSet.remove elem set) + end in + try find_max 1 filter (WeightedPnameSet.inter set priority_set) (* try with priority elements first *) + with Not_found -> find_max 1 filter set + +(** Handle timeout events *) +module Timeout : sig + val exe_timeout : int -> ('a -> 'b) -> 'a -> 'b option (* execute the function up to a given timeout given by the parameter *) +end = struct + let set_alarm nsecs = + match Config.os_type with + | Config.Unix | Config.Cygwin -> + ignore (Unix.setitimer Unix.ITIMER_REAL + { Unix.it_interval = 3.0; (* try again after 3 seconds if the signal is lost *) + Unix.it_value = float_of_int nsecs }) + | Config.Win32 -> + SymOp.set_wallclock_alarm nsecs + + let unset_alarm () = + match Config.os_type with + | Config.Unix | Config.Cygwin -> set_alarm 0 + | Config.Win32 -> SymOp.unset_wallclock_alarm () + + let pp_kind f = function + | TOtime -> + F.fprintf f "time" + | TOrecursion n -> + F.fprintf f "recursion %d" n + | TOsymops n -> + F.fprintf f "SymOps %d" n + + let timeout_action _ = + unset_alarm (); + raise (Timeout_exe (TOtime)) + + let () = begin + match Config.os_type with + | Config.Unix | Config.Cygwin -> + Sys.set_signal Sys.sigvtalrm (Sys.Signal_handle timeout_action); + Sys.set_signal Sys.sigalrm (Sys.Signal_handle timeout_action) + | Config.Win32 -> + SymOp.set_wallclock_timeout_handler timeout_action; + ignore (Gc.create_alarm SymOp.check_wallclock_alarm) (* use the Gc alarm for periodic timeout checks *) + end + + let exe_timeout iterations f x = + try + set_iterations iterations; + set_alarm (get_timeout_seconds ()); + SymOp.set_alarm (); + let res = f x in + unset_alarm (); + SymOp.unset_alarm (); + Some res + with + | Timeout_exe kind -> + unset_alarm (); + SymOp.unset_alarm (); + Errdesc.warning_err (State.get_loc ()) "TIMEOUT: %a@." pp_kind kind; + None + | exe -> + unset_alarm (); + SymOp.unset_alarm (); + raise exe +end + +module Process = Process_fork + +(** Main algorithm responsible for driving the analysis of an Exe_env (set of procedures). +The algorithm computes dependencies between procedures, spawns processes if required, +propagates results, and handles fixpoints in the call graph. *) +let parallel_execution exe_env num_processes analyze_proc filter_out process_result : unit = + parallel_mode := num_processes > 1 || !Config.max_num_proc > 0; + let call_graph = Exe_env.get_cg exe_env in + let filter_initial (pname, w) = + let summary = Specs.get_summary_unsafe pname in + Specs.get_timestamp summary = 0 in + wpnames_todo := WeightedPnameSet.filter filter_initial (compute_weighed_pnameset call_graph); + let wpnames_address_of = (* subset of the procedures whose address is taken *) + let address_taken_of n = + Procname.Set.mem n (Cfg.get_priority_procnames (Exe_env.get_cfg exe_env n)) in + WeightedPnameSet.filter (fun (n, _) -> address_taken_of n) !wpnames_todo in + tot_procs := WeightedPnameSet.cardinal !wpnames_todo; + num_procs_done := 0; + let avail_num = ref num_processes (* number of processors available *) in + let max_timeout = ref 1 in + let wpname_can_be_analyzed (pname, weight) : bool = (* Return true if [pname] is not up to date and it can be analyzed right now *) + Specs.is_inactive pname && + Procname.Set.for_all + (proc_is_done call_graph) (Cg.get_nonrecursive_dependents call_graph pname) && + Procname.Set.for_all + (fun child -> Specs.is_inactive child) (Cg.get_defined_children call_graph pname) && + (Specs.get_timestamp (Specs.get_summary_unsafe pname) = 0 + || not (proc_is_up_to_date call_graph pname)) in + let process_one_proc pname (calls: Cg.in_out_calls) = + DB.current_source := (Specs.get_summary_unsafe pname).Specs.loc.Sil.file; + if !trace then + begin + let whole_seconds = false in + L.err "@[ *********** Summary of %a ***********@," Procname.pp pname; + L.err "%a@]@\n" (Specs.pp_summary pe_text whole_seconds) (Specs.get_summary_unsafe pname) + end; + if filter_out call_graph pname + then + post_process_procs exe_env [pname] + else + begin + Specs.set_status pname Specs.ACTIVE; + max_timeout := max (Specs.get_iterations pname) !max_timeout; + Specs.update_dependency_map pname; + if !parallel_mode then + let p_str = Process.spawn_fun (analyze_proc exe_env) in + Timing_log.event_start (Procname.to_string pname); + Process.send_to_child p_str (pname, calls); + decr avail_num + else + begin + Timing_log.event_start (Procname.to_string pname); + process_result exe_env (pname, calls) (analyze_proc exe_env (pname, calls)); + Timing_log.event_finish (Procname.to_string pname) + end + end in + let wait_for_next_result () = + try + match Timeout.exe_timeout (2 * !max_timeout) Process.receive_from_child () with + | None -> + let remaining_procedures = + let procs = list_map Process.get_node_calls (Process.get_remaining_processes ()) in + list_map (function None -> assert false | Some x -> x) procs in + L.err "No process is responding: killing %d pending processes@." (list_length remaining_procedures); + Process.kill_remaining_processes (); + let do_proc (pname, calls) = + let prev_summary = Specs.get_summary_unsafe pname in + let timestamp = max 1 (prev_summary.Specs.timestamp) in + let stats = { prev_summary.Specs.stats with Specs.stats_timeout = true } in + let summ = + { prev_summary with + Specs.stats = stats; + Specs.payload = Specs.PrePosts []; + Specs.timestamp = timestamp; + Specs.status = Specs.INACTIVE } in + process_result exe_env (pname, calls) summ; + Timing_log.event_finish (Procname.to_string pname); + incr avail_num in + list_iter do_proc remaining_procedures + | Some (p_str, summ) -> + let (pname, weight) = Process.get_last_input p_str in + (try + DB.current_source := (Specs.get_summary_unsafe pname).Specs.loc.Sil.file; + process_result exe_env (pname, weight) summ + with exn -> assert false); + Timing_log.event_finish (Procname.to_string pname); + Process.kill_process p_str; + incr avail_num + with + | Sys_blocked_io -> () in + while not (WeightedPnameSet.is_empty !wpnames_todo) do + if !avail_num > 0 then + begin + if !trace then begin + let analyzable_pnames = WeightedPnameSet.filter wpname_can_be_analyzed !wpnames_todo in + L.err "Nodes todo: %a@\n" pp_weightedpnameset !wpnames_todo; + L.err "Analyzable procs: %a@\n" pp_weightedpnameset analyzable_pnames + end; + try + let pname, calls = filter_max exe_env call_graph wpname_can_be_analyzed !wpnames_todo wpnames_address_of in (** find max analyzable proc *) + process_one_proc pname calls + with Not_found -> (* no analyzable procs *) + if !avail_num < num_processes (* some other process is doing work *) + then wait_for_next_result () + else + (L.err "Error: can't analyze any procs. Printing current spec table@\n@[%a@]@." (Specs.pp_spec_table pe_text false) (); + raise (Failure "Stopping")) + end + else + wait_for_next_result () + done + +(** [parallel_iter_nodes cfg call_graph analyze_proc process_result filter_out] +executes [analyze_proc] in parallel as much as possible as allowed +by the call graph, and applies [process_result] to the result as +soon as it is returned by a child process. If [filter_out] returns +true, no execution. *) +let parallel_iter_nodes (exe_env: Exe_env.t) (_analyze_proc: Exe_env.t -> Procname.t -> 'a) (_process_result: Exe_env.t -> (Procname.t * Cg.in_out_calls) -> 'a -> unit) (filter_out: Cg.t -> Procname.t -> bool) : unit = + let analyze_proc exe_env pname = (* wrap _analyze_proc and handle exceptions *) + try _analyze_proc exe_env pname with + | exn -> + Reporting.log_error pname exn; + let prev_summary = Specs.get_summary_unsafe pname in + let timestamp = max 1 (prev_summary.Specs.timestamp) in + let stats = { prev_summary.Specs.stats with Specs.stats_timeout = true } in + let summ = + { prev_summary with + Specs.stats = stats; + Specs.payload = Specs.PrePosts []; + Specs.timestamp = timestamp } in + summ in + let process_result exe_env (pname, calls) summary = (* wrap _process_result and handle exceptions *) + try _process_result exe_env (pname, calls) summary with + | exn -> + let err_name, _, mloco, _, _, _, _ = Exceptions.recognize_exception exn in + let err_str = "process_result raised " ^ (Localise.to_string err_name) in + L.err "Error: %s@." err_str; + let exn' = Exceptions.Internal_error (Localise.verbatim_desc err_str) in + Reporting.log_error pname exn'; + post_process_procs exe_env [pname] in + let num_processes = if !Config.max_num_proc = 0 then !Config.num_cores else !Config.max_num_proc in + Unix.handle_unix_error (parallel_execution exe_env num_processes (fun exe_env (n, w) -> analyze_proc exe_env n) filter_out) process_result diff --git a/infer/src/backend/fork.mli b/infer/src/backend/fork.mli new file mode 100644 index 000000000..ec7b152d9 --- /dev/null +++ b/infer/src/backend/fork.mli @@ -0,0 +1,42 @@ +(* +* Copyright (c) 2009-2013 Monoidics ltd. +* Copyright (c) 2013- Facebook. +* All rights reserved. +*) + +(** Implementation of the Parallel Interprocedural Footprint Analysis Algorithm *) + +(** Handle timeout events *) +module Timeout : sig + val exe_timeout : int -> ('a -> 'b) -> 'a -> 'b option (* execute the function up to a given timeout given by the iterations parameter *) +end + +val this_cluster_files : int ref (** Number of files in the current cluster *) +val tot_files : int ref (** Total number of files in all the clusters *) +val tot_files_done : int ref (** Total number of files done so far *) + +(** {2 Algorithm} *) + +val procs_become_done : Cg.t -> Procname.t -> Procname.t list + +val post_process_procs : Exe_env.t -> Procname.t list -> unit + +(** Return the list of procedures which should perform a phase +transition from [FOOTPRINT] to [RE_EXECUTION] *) +val should_perform_transition : Cg.t -> Procname.t -> Procname.t list + +(** Perform the transition from [FOOTPRINT] to [RE_EXECUTION] in spec table *) +val transition_footprint_re_exe : Procname.t -> Prop.normal Specs.Jprop.t list -> unit + +(** Update the specs of the current proc after the execution of one phase *) +val update_specs : Procname.t -> Specs.NormSpec.t list -> Specs.NormSpec.t list * bool + +(** [parallel_iter_nodes tenv cfg call_graph analyze_proc process_result filter_out] +executes [analyze_proc] in parallel as much as possible as allowed +by the call graph, and applies [process_result] to the result as +soon as it is returned by a child process. If [filter_out] returns +true, no execution. *) +val parallel_iter_nodes : Exe_env.t -> (Exe_env.t -> Procname.t -> Specs.summary) -> (Exe_env.t -> (Procname.t * Cg.in_out_calls) -> Specs.summary -> unit) -> (Cg.t -> Procname.t -> bool) -> unit + +(** print the timing stats, and generate timing.dot if in dotty mode *) +val print_timing : unit -> unit diff --git a/infer/src/backend/ident.ml b/infer/src/backend/ident.ml new file mode 100644 index 000000000..ae9b92c2d --- /dev/null +++ b/infer/src/backend/ident.ml @@ -0,0 +1,344 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Module for Names and Identifiers *) + +module L = Logging +module F = Format +open Utils + +type name = string + +type fieldname = + { fpos : int; + fname : Mangled.t } + +type kind = int + +let kprimed = - 1 +let knormal = 0 +let kfootprint = 1 + +type t = + { kind: int; + name: name; + stamp: int } + +type _ident = t + +(** {2 Comparison Functions} *) + +let name_compare = string_compare + +let fieldname_compare fn1 fn2 = + let n = int_compare fn1.fpos fn2.fpos in + if n <> 0 then n else Mangled.compare fn1.fname fn2.fname + +let name_equal = string_equal + +let kind_equal k1 k2 = k1 == k2 + +let compare i1 i2 = + let n = i2.kind - i1.kind + in if n <> 0 then n + else + let n = name_compare i1.name i2.name + in if n <> 0 then n + else int_compare i1.stamp i2.stamp + +let equal i1 i2 = + i1.stamp == i2.stamp && i1.kind == i2.kind && name_equal i1.name i2.name (* most unlikely first *) + +let fieldname_equal fn1 fn2 = + fieldname_compare fn1 fn2 = 0 + +let rec ident_list_compare il1 il2 = match il1, il2 with + | [],[] -> 0 + | [], _ -> - 1 + | _,[] -> 1 + | i1:: l1, i2:: l2 -> + let n = compare i1 i2 + in if n <> 0 then n + else ident_list_compare l1 l2 + +let ident_list_equal ids1 ids2 = (ident_list_compare ids1 ids2 = 0) + +(** {2 Set for identifiers} *) + +module IdentSet = Set.Make + (struct + type t = _ident + let compare = compare + end) + +module IdentHash = + Hashtbl.Make(struct + type t = _ident + let equal = equal + let hash (id: t) = Hashtbl.hash id + end) + +module FieldSet = Set.Make(struct + type t = fieldname + let compare = fieldname_compare +end) + +module FieldMap = Map.Make(struct + type t = fieldname + let compare = fieldname_compare +end) + +let idlist_to_idset ids = + list_fold_left (fun set id -> IdentSet.add id set) IdentSet.empty ids + +(** {2 Conversion between Names and Strings} *) + +module StringHash = + Hashtbl.Make(struct + type t = string + let equal (s1: string) (s2: string) = s1 = s2 + let hash = Hashtbl.hash + end) + +module NameHash = + Hashtbl.Make(struct + type t = name + let equal = name_equal + let hash = Hashtbl.hash + end) + +(** Convert a string to a name *) +let string_to_name (s: string) = + s + +(** Create a field name with the given position (field number in the CSU) *) +let create_fieldname (n: Mangled.t) (position: int) = + { fpos = position; + fname = n } + +(** Convert a name to a string. *) +let name_to_string (name: name) = + name + +(** Convert a fieldname to a string. *) +let fieldname_to_string fn = Mangled.to_string fn.fname + +(** Convert a fieldname to a simplified string with at most one-level path. *) +let fieldname_to_simplified_string fn = + let s = Mangled.to_string fn.fname in + match string_split_character s '.' with + | Some s1, s2 -> + (match string_split_character s1 '.' with + | Some s3, s4 -> s4 ^ "." ^ s2 + | _ -> s) + | _ -> s + +(** Convert a fieldname to a flat string without path. *) +let fieldname_to_flat_string fn = + let s = Mangled.to_string fn.fname in + match string_split_character s '.' with + | Some s1, s2 -> s2 + | _ -> s + +(** Returns the class part of the fieldname *) +let java_fieldname_get_class fn = + let fn = fieldname_to_string fn in + let ri = String.rindex fn '.' in + String.sub fn 0 ri + +(** Returns the last component of the fieldname *) +let java_fieldname_get_field fn = + let fn = fieldname_to_string fn in + let ri = 1 + String.rindex fn '.' in + String.sub fn ri (String.length fn - ri) + +(** Check if the field is the synthetic this$n of a nested class, used to access the n-th outher instance. *) +let java_fieldname_is_outer_instance fn = + let fn = fieldname_to_string fn in + let fn_len = String.length fn in + let this = ".this$" in + let this_len = String.length this in + let zero_to_nine s = s >= "0" && s <= "9" in + fn_len > this_len && + String.sub fn (fn_len - this_len - 1) this_len = this && + zero_to_nine (String.sub fn (fn_len - 1) 1) + +let fieldname_offset fn = fn.fpos + +(** hidded fieldname constant *) +let fieldname_hidden = create_fieldname (Mangled.from_string ".hidden") 0 + +(** hidded fieldname constant *) +let fieldname_is_hidden fn = + fieldname_equal fn fieldname_hidden + +(** {2 Functions and Hash Tables for Managing Stamps} *) + +(** Set the stamp of the identifier *) +let set_stamp i stamp = + { i with stamp = stamp } + +(** Get the stamp of the identifier *) +let get_stamp i = + i.stamp + +(** Map from names to stamps. *) +let name_map = NameHash.create 1000 + +(** Name used for primed tmp variables *) +let name_primed = string_to_name "t" + +(** Name used for normal tmp variables *) +let name_normal = string_to_name "n" + +(** Name used for footprint tmp variables *) +let name_footprint = string_to_name "f" + +(** Name used for spec variables *) +let name_spec = string_to_name "val" + +(** Name used for the return variable *) +let name_return = Mangled.from_string "return" + +(** Return the standard name for the given kind *) +let standard_name kind = + if kind == knormal then name_normal + else if kind == kfootprint then name_footprint + else name_primed + +(** Every identifier with a given stamp should unltimately be created using this function *) +let create_with_stamp kind name stamp = + let update_name_hash () = (* make sure that fresh ids after whis one will be with different stamps *) + try + let curr_stamp = NameHash.find name_map name in + let new_stamp = max curr_stamp stamp in + NameHash.replace name_map name new_stamp + with Not_found -> + NameHash.add name_map name stamp in + update_name_hash (); + { kind = kind; name = name; stamp = stamp } + +(** Create an identifier with default name for the given kind *) +let create kind stamp = + create_with_stamp kind (standard_name kind) stamp + +(** Generate a normal identifier with the given name and stamp *) +let create_normal name stamp = + create_with_stamp knormal name stamp + +(** Generate a primed identifier with the given name and stamp *) +let create_primed name stamp = + create_with_stamp kprimed name stamp + +(** Generate a footprint identifier with the given name and stamp *) +let create_footprint name stamp = + create_with_stamp kfootprint name stamp + +(** {2 Functions for Identifiers} *) + +(** Get a name of an identifier *) +let get_name id = + id.name + +let get_kind id = + id.kind + +let is_primed (id: t) = + id.kind == kprimed + +let is_normal (id: t) = + id.kind == knormal + +let is_footprint (id: t) = + id.kind == kfootprint + +(* timestamp for a path identifier *) +let path_ident_stamp = - 3 + +let is_path (id: t) = + id.kind == knormal && id.stamp = path_ident_stamp + +let make_ident_primed id = + if id.kind == kprimed then assert false + else { id with kind = kprimed } + +let make_unprimed id = + if id.kind <> kprimed then assert false + else { id with kind = knormal } + +(** Reset the name generator *) +let reset_name_generator () = + NameHash.clear name_map + +(** Update the name generator so that the given id's are not generated again *) +let update_name_generator ids = + let upd id = ignore (create_with_stamp id.kind id.name id.stamp) in + list_iter upd ids + +(** Create a fresh identifier with the given kind and name. *) +let create_fresh_ident kind name = + let stamp = + try + let stamp = NameHash.find name_map name in + NameHash.replace name_map name (stamp + 1); + stamp + 1 + with Not_found -> + NameHash.add name_map name 0; + 0 in + { kind = kind; name = name; stamp = stamp } + +(** Create a fresh identifier with default name for the given kind. *) +let create_fresh kind = + create_fresh_ident kind (standard_name kind) + +(** Generate a normal identifier whose name encodes a path given as a string. *) +let create_path pathstring = + create_normal (string_to_name ("%path%" ^ pathstring)) path_ident_stamp + +(** {2 Pretty Printing} *) + +(** Convert an identifier to a string. *) +let to_string id = + let base_name = name_to_string id.name in + let prefix = + if id.kind == kfootprint then "@" + else if id.kind == knormal then "" + else "_" in + let suffix = "$" ^ (string_of_int id.stamp) + in prefix ^ base_name ^ suffix + +(** Pretty print a name. *) +let pp_name f name = + F.fprintf f "%s" (name_to_string name) + +let pp_fieldname f fn = + (* only use for debug F.fprintf f "%a#%d" pp_name fn.fname fn.fpos *) + Mangled.pp f fn.fname + +(** Pretty print a name in latex. *) +let pp_name_latex style f (name: name) = + Latex.pp_string style f (name_to_string name) + +let pp_fieldname_latex style f fn = + Latex.pp_string style f (Mangled.to_string fn.fname) + +(** Pretty print an identifier. *) +let pp pe f id = match pe.pe_kind with + | PP_TEXT | PP_HTML -> + F.fprintf f "%s" (to_string id) + | PP_LATEX -> + let base_name = name_to_string id.name in + let style = + if id.kind = kfootprint then Latex.Boldface + else if id.kind = knormal then Latex.Roman + else Latex.Roman in + F.fprintf f "%a_{%s}" (Latex.pp_string style) base_name (string_of_int id.stamp) + +(** pretty printer for lists of identifiers *) +let pp_list pe = pp_comma_seq (pp pe) + +(** pretty printer for lists of names *) +let pp_name_list = pp_comma_seq pp_name diff --git a/infer/src/backend/ident.mli b/infer/src/backend/ident.mli new file mode 100644 index 000000000..ba2c51ee5 --- /dev/null +++ b/infer/src/backend/ident.mli @@ -0,0 +1,194 @@ +(* +* Copyright (c) 2009-2013 Monoidics ltd. +* Copyright (c) 2013- Facebook. +* All rights reserved. +*) + +(** Identifiers: program variables and logical variables *) + +open Utils + +(** Program and logical variables. *) +type t + +(** Names used to replace strings. *) +type name + +(** Names for fields of class/struct/union *) +type fieldname + +(** Kind of identifiers. *) +type kind + +(** Set for identifiers. *) +module IdentSet : Set.S with type elt = t + +(** Hash table with ident as key. *) +module IdentHash : Hashtbl.S with type key = t + +(** Set for fieldnames *) +module FieldSet : Set.S with type elt = fieldname + +(** Map for fieldnames *) +module FieldMap : Map.S with type key = fieldname + +(** Convert an identfier list to an identifier set *) +val idlist_to_idset : t list -> IdentSet.t + +val kprimed : kind +val knormal : kind +val kfootprint : kind + +(** hash table with names as keys *) +module NameHash : Hashtbl.S with type key = name + +(** Name used for primed tmp variables *) +val name_primed : name + +(** Name used for spec variables *) +val name_spec : name + +(** Name used for the return variable *) +val name_return : Mangled.t + +(** Convert a string to a name. *) +val string_to_name : string -> name + +(** Create a field name at the given position *) +val create_fieldname : Mangled.t -> int -> fieldname + +(** Convert a name to a string. *) +val name_to_string : name -> string + +(** Convert a field name to a string. *) +val fieldname_to_string : fieldname -> string + +(** Convert a fieldname to a simplified string with at most one-level path. *) +val fieldname_to_simplified_string : fieldname -> string + +(** Convert a fieldname to a flat string without path. *) +val fieldname_to_flat_string : fieldname -> string + +(** The class part of the fieldname *) +val java_fieldname_get_class : fieldname -> string + +(** The last component of the fieldname *) +val java_fieldname_get_field : fieldname -> string + +(** Check if the field is the synthetic this$n of a nested class, used to access the n-th outher instance. *) +val java_fieldname_is_outer_instance : fieldname -> bool + +(** get the offset of a fieldname *) +val fieldname_offset : fieldname -> int + +(** hidded fieldname constant *) +val fieldname_hidden : fieldname + +(** hidded fieldname constant *) +val fieldname_is_hidden : fieldname -> bool + +(** Name of the identifier. *) +val get_name : t -> name + +(** Kind of the identifier. *) +val get_kind : t -> kind + +(** Create an identifier with default name for the given kind *) +val create : kind -> int -> t + +(** Generate a normal identifier with the given name and stamp. *) +val create_normal : name -> int -> t + +(** Generate a primed identifier with the given name and stamp. *) +val create_primed : name -> int -> t + +(** Generate a footprint identifier with the given name and stamp. *) +val create_footprint : name -> int -> t + +(** Reset the name generator *) +val reset_name_generator : unit -> unit + +(** Update the name generator so that the given id's are not generated again *) +val update_name_generator : t list -> unit + +(** Create a fresh identifier with default name for the given kind. *) +val create_fresh : kind -> t + +(** Generate a normal identifier whose name encodes a path given as a string. *) +val create_path : string -> t + +(** Check whether an identifier is primed or not. *) +val is_primed : t -> bool + +(** Check whether an identifier is normal or not. *) +val is_normal : t -> bool + +(** Check whether an identifier is footprint or not. *) +val is_footprint : t -> bool + +(** Check whether an identifier represents a path or not. *) +val is_path : t -> bool + +(** Convert a primed ident into a nonprimed one, keeping the stamp. *) +val make_unprimed : t -> t + +(** Get the stamp of the identifier *) +val get_stamp: t -> int + +(** Set the stamp of the identifier *) +val set_stamp: t -> int -> t + +(** {2 Comparision Functions} *) + +(** Comparison for names. *) +val name_compare : name -> name -> int + +(** Comparison for field names. *) +val fieldname_compare : fieldname -> fieldname -> int + +(** Equality for names. *) +val name_equal : name -> name -> bool + +(** Equality for field names. *) +val fieldname_equal : fieldname -> fieldname -> bool + +(** Equality for kind. *) +val kind_equal : kind -> kind -> bool + +(** Comparison for identifiers. *) +val compare : t -> t -> int + +(** Equality for identifiers. *) +val equal : t -> t -> bool + +(** Comparison for lists of identities *) +val ident_list_compare : t list -> t list -> int + +(** Equality for lists of identities *) +val ident_list_equal : t list -> t list -> bool + +(** {2 Pretty Printing} *) + +(** Pretty print a name. *) +val pp_name : Format.formatter -> name -> unit + +(** Pretty print a field name. *) +val pp_fieldname : Format.formatter -> fieldname -> unit + +(** Pretty print a name in latex. *) +val pp_name_latex : Latex.style -> Format.formatter -> name -> unit + +(** Pretty print a field name in latex. *) +val pp_fieldname_latex : Latex.style -> Format.formatter -> fieldname -> unit + +(** Pretty print an identifier. *) +val pp : printenv -> Format.formatter -> t -> unit + +(** Convert an identifier to a string. *) +val to_string : t -> string + +(** Pretty print a list of identifiers. *) +val pp_list : printenv -> Format.formatter -> t list -> unit + +(** Pretty print a list of names. *) +val pp_name_list : Format.formatter -> name list -> unit diff --git a/infer/src/backend/inferanalyze.ml b/infer/src/backend/inferanalyze.ml new file mode 100644 index 000000000..e151d2a2a --- /dev/null +++ b/infer/src/backend/inferanalyze.ml @@ -0,0 +1,774 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +(** Main module for the analysis after the capture phase *) + +module L = Logging +module F = Format +open Utils + +(* This module, unused by default, generates random c files with procedure calls *) +module Codegen = struct + let num_files = 5000 + let num_functions = 1000 + let calls_per_fun = 5 + let fun_nr = ref 0 + + let gen () = + for file_nr = 1 to num_files do + let fname = Printf.sprintf "file%04d.c" file_nr in + let fmt = open_out fname in + for i = 1 to num_functions do + incr fun_nr; + let num_calls = if !fun_nr = 1 then 0 else Random.int calls_per_fun in + Printf.fprintf fmt "void f%04d() {\n" !fun_nr; + for call_nr = 1 to num_calls do + let callee_nr = 1 + Random.int (max 1 (num_calls - 1)) in + Printf.fprintf fmt "f%04d();\n" callee_nr + done; + Printf.fprintf fmt "}\n"; + done; + close_out fmt + done + + let dont () = + gen (); + exit 0 +end + +(** if true, print tracing information for functions that manipulate clusters *) +let trace_clusters = ref false + +(** if true, save file dependency graph to disk *) +let save_file_dependency = ref false + +type incremental = + | ANALYZE_ALL (** analyze all the files in the results dir *) + | ANALYZE_CHANGED_AND_DEPENDENCIES (** analyze the files that have changed, as well as those dependent on them *) + | ANALYZE_CHANGED_ONLY (** only analyze the files that have changed *) + +let incremental_mode = ref ANALYZE_ALL + +(** command line option: if true, simulate the analysis only *) +let simulate = ref false + +(** command line option: if true, run the analysis in checker mode *) +let checkers = ref false + +(** command line option: name of the makefile to create with clusters and dependencies *) +let makefile_cmdline = ref "" + +(** Command line option: only consider procedures whose name begins with the given string. *) +let select_proc = ref None + +(** if not empty, only analyze the files in the list provided from the command-line *) +let only_files_cmdline = ref [] + +(** optional command-line name of the .cluster file *) +let cluster_cmdline = ref None + +(** value of -out_file command-line *) +let out_file_cmdline = ref "" + +(** value of -err_file command-line *) +let err_file_cmdline = ref "" + +(** Files excluded from the analysis *) +let excluded_files : string list ref = ref [] + +(** Absolute path to the project source, used for relative paths in the exclude list *) +let source_path = ref "" + +(** List of obj memory leak buckets to be checked in objc *) +let objc_ml_buckets_arg = ref "cf" + +(** Whether specs can be cleaned up before starting analysis *) +let allow_specs_cleanup = ref false + +(** Compute the exclude function from excluded_files and source_path. +The exclude function builds an exclude list of file path prefixes, and checks if one +of them is a prefix of the given source file. +Prefixes are obtained by prepending source_path, if any, to relative paths in excluded_fies *) +let compute_exclude_fun () : DB.source_file -> bool = + let prepend_source_path s = + if Filename.is_relative s then Filename.concat !source_path s + else s in + let excluded_list = list_map (fun file_path -> prepend_source_path file_path) !excluded_files in + let exclude_fun (source_file : DB.source_file) = + list_exists (fun excluded_path -> string_is_prefix excluded_path (DB.source_file_to_string source_file)) excluded_list in + exclude_fun + +let version_string () = + "Infer version " ^ Version.versionString ^ "\nCopyright 2009 - present Facebook. All Rights Reserved.\n" + +let print_version () = + F.fprintf F.std_formatter "%s@." (version_string ()); + exit 0 + +let print_version_json () = + F.fprintf F.std_formatter "%s@." Version.versionJson; + exit 0 + +let arg_desc = + let analyzer_mode s = Facebook.analyzer_mode s in + let base_arg = + let exclude s = match read_file s with + | None -> + F.fprintf F.std_formatter "ERROR: cannot read the exclude file %s@." s; + exit 1 + | Some files -> excluded_files := files in + let source_path s = + if Filename.is_relative s then + begin + F.fprintf F.std_formatter "ERROR: source_path must be an absolute path: %s @." s; + exit 1 + end; + source_path := s in + let desc = + base_arg_desc @ + [ + "-err_file", Arg.Set_string err_file_cmdline, Some "file", "use file for the err channel"; + "-exclude", Arg.String exclude, Some "file", "exclude from analysis the files and directories specified in file"; + "-incremental_ignore_dependencies", Arg.Unit (fun () -> incremental_mode := ANALYZE_CHANGED_ONLY), None, "only analyze files captured since the last analysis"; + "-incremental", Arg.Unit (fun () -> incremental_mode := ANALYZE_CHANGED_AND_DEPENDENCIES), None, "analyze files captured since the last analysis plus any dependencies"; + "-iterations", Arg.Set_int iterations_cmdline, Some "n", "set the max number of operations for each function, expressed as a multiple of symbolic operations (default n=1)"; + "-nonstop", Arg.Set Config.nonstop, None, "activate the nonstop mode: the analysis continues after finding errors. With this option the analysis can become less precise."; + "-out_file", Arg.Set_string out_file_cmdline, Some "file", "use file for the out channel"; + "-print_builtins", Arg.Unit SymExec.print_builtins, None, "print the builtin functions and exit"; + "-source_path", Arg.String source_path, Some "path", "specify the absolute path to the root of the source files. Used to interpret relative paths when using option -exclude."; + (* TODO: merge with the -project_root option *) + "-java", Arg.Unit (fun () -> Sil.curr_language := Sil.Java), None, "Set language to Java"; + "-version", Arg.Unit print_version, None, "print version information and exit"; + "-version_json", Arg.Unit print_version_json, None, "print version json formatted"; + "-objcm", Arg.Set Config.objc_memory_model_on, None, "Use ObjC memory model"; + "-objc_ml_buckets", Arg.Set_string objc_ml_buckets_arg, Some "objc_ml_buckets", + "memory leak buckets to be checked, separated by commas. The possible buckets are cf (Core Foundation), arc, narc (No arc)"; + ] in + Arg2.create_options_desc false "Analysis Options" desc in + let reserved_arg = + let desc = + reserved_arg_desc @ + [ + "-analysis_stops", Arg.Set Config.analysis_stops, None, "issue a warning when the analysis stops"; + "-angelic_execution", Arg.Set Config.angelic_execution, None, "activate angelic execution: the analysis ignores errors caused by unknown procedure calls."; + "-checkers", Arg.Set checkers, None, " run only the checkers instead of the full analysis"; + "-cluster", Arg.String (fun s -> cluster_cmdline := Some s), Some "fname", "specify a .cluster file to be analyzed"; + "-codequery", Arg.String (fun s -> CodeQuery.query := Some s), Some "query", " execute the code query"; + "-eradicate", Arg.Set Config.eradicate, None, " activate the eradicate checker for java annotations"; + "-file", Arg.String (fun s -> only_files_cmdline := s :: !only_files_cmdline), Some "fname", "specify one file to be analyzed (without path); the option can be repeated"; + "-intraprocedural", Arg.Set Config.intraprocedural, None, "perform an intraprocedural analysis only"; + "-makefile", Arg.Set_string makefile_cmdline, Some "file", "create a makefile to perform the analysis"; + "-max_cluster", Arg.Set_int Config.max_cluster_size, Some "n", "set the max number of procedures in each cluster (default n=2000)"; + "-analyzer_mode", Arg.String analyzer_mode, Some "mode", " run the analysis in a specific mode"; + "-only_nospecs", Arg.Set Config.only_nospecs, None, " only analyze procedures which were analyzed before but have no specs"; + "-only_skips", Arg.Set Config.only_skips, None, " only analyze procedures dependent on previous skips which now have a .specs file"; + "-seconds_per_iteration", Arg.Set_int seconds_per_iteration, Some "n", "set the number of seconds per iteration (default n=30)"; + "-simulate", Arg.Set simulate, None, " run a simulation of the analysis only"; + "-subtype_multirange", Arg.Set Config.subtype_multirange, None, "use the multirange subtyping domain"; + "-optimistic_cast", Arg.Set Config.optimistic_cast, None, "allow cast of undefined values"; + "-select_proc", Arg.String (fun s -> select_proc := Some s), Some "string", "only consider procedures whose name contains the given string"; + "-symops_per_iteration", Arg.Set_int symops_per_iteration, Some "n", "set the number of symbolic operations per iteration (default n="^(string_of_int !symops_per_iteration)^")"; + "-type_size", Arg.Set Config.type_size, None, "consider the size of types during analysis"; + "-tracing", Arg.Unit (fun () -> Config.report_runtime_exceptions := true), None, + "Report error traces for runtime exceptions (Only for Java)"; + "-allow_specs_cleanup", Arg.Unit (fun () -> allow_specs_cleanup := true), None, + "Allow to remove existing specs before running analysis when it's not incremental" + ] in + Arg2.create_options_desc false "Reserved Options: Experimental features, use with caution!" desc in + base_arg @ reserved_arg + +let usage = + (version_string ()) ^ + "\nUsage: InferAnalyze [options]\n" ^ + " Analyze the files captured in the project results directory, which can be specified with the -results_dir option." + +let print_usage_exit () = + Arg2.usage arg_desc usage; + exit(1) + +let () = (* parse command-line arguments *) + let f arg = + () (* ignore anonymous arguments *) in + Arg2.parse arg_desc f usage; + if not (Sys.file_exists !Config.results_dir) then + begin + L.err "ERROR: results directory %s does not exist@.@." !Config.results_dir; + print_usage_exit () + end + +module Simulator = struct (** Simulate the analysis only *) + let reset_summaries cg = + list_iter + (fun (pname, in_out_calls) -> Specs.reset_summary cg pname Sil.loc_none) + (Cg.get_nodes_and_calls cg) + + (** Perform phase transition from [FOOTPRINT] to [RE_EXECUTION] for + the procedures enabled after the analysis of [proc_name] *) + let perform_transition exe_env proc_name = + let proc_names = Fork.should_perform_transition (Exe_env.get_cg exe_env) proc_name in + let f proc_name = + let joined_pres = [] in + Fork.transition_footprint_re_exe proc_name joined_pres in + list_iter f proc_names + + let process_result (exe_env: Exe_env.t) ((proc_name: Procname.t), (calls: Cg.in_out_calls)) (_summ: Specs.summary) : unit = + L.err "in process_result %a@." Procname.pp proc_name; + let summ = { _summ with Specs.status = Specs.INACTIVE; Specs.stats = { _summ.Specs.stats with Specs.stats_calls = calls }} in + Specs.add_summary proc_name summ; + perform_transition exe_env proc_name; + let procs_done = Fork.procs_become_done (Exe_env.get_cg exe_env) proc_name in + Fork.post_process_procs exe_env procs_done + + let analyze_proc exe_env tenv proc_name = + L.err "in analyze_proc %a@." Procname.pp proc_name; + (* for i = 1 to Random.int 1000000 do () done; *) + let prev_summary = Specs.get_summary_unsafe proc_name in + let timestamp = max 1 (prev_summary.Specs.timestamp) in + { prev_summary with Specs.timestamp = timestamp } + + let filter_out cg proc_name = false +end + +let analyze exe_env = + let init_time = Unix.gettimeofday () in + Specs.clear_spec_tbl (); + Random.self_init (); + let line_reader = Printer.LineReader.create () in + if !checkers then (* run the checkers only *) + begin + let call_graph = Exe_env.get_cg exe_env in + Callbacks.iterate_callbacks Checkers.ST.store_summary call_graph exe_env + end + else if !simulate then (* simulate the analysis *) + begin + Simulator.reset_summaries (Exe_env.get_cg exe_env); + Fork.parallel_iter_nodes exe_env (Simulator.analyze_proc exe_env) Simulator.process_result Simulator.filter_out + end + else (* full analysis *) + begin + Interproc.do_analysis exe_env; + Printer.c_files_write_html line_reader exe_env; + Interproc.print_stats exe_env; + let elapsed = Unix.gettimeofday () -. init_time in + L.out "Interprocedural footprint analysis terminated in %f sec@." elapsed + end + +(** add [x] to list [l] at position [nth] *) +let list_add_nth x l nth = + let rec add acc todo nth = + if nth = 0 then list_rev_append acc (x:: todo) + else match todo with + | [] -> raise Not_found + | y:: todo' -> add (y:: acc) todo' (nth - 1) in + add [] l nth + +(** sort a list weakly w.r.t. a compare function which doest not have to be a total order +the number returned by [compare x y] indicates 'how strongly' x should come before y *) +let weak_sort compare list = + let weak_add l x = + let length = list_length l in + let fitness = Array.make (length + 1) 0 in + list_iter (fun y -> fitness.(0) <- fitness.(0) + compare x y) l; + let best_position = ref 0 in + let best_value = ref (fitness.(0)) in + let i = ref 0 in + list_iter (fun y -> + incr i; + let new_value = fitness.(!i - 1) - (compare x y) + (compare y x) in + fitness.(!i) <- new_value; + if new_value < !best_value then + begin + best_value := new_value; + best_position := !i + end) + l; + list_add_nth x l !best_position in + list_fold_left weak_add [] list + +let pp_stringlist fmt slist = + list_iter (fun pname -> F.fprintf fmt "%s " pname) slist + +let weak_sort_nodes cg = + let nodes = Cg.get_defined_nodes cg in + let grand_hash = Procname.Hash.create 1 in + let get_grandparents x = + try Procname.Hash.find grand_hash x with + | Not_found -> + let res = ref Procname.Set.empty in + let do_parent p = res := Procname.Set.union (Cg.get_parents cg p) !res in + Procname.Set.iter do_parent (Cg.get_parents cg x); + Procname.Hash.replace grand_hash x !res; + !res in + let cmp x y = + let res = ref 0 in + if Procname.Set.mem y (Cg.get_parents cg x) then res := !res - 2; + if Procname.Set.mem y (get_grandparents x) then res := !res - 1; + if Procname.Set.mem x (Cg.get_parents cg y) then res := !res + 2; + if Procname.Set.mem x (get_grandparents y) then res := !res + 1; + !res in + weak_sort cmp nodes + +(** cluster element: the file name, the number of procedures defined in it, and the list of active procedures +A procedure is active if it is defined only in this file, or if it is defined in several files and this +is the representative file for it (see Exe_env.add_cg) *) +type cluster_elem = + { ce_file : DB.source_file; + ce_naprocs : int; (** number of active procedures defined in the file *) + ce_active_procs : Procname.t list; (** list of active procedures *) + ce_source_map : DB.source_file Procname.Map.t; (** map from undefined procedures to the file where they are defined *) } + +(** cluster of files *) +type cluster = + cluster_elem list + +(** .cluster file: (n,m,cl) indicates cl is cluster n out of m *) +type cluster_serial = int * int * cluster + +(** Serializer for clusters *) +let cluster_serializer : cluster_serial Serialization.serializer = Serialization.create_serializer Serialization.cluster_key + +(** Load a cluster from a file *) +let load_cluster_from_file (filename : DB.filename) : cluster_serial option = + Serialization.from_file cluster_serializer filename + +(** Save a cluster into a file *) +let store_cluster_to_file (filename : DB.filename) (cluster_serial: cluster_serial) = + Serialization.to_file cluster_serializer filename cluster_serial + +(* this relies on the assumption that a source_file can be converted to a string, then pname, then back *) +let source_file_from_pname pname = + DB.source_file_from_string (Procname.to_string pname) + +let source_file_to_pname fname = + Procname.from_string (DB.source_file_to_string fname) + +(** create clusters of minimal size in the dependence order, with recursive parts grouped together *) +let create_minimal_clusters file_cg exe_env (only_analyze : Procname.Set.t option) : cluster list = + if !trace_clusters then L.err "[create_minimal_clusters]@."; + let sorted_files = weak_sort_nodes file_cg in + let seen = ref Procname.Set.empty in + let clusters = ref [] in + let create_cluster_elem pname = (* create a cluster_elem for the file *) + let source_file = source_file_from_pname pname in + if !trace_clusters then L.err " [create_cluster_elem] %s@." (DB.source_file_to_string source_file); + DB.current_source := source_file; + let source_dir = DB.source_dir_from_source_file source_file in + let cg_fname = DB.source_dir_get_internal_file source_dir ".cg" in + match Cg.load_from_file cg_fname with + | None -> { ce_file = source_file; ce_naprocs = 0; ce_active_procs = []; ce_source_map = Procname.Map.empty } + | Some cg -> + (* decide whether a proc is active using pname_to_fname, i.e. whether this is the file associated to it *) + let proc_is_selected pname = match !select_proc with + | None -> true + | Some pattern_str -> string_is_prefix pattern_str (Procname.to_unique_id pname) in + let proc_is_active pname = + proc_is_selected pname && + DB.source_file_equal (Exe_env.get_source exe_env pname) source_file in + let defined_procs = Cg.get_defined_nodes cg in + let active_procs = list_filter proc_is_active defined_procs in + let naprocs = list_length active_procs in + let source_map = + let all_procs, _ = Cg.get_nodes_and_edges cg in + let mapr = ref Procname.Map.empty in + let do_proc (pn, isdefined) = + if not isdefined then + try + let source = Exe_env.get_source exe_env pn in + mapr := Procname.Map.add pn source !mapr; + with Not_found -> () in + list_iter do_proc all_procs; + !mapr in + { ce_file = source_file; ce_naprocs = naprocs; ce_active_procs = active_procs; ce_source_map = source_map } in + let choose_next_file list = (* choose next file from the weakly ordered list *) + let file_has_no_unseen_dependents fname = + Procname.Set.subset (Cg.get_dependents file_cg fname) !seen in + match list_partition file_has_no_unseen_dependents list with + | (fname :: no_deps), deps -> (* if all the dependents of fname have been seen, bypass the order in the list *) + if !trace_clusters then L.err " [choose_next_file] %s (NO dependents)@." (Procname.to_string fname); + Some (fname, list_rev_append no_deps deps) + | [], _ -> + begin + match list with + | fname :: list' -> + if !trace_clusters then L.err " [choose_next_file] %s (HAS dependents)@." (Procname.to_string fname); + Some(fname, list') + | [] -> None + end in + let rec build_clusters list = match choose_next_file list with + | None -> () + | Some (fname, list') -> + if !trace_clusters then L.err " [build_clusters] %s@." (Procname.to_string fname); + if Procname.Set.mem fname !seen then build_clusters list' + else + let cluster_set = Procname.Set.add fname (Cg.get_recursive_dependents file_cg fname) in + let cluster, list'' = list_partition (fun node -> Procname.Set.mem node cluster_set) list in + seen := Procname.Set.union !seen cluster_set; + let files_to_analyze = list_filter (fun node -> + match only_analyze with + | None -> true + | Some files_to_analyze -> Procname.Set.mem node files_to_analyze) cluster in + if files_to_analyze <> [] then + begin + let cluster = list_map create_cluster_elem files_to_analyze in + clusters := cluster :: !clusters; + end; + build_clusters list'' in + build_clusters sorted_files; + list_rev !clusters + +let cluster_nfiles cluster = list_length cluster + +let cluster_naprocs cluster = list_fold_left (fun n ce -> ce.ce_naprocs + n) 0 cluster + +let clusters_nfiles clusters = list_fold_left (fun n cluster -> cluster_nfiles cluster + n) 0 clusters + +let clusters_naprocs clusters = list_fold_left (fun n cluster -> cluster_naprocs cluster + n) 0 clusters + +let print_clusters_stats clusters = + let i = ref 0 in + list_iter (fun cluster -> incr i; L.err "cluster #%d files: %d active procedures: %d@." !i (cluster_nfiles cluster) (cluster_naprocs cluster)) clusters + +let cluster_split_prefix (cluster : cluster) size = + let rec split (cluster_seen : cluster) (cluster_todo : cluster) n = + if n <= 0 then (list_rev cluster_seen, cluster_todo) + else match cluster_todo with + | [] -> raise Not_found + | ce :: todo' -> split (ce :: cluster_seen) todo' (n - ce.ce_naprocs) in + split [] cluster size + +let combine_split_clusters (clusters : cluster list) max_size desired_size = + if !trace_clusters then L.err "[combine_split_clusters]@."; + let old_clusters = ref clusters in + let old_size = clusters_naprocs !old_clusters in + let new_clusters = ref [] in + let current = ref [] in + let current_size = ref 0 in + while !old_clusters != [] do + if old_size != clusters_naprocs !old_clusters + clusters_naprocs !new_clusters + !current_size then begin + L.err "mismatch in invariant for cluster size@."; + assert (cluster_naprocs !current = !current_size); + L.err "old size: %d@." old_size; + L.err "old clusters size: %d@." (clusters_naprocs !old_clusters); + L.err "new clusters size: %d@." (clusters_naprocs !new_clusters); + L.err "current size: %d@." !current_size; + assert false + end; + let next_cluster = list_hd !old_clusters in + let next_size = cluster_naprocs next_cluster in + let new_size = !current_size + next_size in + if (new_size > max_size || new_size > desired_size) && !current_size > 0 then + begin + new_clusters := !new_clusters @ [!current]; + current := []; + current_size := 0 + end + else if new_size > max_size then + begin + let next_cluster', next_cluster'' = cluster_split_prefix next_cluster max_size in + current := []; + current_size := 0; + new_clusters := !new_clusters @ [next_cluster']; + old_clusters := next_cluster'' :: (list_tl !old_clusters) + end + else + begin + current := !current @ next_cluster; + current_size := !current_size + next_size; + old_clusters := list_tl !old_clusters + end + done; + if !current_size > 0 then new_clusters := !new_clusters @ [!current]; + !new_clusters + +(** return the set of active procedures in a cluster *) +let cluster_to_active_procs cluster = + let procset = ref Procname.Set.empty in + let do_cluster_elem cluster_elem = + let add proc = if not (Procname.Set.mem proc !procset) then procset := Procname.Set.add proc !procset in + list_iter add cluster_elem.ce_active_procs in + list_iter do_cluster_elem cluster; + !procset + +(** Module to create a makefile with dependencies between clusters *) +module ClusterMakefile = struct + let cl_name n = "cl" ^ string_of_int n + let cl_file n = "x" ^ (cl_name n) ^ ".cluster" + + let pp_cl fmt n = Format.fprintf fmt "%s" (cl_name n) + + let pp_prolog fmt num_clusters = + F.fprintf fmt "INFERANALYZE= %s $(INFER_OPTIONS) -results_dir '%s'\n@." + Sys.executable_name + (Escape.escape_map (fun c -> if c = '#' then Some "\\#" else None) !Config.results_dir); + F.fprintf fmt "OBJECTS="; + for i = 1 to num_clusters do F.fprintf fmt "%a " pp_cl i done; + F.fprintf fmt "@.@.default: test@.@.all: test@.@."; + F.fprintf fmt "test: $(OBJECTS)@.\techo \"Analysis done\"@.@." + + let pp_epilog fmt () = + F.fprintf fmt "@.clean:@.\trm -f $(OBJECTS)@." + + let pp_cluster_dependency nr tot_nr cluster print_files fmt dependents = + let fname = cl_file nr in + store_cluster_to_file (DB.filename_from_string fname) (nr, tot_nr, cluster); + let pp_active_procs fmt cluster = + let procnames = Procname.Set.elements (cluster_to_active_procs cluster) in + let pp_pname fmt pname = Format.fprintf fmt "%s" (Procname.to_string pname) in + F.fprintf fmt "procedures: %a" (pp_seq pp_pname) procnames in + let pp_file fmt ce = F.fprintf fmt "%s" (DB.source_file_to_string ce.ce_file) in + let pp_files fmt cluster = F.fprintf fmt "files: %a" (pp_seq pp_file) cluster in + F.fprintf fmt "%a : %a@\n" pp_cl nr (pp_seq pp_cl) dependents; + F.fprintf fmt "\t$(INFERANALYZE) -cluster %s >%a@\n" fname pp_cl nr; + if print_files then F.fprintf fmt "# %a %a" pp_files cluster pp_active_procs cluster; + F.fprintf fmt "@\n" + + let create_cluster_makefile_and_exit (clusters: cluster list) (file_cg: Cg.t) (fname: string) (print_files: bool) = + let outc = open_out fname in + let fmt = Format.formatter_of_out_channel outc in + let file_to_cluster = ref DB.SourceFileMap.empty in + let cluster_nr = ref 0 in + let tot_clusters_nr = list_length clusters in + let do_cluster cluster = + incr cluster_nr; + let dependent_clusters = ref IntSet.empty in + let add_dependent file_as_pname = + let source_file = source_file_from_pname file_as_pname in + try + let num = DB.SourceFileMap.find source_file !file_to_cluster in + if num < !cluster_nr then + dependent_clusters := IntSet.add num !dependent_clusters + with Not_found -> F.fprintf fmt "#[%a] missing dependency to %s@." pp_cl !cluster_nr (DB.source_file_to_string source_file) in + let do_file ce = + let source_file = ce.ce_file in + let children = Cg.get_defined_children file_cg (source_file_to_pname source_file) in + Procname.Set.iter add_dependent children; + () (* L.err "file %s has %d children@." file (StringSet.cardinal children) *) in + list_iter (fun ce -> file_to_cluster := DB.SourceFileMap.add ce.ce_file !cluster_nr !file_to_cluster) cluster; + list_iter do_file cluster; + pp_cluster_dependency !cluster_nr tot_clusters_nr cluster print_files fmt (IntSet.elements !dependent_clusters); + (* L.err "cluster %d has %d dependencies@." !cluster_nr (IntSet.cardinal !dependent_clusters) *) in + pp_prolog fmt tot_clusters_nr; + list_iter do_cluster clusters; + pp_epilog fmt (); + exit 0 +end + +(** compute the clusters *) +let compute_clusters exe_env (files_changed : Procname.Set.t) : cluster list = + if !trace_clusters then L.err "[compute_clusters] %d changed files@." (Procname.Set.cardinal files_changed); + let file_cg = Cg.create () in + let global_cg = Exe_env.get_cg exe_env in + let nodes, edges = Cg.get_nodes_and_edges global_cg in + let defined_procs = Cg.get_defined_nodes global_cg in + let do_node (n, defined) = + if defined then Cg.add_node file_cg (source_file_to_pname (Exe_env.get_source exe_env n)) in + let do_edge (n1, n2) = + if Cg.node_defined global_cg n1 && Cg.node_defined global_cg n2 then + begin + let src1 = Exe_env.get_source exe_env n1 in + let src2 = Exe_env.get_source exe_env n2 in + if not (DB.source_file_equal src1 src2) then begin + if !trace_clusters then L.err "file_cg %s -> %s [%a]@." (DB.source_file_to_string src1) (DB.source_file_to_string src2) Procname.pp n2; + Cg.add_edge file_cg (source_file_to_pname src1) (source_file_to_pname src2) + end + end in + list_iter do_node nodes; + if !Config.intraprocedural = false then list_iter do_edge edges; + if !save_file_dependency then + Cg.save_call_graph_dotty (Some (DB.filename_from_string "file_dependency.dot")) Specs.get_specs file_cg; + let files = Cg.get_defined_nodes file_cg in + let num_files = list_length files in + L.err "@.Found %d defined procedures in %d files.@." (list_length defined_procs) num_files; + let files_changed_and_dependents = ref files_changed in + if !incremental_mode != ANALYZE_ALL then + begin + Procname.Set.iter (fun c_file -> + let ancestors = + try Cg.get_ancestors file_cg c_file with + | Not_found -> + L.err "Warning: modified file %s is ignored, all its functions might be already defined in another file@." (Procname.to_string c_file); + Procname.Set.empty in + files_changed_and_dependents := Procname.Set.union ancestors !files_changed_and_dependents) files_changed; + L.err "Number of files changed since the last analysis: %d.@." (Procname.Set.cardinal files_changed) + end + else L.err ".@."; + let only_analyze = match !incremental_mode with + | ANALYZE_ALL -> None + | ANALYZE_CHANGED_AND_DEPENDENCIES -> Some !files_changed_and_dependents + | ANALYZE_CHANGED_ONLY -> Some files_changed in + let num_files_to_analyze = match only_analyze with + | None -> num_files + | Some set -> Procname.Set.cardinal set in + L.err "Analyzing %d files.@.@." num_files_to_analyze; + let clusters = create_minimal_clusters file_cg exe_env only_analyze in + L.err "Minimal clusters:@."; + print_clusters_stats clusters; + if !makefile_cmdline <> "" then + begin + let max_cluster_size = 50 in + let desired_cluster_size = 1 in + let clusters' = combine_split_clusters clusters max_cluster_size desired_cluster_size in + L.err "@.Combined clusters with max size %d@." max_cluster_size; + print_clusters_stats clusters'; + ClusterMakefile.create_cluster_makefile_and_exit clusters' file_cg !makefile_cmdline false + end; + let clusters' = combine_split_clusters clusters !Config.max_cluster_size !Config.max_cluster_size in + L.err "@.Combined clusters with max size %d@." !Config.max_cluster_size; + print_clusters_stats clusters'; + clusters' + +(** Check whether the cg file is changed. It is unchanged if for each defined procedure, the .specs +file exists and is more recent than the cg file. *) +let cg_check_changed exe_env source_dir cg = + let cg_fname = DB.source_dir_get_internal_file source_dir ".cg" in + let defined_nodes = Cg.get_defined_nodes cg in + let changed = ref false in + let cg_source_file = Cg.get_source cg in + let proc_is_active pname = DB.source_file_equal (Exe_env.get_source exe_env pname) cg_source_file in + let check_needs_update pname = + let is_active = proc_is_active pname in + let spec_fname = Specs.res_dir_specs_filename pname in + if is_active then + changed := (!changed || not (Sys.file_exists (DB.filename_to_string spec_fname)) || + DB.file_modified_time cg_fname > DB.file_modified_time spec_fname) in + list_iter check_needs_update defined_nodes; + !changed + +(** Load a .c or .cpp file into an execution environment *) +let load_cg_file (_exe_env: Exe_env.initial) (source_dir : DB.source_dir) exclude_fun = + match Exe_env.add_cg_exclude_fun _exe_env source_dir exclude_fun with + | None -> None + | Some cg -> + L.err "loaded %s@." (DB.source_dir_to_string source_dir); + Some cg + +(** Load a list of cg files and return the set of changed ones if [check_changed] is true *) +let load_cg_files _exe_env check_changed (source_dirs : DB.source_dir list) exclude_fun = + let sorted_dirs = list_sort DB.source_dir_compare source_dirs in + let files_changed = ref Procname.Set.empty in + let cg_list = ref [] in + let check_cgs_changed exe_env = + let check_cg_changed (source_dir, cg) = + let is_changed = cg_check_changed exe_env source_dir cg in + if is_changed then files_changed := + Procname.Set.add (source_file_to_pname (Cg.get_source cg)) !files_changed in + list_iter check_cg_changed !cg_list in + list_iter (fun source_dir -> + match load_cg_file _exe_env source_dir exclude_fun with + | None -> () + | Some cg -> + if check_changed then cg_list := (source_dir, cg) :: !cg_list) sorted_dirs; + let exe_env = Exe_env.freeze _exe_env in + if check_changed then check_cgs_changed exe_env; + !files_changed, exe_env + +(** Create an exe_env from a cluster. *) +let exe_env_from_cluster cluster = + let _exe_env = Exe_env.create (Some (cluster_to_active_procs cluster)) in + let exclude_fun _ = false in + let callees = ref [] in + let source_files = + let do_cluster_elem ce = + let source_map = ce.ce_source_map in + let do_callee pn file = + callees := (pn, file) :: !callees in + Procname.Map.iter do_callee source_map; + DB.source_dir_from_source_file ce.ce_file in + list_map do_cluster_elem cluster in + let _, exe_env = load_cg_files _exe_env false source_files exclude_fun in + let do_callee (pn, file) = + Exe_env.add_callee exe_env file pn in + list_iter do_callee !callees; + exe_env + +(** Analyze a cluster of files *) +let analyze_cluster cluster_num tot_clusters (cluster : cluster) = + incr cluster_num; + let exe_env = exe_env_from_cluster cluster in + let num_files = list_length cluster in + let defined_procs = Cg.get_defined_nodes (Exe_env.get_cg exe_env) in + let num_procs = list_length defined_procs in + L.err "@.Processing cluster #%d/%d with %d files and %d procedures@." !cluster_num tot_clusters num_files num_procs; + Fork.this_cluster_files := num_files; + analyze exe_env; + Fork.tot_files_done := num_files + !Fork.tot_files_done; + Fork.print_timing () + +let process_cluster_cmdline_exit () = + match !cluster_cmdline with + | None -> () + | Some fname -> + (match load_cluster_from_file (DB.filename_from_string fname) with + | None -> + L.err "Cannot find cluster file %s@." fname; + exit 0 + | Some (nr, tot_nr, cluster) -> + Fork.tot_files_done := (nr - 1) * list_length cluster; + Fork.tot_files := tot_nr * list_length cluster; + analyze_cluster (ref (nr -1)) tot_nr cluster; + exit 0) + +let open_output_file f fname = + try + let cout = open_out fname in + let fmt = Format.formatter_of_out_channel cout in + f fmt; + Some (fmt, cout) + with Sys_error _ -> + Format.fprintf Format.std_formatter "Error: cannot open output file %s@." fname; + exit(-1) + +let close_output_file = function + | None -> () + | Some (fmt, cout) -> close_out cout + +let log_dir_name = "log" +let analyzer_out_name = "analyzer_out" +let analyzer_err_name = "analyzer_err" + +let () = + let () = + match !cluster_cmdline with + | None -> L.stdout "Starting analysis@."; + | Some clname -> L.stdout "Cluster %s@." clname in + RegisterCheckers.register (); + Facebook.register_checkers (); + + if !allow_specs_cleanup = true && !incremental_mode = ANALYZE_ALL && !cluster_cmdline = None then + DB.Results_dir.clean_specs_dir (); + + let log_dir = DB.filename_to_string (DB.Results_dir.path_to_filename DB.Results_dir.Abs_root [log_dir_name]) in + DB.create_dir log_dir; + let analyzer_out_file = + if !out_file_cmdline = "" then Filename.concat log_dir analyzer_out_name + else !out_file_cmdline in + let analyzer_err_file = + if !err_file_cmdline = "" then Filename.concat log_dir analyzer_err_name + else !err_file_cmdline in + let analyzer_out_of = open_output_file Logging.set_out_formatter analyzer_out_file in + let analyzer_err_of = open_output_file Logging.set_err_formatter analyzer_err_file in + if (!Sil.curr_language = Sil.C_CPP) then Mleak_buckets.init_buckets !objc_ml_buckets_arg; + + process_cluster_cmdline_exit (); + let source_dirs = + if !only_files_cmdline = [] then DB.find_source_dirs () + else + let filter source_dir = + let source_dir_base = Filename.basename (DB.source_dir_to_string source_dir) in + list_exists (fun s -> Utils.string_is_prefix s source_dir_base) !only_files_cmdline in + list_filter filter (DB.find_source_dirs ()) in + L.err "Found %d source files in %s@." (list_length source_dirs) !Config.results_dir; + let _exe_env = Exe_env.create None in + let check_changed = !incremental_mode != ANALYZE_ALL in + let files_changed, exe_env = load_cg_files _exe_env check_changed source_dirs (compute_exclude_fun ()) in + L.err "Procedures defined in more than one file: %a" Procname.pp_set (Exe_env.get_procs_defined_in_several_files exe_env); + let clusters = compute_clusters exe_env files_changed in + let tot_clusters = list_length clusters in + Fork.tot_files := list_fold_left (fun n cluster -> n + list_length cluster) 0 clusters; + list_iter (analyze_cluster (ref 0) tot_clusters) clusters; + L.flush_streams (); + close_output_file analyzer_out_of; + close_output_file analyzer_err_of; + if !cluster_cmdline = None then L.stdout "Analysis finished in %as@." pp_elapsed_time () diff --git a/infer/src/backend/inferconfig.ml b/infer/src/backend/inferconfig.ml new file mode 100644 index 000000000..16fed9e12 --- /dev/null +++ b/infer/src/backend/inferconfig.ml @@ -0,0 +1,343 @@ +(* +* Copyright (c) 2015 - Facebook. +* All rights reserved. +*) + +open Utils + +(** Name of the infer configuration file *) +let inferconfig_file = ".inferconfig" + +(** Look up a key in a json file containing a list of strings *) +let lookup_string_list key json = + Yojson.Basic.Util.filter_member key [json] + |> Yojson.Basic.Util.flatten + |> Yojson.Basic.Util.filter_string + +type path_filter = DB.source_file -> bool +type error_filter = Localise.t -> bool + +type filters = + { + path_filter : path_filter; + error_filter: error_filter; + } + +let default_path_filter : path_filter = function path -> true +let default_error_filter : error_filter = function error_name -> true + +let do_not_filter : filters = + { + path_filter = default_path_filter; + error_filter = default_error_filter; + } + +type filter_config = + { + whitelist: string list; + blacklist: string list; + blacklist_files_containing : string list; + suppress_errors: string list; + } + +let load_filters analyzer = + try + let json = Yojson.Basic.from_file inferconfig_file in + let inferconfig = + { + whitelist = lookup_string_list (analyzer ^ "_whitelist") json; + blacklist = lookup_string_list (analyzer ^ "_blacklist") json; + blacklist_files_containing = + lookup_string_list (analyzer ^ "_blacklist_files_containing") json; + suppress_errors = lookup_string_list (analyzer ^ "_suppress_errors") json; + } in + Some inferconfig + with Sys_error _ -> None + + +let is_matching patterns = + fun source_file -> + let path = DB.source_file_to_rel_path source_file in + Utils.list_exists + (fun pattern -> + try + (Str.search_forward pattern path 0) = 0 + with Not_found -> false) + patterns + +module FileContainsStringMatcher = struct + type matcher = DB.source_file -> bool + + let default_matcher : matcher = fun fname -> false + + let file_contains regexp file_in = + let rec loop () = + try + (Str.search_forward regexp (input_line file_in) 0) >= 0 + with + | Not_found -> loop () + | End_of_file -> false in + loop () + + + let create_matcher s_patterns = + if s_patterns = [] then + default_matcher + else + let source_map = ref DB.SourceFileMap.empty in + let regexp = + Str.regexp (join_strings "\\|" s_patterns) in + fun source_file -> + try + DB.SourceFileMap.find source_file !source_map + with Not_found -> + let file_in = open_in (DB.source_file_to_string source_file) in + let pattern_found = file_contains regexp file_in in + close_in file_in; + source_map := DB.SourceFileMap.add source_file pattern_found !source_map; + pattern_found +end + + +let filters_from_inferconfig inferconfig : filters = + let path_filter = + let whitelist_filter : path_filter = + if inferconfig.whitelist = [] then default_path_filter + else is_matching (list_map Str.regexp inferconfig.whitelist) in + let blacklist_filter : path_filter = + is_matching (list_map Str.regexp inferconfig.blacklist) in + let blacklist_files_containing_filter : path_filter = + FileContainsStringMatcher.create_matcher inferconfig.blacklist_files_containing in + function source_file -> + whitelist_filter source_file && + not (blacklist_filter source_file) && + not (blacklist_files_containing_filter source_file) in + let error_filter = + function error_name -> + let error_str = Localise.to_string error_name in + not (list_exists (string_equal error_str) inferconfig.suppress_errors) in + { + path_filter = path_filter; + error_filter = error_filter; + } + +(* Create filters based on .inferconfig.*) +(* The environment varialble NO_PATH_FILTERING disables path filtering. *) +let create_filters analyzer = + Config.project_root := Some (Sys.getcwd ()); + if Config.from_env_variable "NO_PATH_FILTERING" then + do_not_filter + else + match load_filters (Utils.string_of_analyzer analyzer) with + | None -> do_not_filter + | Some inferconfig -> + filters_from_inferconfig inferconfig + +module NeverReturnNull = struct + + let never_returning_null_key = "never_returning_null" + + type matcher = DB.source_file -> Procname.t -> bool + + let default_matcher : matcher = + fun source_file proc_name -> false + + type method_pattern = { + class_name : string; + method_name : string option; + parameters : (string list) option + } + + let default_method_pattern = { + class_name = ""; + method_name = None; + parameters = None + } + + let default_source_contains = "" + + type pattern = + | Method_pattern of Sil.language * method_pattern + | Source_contains of Sil.language * string + + let language_of_string = function + | "Java" -> Sil.Java + | "C_CPP" -> Sil.C_CPP + | _ -> failwith ("Unknown language found in " ^ inferconfig_file) + + let pp_pattern fmt pattern = + let pp_string fmt s = + Format.fprintf fmt "%s" s in + let pp_option pp_value fmt = function + | None -> pp_string fmt "None" + | Some value -> Format.fprintf fmt "%a" pp_value value in + let pp_key_value pp_value fmt (key, value) = + Format.fprintf fmt " %s: %a,\n" key (pp_option pp_value) value in + let pp_method_pattern fmt mp = + let pp_params fmt l = + Format.fprintf fmt "[%a]" + (pp_semicolon_seq_oneline pe_text pp_string) l in + Format.fprintf fmt "%a%a%a" + (pp_key_value pp_string) ("class", Some mp.class_name) + (pp_key_value pp_string) ("method", mp.method_name) + (pp_key_value pp_params) ("parameters", mp.parameters) + and pp_source_contains fmt sc = + Format.fprintf fmt " pattern: %s\n" sc in + match pattern with + | Method_pattern (language, mp) -> + Format.fprintf fmt "Method pattern (%s) {\n%a}\n" + (Sil.string_of_language language) pp_method_pattern mp + | Source_contains (language, sc) -> + Format.fprintf fmt "Source contains (%s) {\n%a}\n" + (Sil.string_of_language language) pp_source_contains sc + + + let detect_language assoc = + let rec loop = function + | [] -> + failwith + ("No language found for " ^ never_returning_null_key ^ " in " ^ inferconfig_file) + | (key, `String s) :: _ when key = "language" -> + language_of_string s + | _:: tl -> loop tl in + loop assoc + + + let detect_pattern assoc = + let language = detect_language assoc in + let is_method_pattern key = list_exists (string_equal key) ["class"; "method"] + and is_source_contains key = list_exists (string_equal key) ["source_contains"] in + let rec loop = function + | [] -> + failwith ("Unknown pattern for " ^ never_returning_null_key ^ " in " ^ inferconfig_file) + | (key, _) :: _ when is_method_pattern key -> + Method_pattern (language, default_method_pattern) + | (key, _) :: _ when is_source_contains key -> + Source_contains (language, default_source_contains) + | _:: tl -> loop tl in + loop assoc + + + let create_pattern (assoc : (string * Yojson.Basic.json) list) = + let collect_params l = + let collect accu = function + | `String s -> s:: accu + | _ -> failwith ("Unrecognised parameters in " ^ Yojson.Basic.to_string (`Assoc assoc)) in + list_rev (list_fold_left collect [] l) in + let create_method_pattern mp assoc = + let loop mp = function + | (key, `String s) when key = "class" -> + { mp with class_name = s } + | (key, `String s) when key = "method" -> + { mp with method_name = Some s } + | (key, `List l) when key = "parameters" -> + { mp with parameters = Some (collect_params l) } + | (key, _) when key = "language" -> mp + | _ -> failwith ("Fails to parse " ^ Yojson.Basic.to_string (`Assoc assoc)) in + list_fold_left loop default_method_pattern assoc + and create_string_contains sc assoc = + let loop sc = function + | (key, `String pattern) when key = "source_contains" -> pattern + | (key, _) when key = "language" -> sc + | _ -> failwith ("Fails to parse " ^ Yojson.Basic.to_string (`Assoc assoc)) in + list_fold_left loop default_source_contains assoc in + match detect_pattern assoc with + | Method_pattern (language, mp) -> + Method_pattern (language, create_method_pattern mp assoc) + | Source_contains (language, sc) -> + Source_contains (language, create_string_contains sc assoc) + + + let rec translate accu (json : Yojson.Basic.json) : pattern list = + match json with + | `Assoc l -> (create_pattern l):: accu + | `List l -> list_fold_left translate accu l + | _ -> assert false + + + let create_method_matcher language m_patterns = + if language <> Sil.Java then assert false + else + if m_patterns = [] then + default_matcher + else + let pattern_map = + list_fold_left + (fun map pattern -> + let previous = + try + StringMap.find pattern.class_name map + with Not_found -> [] in + StringMap.add pattern.class_name (pattern:: previous) map) + StringMap.empty + m_patterns in + fun source_file proc_name -> + let class_name = Procname.java_get_class proc_name + and method_name = Procname.java_get_method proc_name in + try + let class_patterns = StringMap.find class_name pattern_map in + list_exists + (fun p -> + match p.method_name with + | None -> true + | Some m -> string_equal m method_name) + class_patterns + with Not_found -> false + + + let create_file_matcher language patterns = + let s_patterns, m_patterns = + let collect (s_patterns, m_patterns) = function + | Source_contains (lang, s) when lang = language -> (s:: s_patterns, m_patterns) + | Method_pattern (lang, mp) when lang = language -> + (s_patterns, mp :: m_patterns) + | _ -> (s_patterns, m_patterns) in + list_fold_left collect ([], []) patterns in + let s_matcher = + let matcher = FileContainsStringMatcher.create_matcher s_patterns in + fun source_file proc_name -> matcher source_file + and m_matcher = create_method_matcher language m_patterns in + fun source_file proc_name -> + m_matcher source_file proc_name || s_matcher source_file proc_name + + + let load_matcher language = + try + let patterns = + let found = + Yojson.Basic.Util.filter_member + never_returning_null_key + [Yojson.Basic.from_file inferconfig_file] in + list_fold_left translate [] found in + create_file_matcher language patterns + with Sys_error _ -> + default_matcher + + +end (* of module NeverReturnNull *) + + +(* This function loads and list the path that are being filtered by the analyzer. The results *) +(* are of the form: path/to/file.java -> {infer, eradicate} meaning that analysis results will *) +(* be reported on path/to/file.java both for infer and for eradicate *) +let test () = + Config.project_root := Some (Sys.getcwd ()); + let filters = + Utils.list_map (fun analyzer -> (analyzer, create_filters analyzer)) Utils.analyzers in + let matching_analyzers path = + Utils.list_fold_left + (fun l (a, f) -> if f.path_filter path then a:: l else l) + [] filters in + Utils.directory_iter + (fun path -> + if DB.is_source_file path then + let source_file = (DB.source_file_from_string path) in + let matching = matching_analyzers source_file in + if matching <> [] then + let matching_s = + Utils.join_strings ", " + (Utils.list_map Utils.string_of_analyzer matching) in + Logging.stderr "%s -> {%s}@." + (DB.source_file_to_rel_path source_file) + matching_s) + (Sys.getcwd ()) diff --git a/infer/src/backend/inferconfig.mli b/infer/src/backend/inferconfig.mli new file mode 100644 index 000000000..04adc5f99 --- /dev/null +++ b/infer/src/backend/inferconfig.mli @@ -0,0 +1,30 @@ +(* +* Copyright (c) 2015 - Facebook. +* All rights reserved. +*) + +(** Filter type for a source file *) +type path_filter = DB.source_file -> bool + +(** Filter type for an error name. *) +type error_filter = Localise.t -> bool + +type filters = + { + path_filter : path_filter; + error_filter: error_filter; + } + +(** Filters that accept everything. *) +val do_not_filter : filters + +(** Create filters based on the config file *) +val create_filters : Utils.analyzer -> filters + +module NeverReturnNull : sig + type matcher = DB.source_file -> Procname.t -> bool + val load_matcher : Sil.language -> matcher +end + +(** Load the config file and list the files to report on *) +val test: unit -> unit diff --git a/infer/src/backend/inferprint.ml b/infer/src/backend/inferprint.ml new file mode 100644 index 000000000..f61bd7344 --- /dev/null +++ b/infer/src/backend/inferprint.ml @@ -0,0 +1,990 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +module L = Logging +module F = Format +open Utils +open Jsonbug_j + +(** Outfile to save the latex report *) +let latex = ref None + +(** command line flag: if true, print whole seconds only *) +let whole_seconds = ref false + +(** If true, read all .specs files from the results dir *) +let results_dir_cmdline = ref false + +(** Outfile to save bugs stats in csv format *) +let bugs_csv = ref None + +(** Outfile to save bugs stats in JSON format *) +let bugs_json = ref None + +(** Outfile to save bugs stats in txt format *) +let bugs_txt = ref None + +(** Outfile to save bugs stats in xml format *) +let bugs_xml = ref None + +(** Outfile to save procedures stats in csv format *) +let procs_csv = ref None + +(** Outfile to save procedures stats in xml format *) +let procs_xml = ref None + +(** Outfile to save call stats in csv format *) +let calls_csv = ref None + +(** Outfile to save the analysis report *) +let report = ref None + +(** command line flag: if true, produce a svg file *) +let svg = ref false + +(** command line flag: if true, export specs to xml files *) +let xml_specs = ref false + +(** command line flag: if true, produce unit test for each spec *) +let unit_test = ref false + +(** command line flag: if true, do not print the spec to standard output *) +let quiet = ref false + +(** command line flag: if true, print stats about preconditions to standard output *) +let precondition_stats = ref false + +(** name of the file to load analysis results from *) +let load_analysis_results = ref None + +(** name of the file to load save results to *) +let save_analysis_results = ref None + +(** command-line option to print the location of the copy of a source file *) +let source_file_copy = ref None + +(** command line option to test the filtering based on .inferconfig *) +let test_filtering = ref false + +(** Setup the analyzer in order to filter out errors for this analyzer only *) +let analyzer = ref None + +let handle_source_file_copy_option () = match !source_file_copy with + | None -> () + | Some source_file -> + let source_in_resdir = DB.source_file_in_resdir source_file in + F.fprintf F.std_formatter "%s@." (DB.filename_to_string source_in_resdir); + exit 0 + +let canonic_path_from_string s = + if s = Filename.dir_sep then s + else Filename.concat (Filename.dirname s) (Filename.basename s) ^ Filename.dir_sep + +let arg_desc = + let base_arg = + let desc = + [ + "-bugs", Arg.String (fun s -> bugs_csv := create_outfile s), Some "bugs.csv", "create file bugs.csv containing a list of bugs in CSV format"; + "-bugs_json", Arg.String (fun s -> bugs_json := create_outfile s), Some "bugs.json", "create file bugs.json containing a list of bugs in JSON format"; + "-bugs_txt", Arg.String (fun s -> bugs_txt := create_outfile s), Some "bugs.txt", "create file bugs.txt containing a list of bugs in text format"; + "-bugs_xml", Arg.String (fun s -> bugs_xml := create_outfile s), Some "bugs.xml", "create file bugs.xml containing a list of bugs in XML format"; + "-calls", Arg.String (fun s -> calls_csv := create_outfile s), Some "calls.csv", "write individual calls in csv format to file.csv"; + "-load_results", Arg.String (fun s -> load_analysis_results := Some s), Some "file.iar", "load analysis results from Infer Analysis Results file file.iar"; + "-procs", Arg.String (fun s -> procs_csv := create_outfile s), Some "procs.csv", "create file procs.csv containing statistics for each procedure in CSV format"; + "-procs_xml", Arg.String (fun s -> procs_xml := create_outfile s), Some "procs.xml", "create file procs.xml containing statistics for each procedure in XML format"; + "-results_dir", Arg.String (fun s -> results_dir_cmdline := true; Config.results_dir := s), Some "dir", "read all the .specs files in the results dir"; + "-q", Arg.Set quiet, None, "quiet: do not print specs on standard output"; + "-save_results", Arg.String (fun s -> save_analysis_results := Some s), Some "file.iar", "save analysis results to Infer Analysis Results file file.iar"; + "-unit_test", Arg.Set unit_test, None, "print unit test code"; + "-xml", Arg.Set xml_specs, None, "export specs into XML files file1.xml ... filen.xml"; + "-test_filtering", Arg.Set test_filtering, None, + "list all the files Infer can report on (should be call at the root of the procject, where + .inferconfig lives)."; + "-analyzer", Arg.String (fun s -> analyzer := Some (Utils.analyzer_of_string s)), Some "analyzer", + "setup the analyzer for the path filtering"; + ] in + Arg2.create_options_desc false "Options" desc in + let reserved_arg = + let desc = + [ + "-latex", Arg.String (fun s -> latex := create_outfile s), Some "file.tex", "print latex report to file.tex"; + "-print_types", Arg.Set Config.print_types, None, "print types in symbolic heaps"; + "-precondition_stats", Arg.Set precondition_stats, None, "print stats about preconditions to standard output"; + "-report", Arg.String (fun s -> report := create_outfile s), Some "report_file", "create file report_file containing a report of the analysis results"; + "-source_file_copy", Arg.String (fun s -> source_file_copy := Some (DB.abs_source_file_from_path s)), Some "source_file", "print the path of the copy of source_file in the results directory"; + "-svg", Arg.Set svg, None, "generate .dot and .svg"; + "-whole_seconds", Arg.Set whole_seconds, None, "print whole seconds only"; + ] in + Arg2.create_options_desc false "Reserved Options" desc in + base_arg @ reserved_arg + +let usage = + "Usage: InferPrint [options] name1.specs ... namen.specs\n" ^ + " Read, convert, and print .specs files.\n" ^ + " To process all the .specs in the current directory, pass . as only parameter.\n" ^ + " To process all the .specs in the results directory, use option -results_dir.\n" ^ + " Each spec is printed to standard output unless option -q is used." + +let print_usage_exit err_s = + L.err "Load Error: %s@.@." err_s; + Arg2.usage arg_desc usage; + exit(1) + + +(** return the list of the .specs files in the results dir, if it's defined *) +let results_dir_specsfiles () = match !results_dir_cmdline with + | false -> [] + | true -> + let specs_dir = DB.Results_dir.specs_dir () in + let specs_files_in_dir dir = + let is_specs_file fname = not (Sys.is_directory fname) && Filename.check_suffix fname ".specs" in + let all_filenames = Array.to_list (Sys.readdir dir) in + let all_filepaths = list_map (fun fname -> Filename.concat dir fname) all_filenames in + list_filter is_specs_file all_filepaths in + specs_files_in_dir (DB.filename_to_string specs_dir) + +(** Create and initialize latex file *) +let begin_latex_file fmt = + let author = "Infer " ^ Version.versionString in + let title = "Report on Analysis Results" in + let table_of_contents = true in + Latex.pp_begin fmt (author, title, table_of_contents) + +(** Write proc summary to latex file *) +let write_summary_latex fname fmt summary = + let proc_name = summary.Specs.proc_name in + Latex.pp_section fmt ("Analysis of function " ^ (Latex.convert_string (Procname.to_string proc_name))); + F.fprintf fmt "@[%a@]" (Specs.pp_summary (pe_latex Black) !whole_seconds) summary + +let error_desc_to_csv_string error_desc = + let pp fmt () = F.fprintf fmt "%a" Localise.pp_error_desc error_desc in + Escape.escape_csv (pp_to_string pp ()) + +let error_advice_to_csv_string error_desc = + let pp fmt () = F.fprintf fmt "%a" Localise.pp_error_advice error_desc in + Escape.escape_csv (pp_to_string pp ()) + +let error_desc_to_plain_string error_desc = + let pp fmt () = F.fprintf fmt "%a" Localise.pp_error_desc error_desc in + pp_to_string pp () + +let error_desc_to_xml_string error_desc = + let pp fmt () = F.fprintf fmt "%a" Localise.pp_error_desc error_desc in + Escape.escape_xml (pp_to_string pp ()) + +let error_desc_to_xml_tags error_desc = + let tags = Localise.error_desc_get_tags error_desc in + let subtree label contents = + Io_infer.Xml.create_tree label [] [(Io_infer.Xml.String contents)] in + list_map (fun (tag, value) -> subtree tag (Escape.escape_xml value)) tags + +let get_bug_hash (kind: string) (type_str: string) (procedure_id: string) (filename: string) (node_key: int) (error_desc: Localise.error_desc) = + let qualifier_tag_call_procedure = Localise.error_desc_get_tag_call_procedure error_desc in + let qualifier_tag_value = Localise.error_desc_get_tag_value error_desc in + Hashtbl.hash(kind, type_str, procedure_id, filename, node_key, qualifier_tag_call_procedure, qualifier_tag_value) + +let loc_trace_to_jsonbug_record trace_list ekind = + match ekind with + | Exceptions.Kinfo -> [] + | _ -> + (* writes a trace as a record for atdgen conversion *) + let node_tags_to_records tags_list = + list_map (fun tag -> { tag = fst tag; value = snd tag }) tags_list in + let trace_item_to_record trace_item = + { level = trace_item.Errlog.lt_level; + filename = DB.source_file_to_string trace_item.Errlog.lt_loc.Sil.file; + line_number = trace_item.Errlog.lt_loc.Sil.line; + description = trace_item.Errlog.lt_description; + node_tags = node_tags_to_records trace_item.Errlog.lt_node_tags; + } in + let record_list = list_rev (list_rev_map trace_item_to_record trace_list) in + record_list + +let error_desc_to_qualifier_tags_records error_desc = + let tag_value_pairs = Localise.error_desc_to_tag_value_pairs error_desc in + let tag_value_to_record (tag, value) = + {tag = tag; value = value } in + list_map (fun tag_value -> tag_value_to_record tag_value) tag_value_pairs + +type summary_val = + { vname : string; + vname_id : string; + vspecs : int; + vtime : string; + vto : string; + vsymop : int; + verr : int; + vfile : string; + vflags : proc_flags; + vline : int; + vloc : int; + vtop : string; + vsignature : string; + vweight : int; + vproof_coverage : string; + vrank : string; + vin_calls : int; + vout_calls : int; + vproof_trace : string; + vcyclomatic : int } + +(** compute values from summary data to export to csv and xml format *) +let summary_values top_proc_set summary = + let stats = summary.Specs.stats in + let proc_name = summary.Specs.proc_name in + let is_top = Procname.Set.mem proc_name top_proc_set in + let signature = Specs.get_signature summary in + let nodes_nr = list_length summary.Specs.nodes in + let specs = Specs.get_specs_from_payload summary in + let nr_nodes_visited, lines_visited = + let visited = ref Specs.Visitedset.empty in + let do_spec spec = visited := Specs.Visitedset.union spec.Specs.visited !visited in + list_iter do_spec specs; + let visited_lines = ref IntSet.empty in + Specs.Visitedset.iter (fun (n, ls) -> + list_iter (fun l -> visited_lines := IntSet.add l !visited_lines) ls) + !visited; + Specs.Visitedset.cardinal !visited, IntSet.elements !visited_lines in + let proof_trace = + let pp_line fmt l = F.fprintf fmt "%d" l in + let pp fmt () = F.fprintf fmt "%a" (pp_seq pp_line) lines_visited in + pp_to_string pp () in + let node_coverage = + if nodes_nr = 0 then 0.0 + else float_of_int nr_nodes_visited /. float_of_int nodes_nr in + let logscale x = + log10 (float_of_int (x + 1)) in + let in_calls, out_calls = + let calls = stats.Specs.stats_calls in + calls.Cg.in_calls, calls.Cg.out_calls in + let call_rank = + let c1 = 1 and c2 = 1 in + logscale (c1 * in_calls + c2 * out_calls) in + let cyclomatic = stats.Specs.cyclomatic in + { vname = Procname.to_string proc_name; + vname_id = Procname.to_filename proc_name; + vspecs = list_length specs; + vtime = Printf.sprintf "%.0f" stats.Specs.stats_time; + vto = if stats.Specs.stats_timeout then "TO" else " "; + vsymop = stats.Specs.symops; + verr = Errlog.size + (fun ekind in_footprint -> ekind = Exceptions.Kerror && in_footprint) + stats.Specs.err_log; + vflags = summary.Specs.proc_flags; + vfile = DB.source_file_to_string summary.Specs.loc.Sil.file; + vline = summary.Specs.loc.Sil.line; + vloc = summary.Specs.loc.Sil.nLOC; + vtop = if is_top then "Y" else "N"; + vsignature = signature; + vweight = nodes_nr; + vproof_coverage = Printf.sprintf "%2.2f" node_coverage; + vrank = Printf.sprintf "%2.2f" call_rank; + vin_calls = in_calls; + vout_calls = out_calls; + vproof_trace = proof_trace; + vcyclomatic = cyclomatic } + + +module ProcsCsv = struct + (** Print the header of the procedures csv file, with column names *) + let pp_header fmt () = + Format.fprintf fmt "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s@\n" Io_infer.Xml.tag_name Io_infer.Xml.tag_name_id Io_infer.Xml.tag_specs Io_infer.Xml.tag_time Io_infer.Xml.tag_to Io_infer.Xml.tag_symop Io_infer.Xml.tag_err Io_infer.Xml.tag_file Io_infer.Xml.tag_line Io_infer.Xml.tag_loc Io_infer.Xml.tag_top Io_infer.Xml.tag_signature Io_infer.Xml.tag_weight Io_infer.Xml.tag_proof_coverage Io_infer.Xml.tag_rank Io_infer.Xml.tag_in_calls Io_infer.Xml.tag_out_calls Io_infer.Xml.tag_proof_trace Io_infer.Xml.tag_cyclomatic + + (** Write proc summary stats in csv format *) + let pp_summary fname top_proc_set fmt summary = + let pp x = F.fprintf fmt x in + let sv = summary_values top_proc_set summary in + pp "\"%s\"," (Escape.escape_csv sv.vname); + pp "\"%s\"," (Escape.escape_csv sv.vname_id); + pp "%d," sv.vspecs; + pp "%s," sv.vtime; + pp "%s," sv.vto; + pp "%d," sv.vsymop; + pp "%d," sv.verr; + pp "%s," sv.vfile; + pp "%d," sv.vline; + pp "%d," sv.vloc; + pp "%s," sv.vtop; + pp "\"%s\"," (Escape.escape_csv sv.vsignature); + pp "%d," sv.vweight; + pp "%s," sv.vproof_coverage; + pp "%s," sv.vrank; + pp "%d," sv.vin_calls; + pp "%d," sv.vout_calls; + pp "%s," sv.vproof_trace; + pp "%d@\n" sv.vcyclomatic +end + +module ProcsXml = struct + let xml_procs_id = ref 0 + + (** print proc in xml *) + let pp_proc top_proc_set fmt summary = + let sv = summary_values top_proc_set summary in + let subtree label contents = + Io_infer.Xml.create_tree label [] [(Io_infer.Xml.String contents)] in + let tree = + incr xml_procs_id; + let attributes = [("id", string_of_int !xml_procs_id) ] in + let forest = + [ + subtree Io_infer.Xml.tag_name (Escape.escape_xml sv.vname); + subtree Io_infer.Xml.tag_name_id (Escape.escape_xml sv.vname_id); + subtree Io_infer.Xml.tag_specs (string_of_int sv.vspecs); + subtree Io_infer.Xml.tag_time sv.vtime; + subtree Io_infer.Xml.tag_to sv.vto; + subtree Io_infer.Xml.tag_symop (string_of_int sv.vsymop); + subtree Io_infer.Xml.tag_err (string_of_int sv.verr); + subtree Io_infer.Xml.tag_file sv.vfile; + subtree Io_infer.Xml.tag_line (string_of_int sv.vline); + subtree Io_infer.Xml.tag_loc (string_of_int sv.vloc); + subtree Io_infer.Xml.tag_top sv.vtop; + subtree Io_infer.Xml.tag_signature (Escape.escape_xml sv.vsignature); + subtree Io_infer.Xml.tag_weight (string_of_int sv.vweight); + subtree Io_infer.Xml.tag_proof_coverage sv.vproof_coverage; + subtree Io_infer.Xml.tag_rank sv.vrank; + subtree Io_infer.Xml.tag_in_calls (string_of_int sv.vin_calls); + subtree Io_infer.Xml.tag_out_calls (string_of_int sv.vin_calls); + subtree Io_infer.Xml.tag_proof_trace sv.vproof_trace; + subtree Io_infer.Xml.tag_cyclomatic (string_of_int sv.vcyclomatic); + subtree Io_infer.Xml.tag_flags (string_of_int (Hashtbl.length sv.vflags)); + ] in + Io_infer.Xml.create_tree "procedure" attributes forest in + Io_infer.Xml.pp_inner_node fmt tree + + (** print the opening of the procedures xml file *) + let pp_procs_open fmt () = + Io_infer.Xml.pp_open fmt "procedures" + + (** print the closing of the procedures xml file *) + let pp_procs_close fmt () = + Io_infer.Xml.pp_close fmt "procedures" + +end + +module BugsCsv = struct + let csv_bugs_id = ref 0 + + let timestamp = Unix.time () + let pp_header fmt () = + Format.fprintf fmt "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s@\n" + Io_infer.Xml.tag_class + Io_infer.Xml.tag_kind + Io_infer.Xml.tag_type + Io_infer.Xml.tag_qualifier + Io_infer.Xml.tag_severity + Io_infer.Xml.tag_line + Io_infer.Xml.tag_procedure + Io_infer.Xml.tag_procedure_id + Io_infer.Xml.tag_file + Io_infer.Xml.tag_trace + Io_infer.Xml.tag_key + Io_infer.Xml.tag_qualifier_tags + Io_infer.Xml.tag_hash + "bug_id" + "always_report" + "advice" + + (** Write bug report in csv format *) + let pp_bugs error_filter fname fmt summary = + let pp x = F.fprintf fmt x in + let err_log = summary.Specs.stats.Specs.err_log in + let pp_row (node_id, node_key) loc ekind in_footprint error_name error_desc severity ltr pre_opt eclass = + if in_footprint && error_filter error_desc error_name then + let err_desc_string = error_desc_to_csv_string error_desc in + let err_advice_string = error_advice_to_csv_string error_desc in + let qualifier_tag_xml = + let xml_node = Io_infer.Xml.create_tree Io_infer.Xml.tag_qualifier_tags [] (error_desc_to_xml_tags error_desc) in + let p fmt () = F.fprintf fmt "%a" (Io_infer.Xml.pp_document false) xml_node in + let s = Utils.pp_to_string p () in + Escape.escape_csv s in + let kind = Exceptions.err_kind_string ekind in + let type_str = Localise.to_string error_name in + let procedure_id = Procname.to_filename summary.Specs.proc_name in + let filename = DB.source_file_to_string summary.Specs.loc.Sil.file in + let always_report = + match Localise.error_desc_extract_tag_value error_desc "always_report" with + | "" -> "false" + | v -> v in + + let trace = string_of_json_trace { trace = loc_trace_to_jsonbug_record ltr ekind } in + incr csv_bugs_id; + pp "%s," (Exceptions.err_class_string eclass); + pp "%s," kind; + pp "%s," type_str; + pp "\"%s\"," err_desc_string; + pp "%s," severity; + pp "%d," loc.Sil.line; + pp "\"%s\"," (Escape.escape_csv (Procname.to_string summary.Specs.proc_name)); + pp "\"%s\"," (Escape.escape_csv procedure_id); + pp "%s," filename; + pp "\"%s\"," (Escape.escape_csv trace); + pp "\"%d\"," node_key; + pp "\"%s\"," qualifier_tag_xml; + pp "\"%d\"," (get_bug_hash kind type_str procedure_id filename node_key error_desc); + pp "\"%d\"," !csv_bugs_id; (** bug id *) + pp "\"%s\"," always_report; + pp "\"%s\"@\n" err_advice_string; in + Errlog.iter pp_row err_log +end + +module BugsJson = struct + let is_first_item = ref true + let pp_json_open fmt () = F.fprintf fmt "[@?" + let pp_json_close fmt () = F.fprintf fmt "]\n@?" + + (** Write bug report in JSON format *) + let pp_bugs error_filter fname fmt summary = + let pp x = F.fprintf fmt x in + let err_log = summary.Specs.stats.Specs.err_log in + let pp_row (node_id, node_key) loc ekind in_footprint error_name error_desc severity ltr pre_opt eclass = + if in_footprint && error_filter error_desc error_name then + let kind = Exceptions.err_kind_string ekind in + let bug_type = Localise.to_string error_name in + let procedure_id = Procname.to_filename summary.Specs.proc_name in + let file = DB.source_file_to_string summary.Specs.loc.Sil.file in + let bug = { + bug_class = Exceptions.err_class_string eclass; + kind = kind; + bug_type = bug_type; + qualifier = error_desc_to_plain_string error_desc; + severity = severity; + line = loc.Sil.line; + procedure = Procname.to_string summary.Specs.proc_name; + procedure_id = procedure_id; + file = file; + bug_trace = loc_trace_to_jsonbug_record ltr ekind; + key = node_key; + qualifier_tags = error_desc_to_qualifier_tags_records error_desc; + hash = get_bug_hash kind bug_type procedure_id file node_key error_desc; + } in + if not !is_first_item then pp "," else is_first_item := false; + pp "%s@?" (string_of_jsonbug bug) in + Errlog.iter pp_row err_log +end + +module BugsTxt = struct + (** Write bug report in text format *) + let pp_bugs error_filter fname fmt summary = + let err_log = summary.Specs.stats.Specs.err_log in + let pp_row (node_id, node_key) loc ekind in_footprint error_name error_desc severity ltr pre_opt eclass = + if in_footprint && error_filter error_desc error_name then + Exceptions.pp_err (node_id, node_key) loc ekind error_name error_desc None fmt () in + Errlog.iter pp_row err_log +end + +module BugsXml = struct + let xml_bugs_id = ref 0 + let include_precondition_tree = false + + let loc_trace_to_xml linereader ltr = + let subtree label contents = + Io_infer.Xml.create_tree label [] [(Io_infer.Xml.String contents)] in + let level_to_xml level = subtree Io_infer.Xml.tag_level (string_of_int level) in + let line_to_xml line = subtree Io_infer.Xml.tag_line (string_of_int line) in + let file_to_xml file = subtree Io_infer.Xml.tag_file file in + let code_to_xml code = subtree Io_infer.Xml.tag_code code in + let description_to_xml descr = subtree Io_infer.Xml.tag_description (Escape.escape_xml descr) in + let node_tags_to_xml node_tags = + let escaped_tags = list_map (fun (tag, value) -> (tag, Escape.escape_xml value)) node_tags in + Io_infer.Xml.create_tree Io_infer.Xml.tag_node escaped_tags [] in + let num = ref 0 in + let loc_to_xml lt = + incr num; + let loc = lt.Errlog.lt_loc in + let code = match Printer.LineReader.from_loc linereader loc with + | Some s -> Escape.escape_xml s + | None -> "" in + Io_infer.Xml.create_tree Io_infer.Xml.tag_loc [("num", string_of_int !num)] + [(level_to_xml lt.Errlog.lt_level); + (file_to_xml (DB.source_file_to_string loc.Sil.file)); + (line_to_xml loc.Sil.line); + (code_to_xml code); + (description_to_xml lt.Errlog.lt_description); + (node_tags_to_xml lt.Errlog.lt_node_tags)] in + list_rev (list_rev_map loc_to_xml ltr) + + (** print bugs from summary in xml *) + let pp_bugs error_filter linereader fmt summary = + let err_log = summary.Specs.stats.Specs.err_log in + let do_row (node_id, node_key) loc ekind in_footprint error_name error_desc severity ltr pre_opt eclass = + if in_footprint && error_filter error_desc error_name then + let err_desc_string = error_desc_to_xml_string error_desc in + let precondition_tree () = match pre_opt with + | None -> [] + | Some pre -> + Dotty.reset_node_counter (); + [Dotty.prop_to_xml pre Io_infer.Xml.tag_precondition 1] in + let subtree label contents = + Io_infer.Xml.create_tree label [] [(Io_infer.Xml.String contents)] in + let kind = Exceptions.err_kind_string ekind in + let type_str = Localise.to_string error_name in + let tree = + incr xml_bugs_id; + let attributes = [("id", string_of_int !xml_bugs_id) ] in + let error_class = Exceptions.err_class_string eclass in + let error_line = string_of_int loc.Sil.line in + let procedure_name = Procname.to_string summary.Specs.proc_name in + let procedure_id = Procname.to_filename summary.Specs.proc_name in + let filename = DB.source_file_to_string summary.Specs.loc.Sil.file in + let bug_hash = get_bug_hash kind type_str procedure_id filename node_key error_desc in + let forest = + [ + subtree Io_infer.Xml.tag_class error_class; + subtree Io_infer.Xml.tag_kind kind; + subtree Io_infer.Xml.tag_type type_str; + subtree Io_infer.Xml.tag_qualifier err_desc_string; + subtree Io_infer.Xml.tag_severity severity; + subtree Io_infer.Xml.tag_line error_line; + subtree Io_infer.Xml.tag_procedure (Escape.escape_xml procedure_name); + subtree Io_infer.Xml.tag_procedure_id (Escape.escape_xml procedure_id); + subtree Io_infer.Xml.tag_file filename; + Io_infer.Xml.create_tree Io_infer.Xml.tag_trace [] (loc_trace_to_xml linereader ltr); + subtree Io_infer.Xml.tag_key (string_of_int node_key); + Io_infer.Xml.create_tree Io_infer.Xml.tag_qualifier_tags [] (error_desc_to_xml_tags error_desc); + subtree Io_infer.Xml.tag_hash (string_of_int bug_hash) + ] + @ + (if include_precondition_tree then precondition_tree () else []) in + Io_infer.Xml.create_tree "bug" attributes forest in + Io_infer.Xml.pp_inner_node fmt tree in + Errlog.iter do_row err_log + + (** print the opening of the bugs xml file *) + let pp_bugs_open fmt () = + Io_infer.Xml.pp_open fmt "bugs" + + (** print the closing of the bugs xml file *) + let pp_bugs_close fmt () = + Io_infer.Xml.pp_close fmt "bugs" +end + +module CallsCsv = struct + (** Print the header of the calls csv file, with column names *) + let pp_header fmt () = + Format.fprintf fmt "%s,%s,%s,%s,%s,%s,%s@\n" Io_infer.Xml.tag_caller Io_infer.Xml.tag_caller_id Io_infer.Xml.tag_callee Io_infer.Xml.tag_callee_id Io_infer.Xml.tag_file Io_infer.Xml.tag_line Io_infer.Xml.tag_call_trace + + (** Write proc summary stats in csv format *) + let pp_calls fname fmt summary = + let pp x = F.fprintf fmt x in + let stats = summary.Specs.stats in + let caller_name = summary.Specs.proc_name in + let do_call (callee_name, loc) trace = + pp "\"%s\"," (Escape.escape_csv (Procname.to_string caller_name)); + pp "\"%s\"," (Escape.escape_csv (Procname.to_filename caller_name)); + pp "\"%s\"," (Escape.escape_csv (Procname.to_string callee_name)); + pp "\"%s\"," (Escape.escape_csv (Procname.to_filename callee_name)); + pp "%s," (DB.source_file_to_string summary.Specs.loc.Sil.file); + pp "%d," loc.Sil.line; + pp "%a@\n" Specs.CallStats.pp_trace trace in + Specs.CallStats.iter do_call stats.Specs.call_stats +end + +module UnitTest = struct + (** Store the unit test functions generated, so that they can be called by main at the end *) + let procs_done = ref [] + + (** Print unit test for every spec in the summary *) + let print_unit_test fname proc_name summary = + let cnt = ref 0 in + let fmt = F.std_formatter in + let do_spec spec = + incr cnt; + let c_file = Filename.basename (DB.source_file_to_string summary.Specs.loc.Sil.file) in + let code = Autounit.genunit c_file proc_name !cnt summary.Specs.formals spec in + F.fprintf fmt "%a@." Autounit.pp_code code in + let specs = Specs.get_specs_from_payload summary in + list_iter do_spec specs; + procs_done := (proc_name, list_length specs) :: !procs_done + + (** Print main function which calls all the unit test functions generated *) + let print_unit_test_main () = + let fmt = F.std_formatter in + let code = Autounit.genmain !procs_done in + F.fprintf fmt "%a@." Autounit.pp_code code +end + +(** Module to compute the top procedures. +A procedure is top if it has specs and any procedure calling it has no specs *) +module TopProcedures : sig + type t + val create : unit -> t + val process_summary : t -> string * Specs.summary -> unit + val top_set: t -> Procname.Set.t +end = struct + type t = + { mutable possible: Procname.Set.t; + mutable impossible: Procname.Set.t } + let create () = + { possible = Procname.Set.empty; + impossible = Procname.Set.empty } + let mark_possible x pname = + x.possible <- Procname.Set.add pname x.possible + let mark_impossible x pname = + x.impossible <- Procname.Set.add pname x.impossible + let top_set x = + Procname.Set.diff x.possible x.impossible + let process_summary x (_, summary) = + let proc_name = summary.Specs.proc_name in + let nspecs = list_length (Specs.get_specs_from_payload summary) in + if nspecs > 0 then + begin + mark_possible x proc_name; + Procname.Map.iter (fun p _ -> mark_impossible x p) summary.Specs.dependency_map + end +end + +module Stats = struct + type t = { + files : (DB.source_file, unit) Hashtbl.t; + mutable nchecked : int; + mutable ndefective : int; + mutable nerrors : int; + mutable ninfos : int; + mutable nLOC : int; + mutable nprocs : int; + mutable nspecs : int; + mutable nverified : int; + mutable nwarnings : int; + mutable saved_errors : string list; + } + + let create () = { + files = Hashtbl.create 3; + nchecked = 0; + ndefective = 0; + nerrors = 0; + ninfos = 0; + nLOC = 0; + nprocs = 0; + nspecs = 0; + nverified = 0; + nwarnings = 0; + saved_errors = []; + } + + let process_loc loc stats = + try Hashtbl.find stats.files loc.Sil.file + with Not_found -> + stats.nLOC <- stats.nLOC + loc.Sil.nLOC; + Hashtbl.add stats.files loc.Sil.file () + + let loc_trace_to_string_list linereader indent_num ltr = + let res = ref [] in + let indent_string n = + let s = ref "" in + for i = 1 to n do s := " " ^ !s done; + !s in + let num = ref 0 in + let loc_to_string lt = + incr num; + let loc = lt.Errlog.lt_loc in + let level = lt.Errlog.lt_level in + let description = lt.Errlog.lt_description in + let code = match Printer.LineReader.from_loc linereader loc with + | Some s -> s + | None -> "" in + let line = + let pp fmt () = + if description <> "" then F.fprintf fmt "%s%04s // %s@\n" (indent_string (level + indent_num)) " " description; + F.fprintf fmt "%s%04d: %s" (indent_string (level + indent_num)) loc.Sil.line code in + pp_to_string pp () in + res := line :: "" :: !res in + list_iter loc_to_string ltr; + list_rev !res + + let process_err_log error_filter linereader err_log stats = + let found_errors = ref false in + let process_row (node_id, node_key) loc ekind in_footprint error_name error_desc severity ltr pre_opt eclass = + let type_str = Localise.to_string error_name in + if in_footprint && error_filter error_desc error_name + then match ekind with + | Exceptions.Kerror -> + found_errors := true; + stats.nerrors <- stats.nerrors + 1; + let error_strs = + let pp1 fmt () = F.fprintf fmt "%d: %s" stats.nerrors type_str in + let pp2 fmt () = F.fprintf fmt " %s:%d" (DB.source_file_to_string loc.Sil.file) loc.Sil.line in + let pp3 fmt () = F.fprintf fmt " (%a)" Localise.pp_error_desc error_desc in + [pp_to_string pp1 (); pp_to_string pp2 (); pp_to_string pp3 ()] in + let trace = loc_trace_to_string_list linereader 1 ltr in + stats.saved_errors <- list_rev_append (error_strs @ trace @ [""]) stats.saved_errors + | Exceptions.Kwarning -> stats.nwarnings <- stats.nwarnings + 1 + | Exceptions.Kinfo -> stats.ninfos <- stats.ninfos + 1 in + Errlog.iter process_row err_log; + !found_errors + + let process_summary error_filter summary linereader stats = + let specs = Specs.get_specs_from_payload summary in + let found_errors = + process_err_log error_filter linereader summary.Specs.stats.Specs.err_log stats in + let is_defective = found_errors in + let is_verified = specs <> [] && not is_defective in + let is_checked = not (is_defective || is_verified) in + stats.nprocs <- stats.nprocs + 1; + stats.nspecs <- stats.nspecs + (list_length specs); + if is_verified then stats.nverified <- stats.nverified + 1; + if is_checked then stats.nchecked <- stats.nchecked + 1; + if is_defective then stats.ndefective <- stats.ndefective + 1; + process_loc summary.Specs.loc stats + + let num_files stats = + Hashtbl.length stats.files + + let pp fmt stats = + F.fprintf fmt "Files: %d@\n" (num_files stats); + F.fprintf fmt "LOC: %d@\n" stats.nLOC; + F.fprintf fmt "Specs: %d@\n" stats.nspecs; + F.fprintf fmt "Procedures: %d@\n" stats.nprocs; + F.fprintf fmt " Verified: %d@\n" stats.nverified; + F.fprintf fmt " Checked: %d@\n" stats.nchecked; + F.fprintf fmt " Defective: %d@\n" stats.ndefective; + F.fprintf fmt "Errors: %d@\n" stats.nerrors; + F.fprintf fmt "Warnings: %d@\n" stats.nwarnings; + F.fprintf fmt "Infos: %d@\n" stats.ninfos; + F.fprintf fmt "@\n -------------------@\n"; + F.fprintf fmt "@\nDetailed Errors@\n@\n"; + list_iter (fun s -> F.fprintf fmt "%s@\n" s) (list_rev stats.saved_errors); +end + +module Report = struct + let pp_header fmt () = + F.fprintf fmt "Infer Analysis Results -- generated %a@\n@\n" pp_current_time (); + F.fprintf fmt "Summary Report@\n@\n" + + let pp_stats fmt stats = + Stats.pp fmt stats +end + +(** Categorize the preconditions of specs and print stats *) +module PreconditionStats = struct + let nr_nopres = ref 0 + let nr_empty = ref 0 + let nr_onlyallocation = ref 0 + let nr_dataconstraints = ref 0 + + let do_summary proc_name summary = + let specs = Specs.get_specs_from_payload summary in + let preconditions = list_map (fun spec -> Specs.Jprop.to_prop spec.Specs.pre) specs in + match Prop.CategorizePreconditions.categorize preconditions with + | Prop.CategorizePreconditions.Empty -> + incr nr_empty; + L.out "Procedure: %a footprint:Empty@." Procname.pp proc_name + | Prop.CategorizePreconditions.OnlyAllocation -> + incr nr_onlyallocation; + L.out "Procedure: %a footprint:OnlyAllocation@." Procname.pp proc_name + | Prop.CategorizePreconditions.NoPres -> + incr nr_nopres; + L.out "Procedure: %a footprint:NoPres@." Procname.pp proc_name + | Prop.CategorizePreconditions.DataConstraints -> + incr nr_dataconstraints; + L.out "Procedure: %a footprint:DataConstraints@." Procname.pp proc_name + + let pp_stats () = + L.out "@.Precondition stats@."; + L.out "Procedures with no preconditions: %d@." !nr_nopres; + L.out "Procedures with empty precondition: %d@." !nr_empty; + L.out "Procedures with only allocation conditions: %d@." !nr_onlyallocation; + L.out "Procedures with data constraints: %d@." !nr_dataconstraints +end + +(** Process a summary *) +let process_summary filters linereader stats (top_proc_set: Procname.Set.t) (fname, summary) = + + let proc_name = summary.Specs.proc_name in + let base = DB.chop_extension (DB.filename_from_string fname) in + let pp_simple_saved = !Config.pp_simple in + Config.pp_simple := true; + if !quiet then () (* L.err "File: %s@." fname *) + else L.out "Procedure: %a@\n%a@." Procname.pp proc_name (Specs.pp_summary pe_text !whole_seconds) summary; + let error_filter error_desc error_name = + let always_report () = + Localise.error_desc_extract_tag_value error_desc "always_report" = "true" in + (filters.Inferconfig.path_filter summary.Specs.loc.Sil.file + || always_report ()) && + filters.Inferconfig.error_filter error_name in + do_outf procs_csv (fun outf -> F.fprintf outf.fmt "%a" (ProcsCsv.pp_summary fname top_proc_set) summary); + do_outf calls_csv (fun outf -> F.fprintf outf.fmt "%a" (CallsCsv.pp_calls fname) summary); + do_outf procs_xml (fun outf -> ProcsXml.pp_proc top_proc_set outf.fmt summary); + do_outf bugs_csv (fun outf -> BugsCsv.pp_bugs error_filter fname outf.fmt summary); + do_outf bugs_json (fun outf -> BugsJson.pp_bugs error_filter fname outf.fmt summary); + do_outf bugs_txt (fun outf -> BugsTxt.pp_bugs error_filter linereader outf.fmt summary); + do_outf bugs_xml (fun outf -> BugsXml.pp_bugs error_filter linereader outf.fmt summary); + do_outf report (fun outf -> Stats.process_summary error_filter summary linereader stats); + if !precondition_stats then PreconditionStats.do_summary proc_name summary; + if !unit_test then UnitTest.print_unit_test fname proc_name summary; + Config.pp_simple := pp_simple_saved; + do_outf latex (fun outf -> write_summary_latex (DB.filename_from_string fname) outf.fmt summary); + if !svg then begin + let specs = Specs.get_specs_from_payload summary in + let dot_file = DB.filename_add_suffix base ".dot" in + let svg_file = DB.filename_add_suffix base ".svg" in + if not (DB.file_exists dot_file) + || DB.file_modified_time (DB.filename_from_string fname) > DB.file_modified_time dot_file + then + Dotty.pp_speclist_dotty_file base specs; + if not (DB.file_exists svg_file) + || DB.file_modified_time dot_file > DB.file_modified_time svg_file + then + ignore (Sys.command ("dot -Tsvg \"" ^ (DB.filename_to_string dot_file) ^ "\" >\"" ^ (DB.filename_to_string svg_file) ^"\"")) + end; + if !xml_specs then begin + let xml_file = DB.filename_add_suffix base ".xml" in + let specs = Specs.get_specs_from_payload summary in + if not (DB.file_exists xml_file) + || DB.file_modified_time (DB.filename_from_string fname) > DB.file_modified_time xml_file + then + begin + let xml_out = ref (create_outfile (DB.filename_to_string xml_file)) in + do_outf xml_out (fun outf -> + Dotty.print_specs_xml (Specs.get_signature summary) specs summary.Specs.loc outf.fmt; + close_outf outf) + end + end +(* ignore (Sys.command ("open " ^ base ^ ".svg")) *) + +module AnalysisResults = struct + type t = (string * Specs.summary) list + + let spec_files_from_cmdline = (* parse command-line arguments, and find spec files specified there *) + let args = ref [] in + let f arg = + if not (Filename.check_suffix arg ".specs") && arg <> "." + then print_usage_exit "arguments must be .specs files" + else args := arg::!args in + Arg2.parse arg_desc f usage; + if !test_filtering then + begin + Inferconfig.test (); + exit(0) + end; + list_append (if !args = ["."] then begin + let arr = Sys.readdir "." in + let all_files = Array.to_list arr in + list_filter (fun fname -> (Filename.check_suffix fname ".specs")) all_files + end + else !args) (results_dir_specsfiles ()) + + (** apply [f] to [arg] with the gc compaction disabled during the execution *) + let apply_without_gc f arg = + let stat = Gc.get () in + let space_oh = stat.Gc.space_overhead in + Gc.set { stat with Gc.space_overhead = 10000 }; + let res = f arg in + Gc.set { stat with Gc.space_overhead = space_oh }; + res + + (** Load .specs files in memory and return list of summaries *) + let load_summaries_in_memory () : t = + let summaries = ref [] in + let load_file fname = + match Specs.load_summary (DB.filename_from_string fname) with + | None -> + L.err "Error: cannot open file %s@." fname; + exit 0 + | Some summary -> + summaries := (fname, summary) :: !summaries in + apply_without_gc (list_iter load_file) spec_files_from_cmdline; + let summ_cmp (fname1, summ1) (fname2, summ2) = + let n = DB.source_file_compare summ1.Specs.loc.Sil.file summ2.Specs.loc.Sil.file in + if n <> 0 then n else int_compare summ1.Specs.loc.Sil.line summ2.Specs.loc.Sil.line in + list_sort summ_cmp !summaries + + (** Create an iterator which loads spec files one at a time *) + let iterator_of_spec_files () = + let sorted_spec_files = list_sort string_compare spec_files_from_cmdline in + let do_spec f fname = + match Specs.load_summary (DB.filename_from_string fname) with + | None -> + L.err "Error: cannot open file %s@." fname; + exit 0 + | Some summary -> + f (fname, summary) in + let iterate f = + list_iter (do_spec f) sorted_spec_files in + iterate + + (** Serializer for analysis results *) + let analysis_results_serializer : t Serialization.serializer = Serialization.create_serializer Serialization.analysis_results_key + + (** Load analysis_results from a file *) + let load_analysis_results_from_file (filename : DB.filename) : t option = + Serialization.from_file analysis_results_serializer filename + + (** Save analysis_results into a file *) + let store_analysis_results_to_file (filename : DB.filename) (analysis_results: t) = + Serialization.to_file analysis_results_serializer filename analysis_results + + (** Return an iterator over all the summaries. + If options - load_results or - save_results are used, all the summaries are loaded in memory *) + let get_summary_iterator () = + let iterator_of_summary_list r = + fun f -> list_iter f r in + match !load_analysis_results with + | None -> + begin + match !save_analysis_results with + | None -> + iterator_of_spec_files () + | Some s -> + let r = load_summaries_in_memory () in + store_analysis_results_to_file (DB.filename_from_string s) r; + iterator_of_summary_list r + end + | Some fname -> + begin + match load_analysis_results_from_file (DB.filename_from_string fname) with + | Some r -> + iterator_of_summary_list r + | None -> + L.err "Error: cannot open analysis results file %s@." fname; + exit 0 + end +end + +let compute_top_procedures = ref false (* warning: computing top procedures iterates over summaries twice *) + +let () = + Config.print_using_diff := true; + handle_source_file_copy_option (); + let iterate_summaries = AnalysisResults.get_summary_iterator () in + let filters = + match !analyzer with + | None -> Inferconfig.do_not_filter + | Some analyzer -> Inferconfig.create_filters analyzer in + + let pdflatex fname = ignore (Sys.command ("pdflatex " ^ fname)) in + do_outf latex (fun outf -> begin_latex_file outf.fmt); + do_outf procs_csv (fun outf -> ProcsCsv.pp_header outf.fmt ()); + do_outf procs_xml (fun outf -> ProcsXml.pp_procs_open outf.fmt ()); + do_outf calls_csv (fun outf -> CallsCsv.pp_header outf.fmt ()); + do_outf bugs_csv (fun outf -> BugsCsv.pp_header outf.fmt ()); + do_outf bugs_json (fun outf -> BugsJson.pp_json_open outf.fmt ()); + do_outf bugs_xml (fun outf -> BugsXml.pp_bugs_open outf.fmt ()); + do_outf report (fun outf -> Report.pp_header outf.fmt ()); + let top_proc = TopProcedures.create () in + if !compute_top_procedures && (!procs_csv != None || !procs_xml != None) then iterate_summaries (TopProcedures.process_summary top_proc); + let top_proc_set = TopProcedures.top_set top_proc in + let linereader = Printer.LineReader.create () in + let stats = Stats.create () in + iterate_summaries (process_summary filters linereader stats top_proc_set); + if !unit_test then UnitTest.print_unit_test_main (); + do_outf procs_csv close_outf; + do_outf procs_xml (fun outf -> ProcsXml.pp_procs_close outf.fmt (); close_outf outf); + do_outf bugs_csv close_outf; + do_outf bugs_json (fun outf -> BugsJson.pp_json_close outf.fmt (); close_outf outf); + do_outf bugs_json close_outf; + do_outf calls_csv close_outf; + do_outf bugs_txt close_outf; + do_outf bugs_xml (fun outf -> BugsXml.pp_bugs_close outf.fmt (); close_outf outf); + do_outf latex (fun outf -> + Latex.pp_end outf.fmt (); + close_outf outf; + pdflatex outf.fname; + let pdf_name = (Filename.chop_extension outf.fname) ^ ".pdf" in + ignore (Sys.command ("open " ^ pdf_name))); + do_outf report (fun outf -> F.fprintf outf.fmt "%a@?" Report.pp_stats stats; close_outf outf); + if !precondition_stats then PreconditionStats.pp_stats () diff --git a/infer/src/backend/interproc.ml b/infer/src/backend/interproc.ml new file mode 100644 index 000000000..277f3f981 --- /dev/null +++ b/infer/src/backend/interproc.ml @@ -0,0 +1,1299 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +(** Interprocedural Analysis *) + +module L = Logging +module F = Format +open Utils (* No abbreviation for Utils, as every module can depend on it *) + +type visitednode = + { node: Cfg.Node.t; visits: int } + +(** Set of nodes with number of visits *) +module NodeVisitSet = + Set.Make(struct + type t = visitednode + let compare_ids n1 n2 = Cfg.Node.compare n2 n1 (* higher id is better *) + let compare_distance_to_exit { node = n1 } { node = n2 } = (* smaller means higher priority *) + let n = match Cfg.Node.get_distance_to_exit n1, Cfg.Node.get_distance_to_exit n2 with + | None, None -> 0 + | None, Some _ -> 1 + | Some _, None -> - 1 + | Some d1, Some d2 -> int_compare d1 d2 (* shorter distance to exit is better *) in + if n <> 0 then n else compare_ids n1 n2 + let compare_number_of_visits x1 x2 = + let n = int_compare x1.visits x2.visits in (* visited fewer times is better *) + if n <> 0 then n else compare_distance_to_exit x1 x2 + let compare x1 x2 = + if !Config.footprint then + match !Config.worklist_mode with + | 0 -> compare_ids x1.node x2.node + | 1 -> compare_distance_to_exit x1 x2 + | _ -> compare_number_of_visits x1 x2 + else compare_ids x1.node x2.node + end) + +(* =============== START of module Worklist =============== *) +module Worklist = struct + module NodeMap = Map.Make(Cfg.Node) + let worklist : NodeVisitSet.t ref = ref NodeVisitSet.empty + let map : int NodeMap.t ref = ref NodeMap.empty + + let reset pdesc = + worklist := NodeVisitSet.empty; + map := NodeMap.empty; + Cfg.Procdesc.compute_distance_to_exit_node pdesc + + let is_empty () : bool = + NodeVisitSet.is_empty !worklist + + let add (node : Cfg.node) : unit = + let visits = try NodeMap.find node !map with Not_found -> 0 in + worklist := NodeVisitSet.add { node = node; visits = visits } !worklist + + (** remove the minimum element from the worklist, and increase its number of visits *) + let remove () : Cfg.Node.t = + try + let min = NodeVisitSet.min_elt !worklist in + worklist := NodeVisitSet.remove min !worklist; + map := NodeMap.add min.node (min.visits + 1) !map; (* increase the visits *) + min.node + with Not_found -> begin + L.out "@\n...Work list is empty! Impossible to remove edge...@\n"; + assert false + end +end +(* =============== END of module Worklist =============== *) + +module Join_table : sig + val reset : unit -> unit + val find : int -> Paths.PathSet.t + val put : int -> Paths.PathSet.t -> unit +end = struct + let table : (int, Paths.PathSet.t) Hashtbl.t = Hashtbl.create 1024 + let reset () = Hashtbl.clear table + let find i = + try Hashtbl.find table i with Not_found -> Paths.PathSet.empty + let put i dset = Hashtbl.replace table i dset +end + +let path_set_visited : (int, Paths.PathSet.t) Hashtbl.t = Hashtbl.create 1024 + +let path_set_todo : (int, Paths.PathSet.t) Hashtbl.t = Hashtbl.create 1024 + +let path_set_worklist_reset pdesc = + State.reset (); + Hashtbl.clear path_set_visited; + Hashtbl.clear path_set_todo; + Join_table.reset (); + Worklist.reset pdesc + +let htable_retrieve (htable : (int, Paths.PathSet.t) Hashtbl.t) (key : int) : Paths.PathSet.t = + try + Hashtbl.find htable key + with Not_found -> + Hashtbl.replace htable key Paths.PathSet.empty; + Paths.PathSet.empty + +let path_set_get_visited (sid: int) : Paths.PathSet.t = + htable_retrieve path_set_visited sid + +(** Add [d] to the pathset todo at [node] returning true if changed *) +let path_set_put_todo (node: Cfg.node) (d: Paths.PathSet.t) : bool = + let changed = + if Paths.PathSet.is_empty d then false + else + let sid = Cfg.Node.get_id node in + let old_todo = htable_retrieve path_set_todo sid in + let old_visited = htable_retrieve path_set_visited sid in + let d' = Paths.PathSet.diff d old_visited in (* differential fixpoint *) + let todo_new = Paths.PathSet.union old_todo d' in + Hashtbl.replace path_set_todo sid todo_new; + not (Paths.PathSet.equal old_todo todo_new) in + changed + +let path_set_checkout_todo (node: Cfg.node) : Paths.PathSet.t = + try + let sid = Cfg.Node.get_id node in + let todo = Hashtbl.find path_set_todo sid in + Hashtbl.replace path_set_todo sid Paths.PathSet.empty; + let visited = Hashtbl.find path_set_visited sid in + let new_visited = Paths.PathSet.union visited todo in + Hashtbl.replace path_set_visited sid new_visited; + todo + with Not_found -> + L.out "@.@.ERROR: could not find todo for node %a@.@." Cfg.Node.pp node; + assert false + +(* =============== END of the edge_set object =============== *) + +(* =============== START: Print a complete path in a dotty file =============== *) + +let pp_path_dotty f path = + let get_node_id_from_path p = + let node = Paths.Path.curr_node p in + Cfg.Node.get_id node in + let count = ref 0 in + let prev_n_id = ref 0 in + let curr_n_id = ref 0 in + prev_n_id:=- 1; + let g level p session exn_opt = + let curr_node = Paths.Path.curr_node p in + let curr_path_set = htable_retrieve path_set_visited (Cfg.Node.get_id curr_node) in + let plist = Paths.PathSet.filter_path p curr_path_set in + incr count; + curr_n_id:= (get_node_id_from_path p); + Dotty.pp_dotty_prop_list_in_path f plist !prev_n_id !curr_n_id; + L.out "@.Path #%d: %a@." !count Paths.Path.pp p; + prev_n_id:=!curr_n_id in + L.out "@.@.Printing Path: %a to file error_path.dot@." Paths.Path.pp path; + Dotty.reset_proposition_counter (); + Dotty.reset_dotty_spec_counter (); + F.fprintf f "@\n@\n@\ndigraph main { \nnode [shape=box]; @\n"; + F.fprintf f "@\n compound = true; @\n"; + (* F.fprintf f "@\n size=\"12,7\"; ratio=fill; @\n"; *) + Paths.Path.iter_longest_sequence g None path; + F.fprintf f "@\n}" + +let pp_complete_path_dotty_file = + let counter = ref 0 in + fun path -> + incr counter; + let outc = open_out ("error_path" ^ string_of_int !counter ^ ".dot") in + let fmt = F.formatter_of_out_channel outc in + F.fprintf fmt "#### Dotty version: ####@.%a@.@." pp_path_dotty path; + close_out outc + +(* =============== END: Print a complete path in a dotty file =============== *) + +let collect_do_abstract_pre pname tenv (pset : Propset.t) : Propset.t = + if !Config.footprint then begin + Config.footprint := false; + let pset' = Abs.lifted_abstract pname tenv pset in + Config.footprint := true; + pset' + end + else Abs.lifted_abstract pname tenv pset + +let collect_do_abstract_post pname tenv (pathset : Paths.PathSet.t) : Paths.PathSet.t = + let abs_option p = + if Prover.check_inconsistency p then None + else Some (Abs.abstract pname tenv p) in + if !Config.footprint then begin + Config.footprint := false; + let pathset' = Paths.PathSet.map_option abs_option pathset in + Config.footprint := true; + pathset' + end + else Paths.PathSet.map_option abs_option pathset + +let do_join_pre plist = + Dom.proplist_collapse_pre plist + +let do_join_post pname tenv (pset: Paths.PathSet.t) = + if !Config.spec_abs_level <= 0 then + Dom.pathset_collapse pset + else + Dom.pathset_collapse (Dom.pathset_collapse_impl pname tenv pset) + +let do_meet_pre pset = + if !Config.meet_level > 0 then + Dom.propset_meet_generate_pre pset + else + Propset.to_proplist pset + +(** Find the preconditions in the current spec table, apply meet then join, and return the joined preconditions *) +let collect_preconditions pname tenv proc_name : Prop.normal Specs.Jprop.t list = + let collect_do_abstract_one tenv prop = + if !Config.footprint then begin + Config.footprint := false; + let prop' = Abs.abstract_no_symop tenv prop in + Config.footprint := true; + prop' end + else Abs.abstract_no_symop tenv prop in + let pres = list_map (fun spec -> Specs.Jprop.to_prop spec.Specs.pre) (Specs.get_specs proc_name) in + let pset = Propset.from_proplist pres in + let pset' = + let f p = Prop.prop_normal_vars_to_primed_vars p in + Propset.map f pset in + + L.d_strln ("#### Extracted footprint of " ^ Procname.to_string proc_name ^ ": ####"); + L.d_increase_indent 1; Propset.d Prop.prop_emp pset'; L.d_decrease_indent 1; L.d_ln (); + L.d_ln (); + let pset'' = collect_do_abstract_pre pname tenv pset' in + let plist_meet = do_meet_pre pset'' in + L.d_strln ("#### Footprint of " ^ Procname.to_string proc_name ^ " after Meet ####"); + L.d_increase_indent 1; Propgraph.d_proplist Prop.prop_emp plist_meet; L.d_decrease_indent 1; L.d_ln (); + L.d_ln (); + L.d_increase_indent 2; (* Indent for the join output *) + let jplist = do_join_pre plist_meet in + L.d_decrease_indent 2; L.d_ln (); + L.d_strln ("#### Footprint of " ^ Procname.to_string proc_name ^ " after Join ####"); + L.d_increase_indent 1; Specs.Jprop.d_list false jplist; L.d_decrease_indent 1; L.d_ln (); + let jplist' = list_map (Specs.Jprop.map Prop.prop_rename_primed_footprint_vars) jplist in + L.d_strln ("#### Renamed footprint of " ^ Procname.to_string proc_name ^ ": ####"); + L.d_increase_indent 1; Specs.Jprop.d_list false jplist'; L.d_decrease_indent 1; L.d_ln (); + let jplist'' = + let f p = Prop.prop_primed_vars_to_normal_vars (collect_do_abstract_one pname tenv p) in + list_map (Specs.Jprop.map f) jplist' in + L.d_strln ("#### Abstracted footprint of " ^ Procname.to_string proc_name ^ ": ####"); + L.d_increase_indent 1; Specs.Jprop.d_list false jplist''; L.d_decrease_indent 1; L.d_ln(); + jplist'' + +(* =============== START of symbolic execution =============== *) + +(* propagate a set of results to the given node *) +let propagate proc_desc is_exception (pset: Paths.PathSet.t) (curr_node: Cfg.node) = + let edgeset_todo = + let f prop path edgeset_curr = (** prop must be a renamed prop by the invariant preserved by PropSet *) + let exn_opt = + if is_exception then Some (Tabulation.prop_get_exn_name proc_desc prop) + else None in + Paths.PathSet.add_renamed_prop prop (Paths.Path.extend curr_node exn_opt (State.get_session ()) path) edgeset_curr in + Paths.PathSet.fold f pset Paths.PathSet.empty in + let changed = path_set_put_todo curr_node edgeset_todo in + if changed then (Worklist.add curr_node) + +(* propagate a set of results, including exceptions and divergence *) +let propagate_nodes_divergence + tenv (pdesc: Cfg.Procdesc.t) (pset: Paths.PathSet.t) + (path: Paths.Path.t) (kind_curr_node : Cfg.Node.nodekind) (_succ_nodes: Cfg.node list) + (exn_nodes: Cfg.node list) = + let pname = Cfg.Procdesc.get_proc_name pdesc in + let pset_exn, pset_ok = Paths.PathSet.partition (Tabulation.prop_is_exn pdesc) pset in + let succ_nodes = match State.get_goto_node () with (* handle Sil.Goto_node target, if any *) + | Some node_id -> + list_filter (fun n -> Cfg.Node.get_id n = node_id) _succ_nodes + | None -> _succ_nodes in + if !Config.footprint && not (Paths.PathSet.is_empty (State.get_diverging_states_node ())) then + begin + if !Config.developer_mode then Errdesc.warning_err (State.get_loc ()) "Propagating Divergence@."; + let exit_node = Cfg.Procdesc.get_exit_node pdesc in + let diverging_states = State.get_diverging_states_node () in + let prop_incons = + let mk_incons prop = + let p_abs = Abs.abstract pname tenv prop in + let p_zero = Prop.replace_sigma [] (Prop.replace_sub Sil.sub_empty p_abs) in + Prop.normalize (Prop.replace_pi [Sil.Aneq (Sil.exp_zero, Sil.exp_zero)] p_zero) in + Paths.PathSet.map mk_incons diverging_states in + (L.d_strln_color Orange) "Propagating Divergence -- diverging states:"; + Propgraph.d_proplist Prop.prop_emp (Paths.PathSet.to_proplist prop_incons); L.d_ln (); + propagate pdesc false prop_incons exit_node + end; + list_iter (propagate pdesc false pset_ok) succ_nodes; + list_iter (propagate pdesc true pset_exn) exn_nodes + +(* ===================== END of symbolic execution ===================== *) + +(* =============== START of forward_tabulate =============== *) + +(** Symbolic execution for a Join node *) +let do_symexec_join proc_desc tenv curr_node (edgeset_todo : Paths.PathSet.t) = + let curr_pdesc = Cfg.Node.get_proc_desc curr_node in + let curr_pname = Cfg.Procdesc.get_proc_name curr_pdesc in + let curr_id = Cfg.Node.get_id curr_node in + let succ_nodes = Cfg.Node.get_succs curr_node in + let new_dset = edgeset_todo in + let old_dset = Join_table.find curr_id in + let old_dset', new_dset' = Dom.pathset_join curr_pname tenv old_dset new_dset in + Join_table.put curr_id (Paths.PathSet.union old_dset' new_dset'); + list_iter (fun node -> + Paths.PathSet.iter (fun prop path -> + State.set_path path None; + propagate proc_desc false (Paths.PathSet.from_renamed_list [(prop, path)]) node) + new_dset') succ_nodes + +let prop_max_size = ref (0, Prop.prop_emp) +let prop_max_chain_size = ref (0, Prop.prop_emp) + +(* Check if the prop exceeds the current max *) +let check_prop_size p path = + let size = Prop.Metrics.prop_size p in + if size > fst !prop_max_size then + (prop_max_size := (size, p); + L.d_strln ("Prop with new max size " ^ string_of_int size ^ ":"); + Prop.d_prop p; + L.d_ln ()) + +(* Check prop size and filter out possible unabstracted lists *) +let check_prop_size edgeset_todo = + if !Config.monitor_prop_size then Paths.PathSet.iter check_prop_size edgeset_todo + +let reset_prop_metrics () = + prop_max_size := (0, Prop.prop_emp); + prop_max_chain_size := (0, Prop.prop_emp) + +(** dump a path *) +let d_path (path, pos_opt) = + let step = ref 0 in + let prop_last_step = ref Prop.prop_emp in (* if the last step had a singleton prop, store it here *) + let f level p session exn_opt = + let curr_node = Paths.Path.curr_node p in + let curr_path_set = htable_retrieve path_set_visited (Cfg.Node.get_id curr_node) in + let plist = Paths.PathSet.filter_path p curr_path_set in + incr step; + (* Propset.pp_proplist_dotty_file ("path" ^ (string_of_int !count) ^ ".dot") plist; *) + L.d_strln ("Path Step #" ^ string_of_int !step ^ + " node " ^ string_of_int (Cfg.Node.get_id curr_node) ^ + " session " ^ string_of_int session ^ ":"); + Propset.d !prop_last_step (Propset.from_proplist plist); L.d_ln (); + Cfg.Node.d_instrs true None curr_node; L.d_ln (); L.d_ln (); + prop_last_step := (match plist with | [prop] -> prop | _ -> Prop.prop_emp) in + L.d_str "Path: "; Paths.Path.d_stats path; L.d_ln (); + Paths.Path.d path; L.d_ln (); + (* pp_complete_path_dotty_file path; *) + (* if !Config.write_dotty then Dotty.print_icfg_dotty (list_rev (get_edges path)) *) + Paths.Path.iter_longest_sequence f pos_opt path + +exception RE_EXE_ERROR + +let do_before_node session node = + let loc = Cfg.Node.get_loc node in + let proc_desc = Cfg.Node.get_proc_desc node in + let proc_name = Cfg.Procdesc.get_proc_name proc_desc in + State.set_node node; + State.set_session session; + L.reset_delayed_prints (); + Printer.start_session node loc proc_name session + +let do_after_node node = Printer.finish_session node + +(** Return the list of normal ids occurring in the instructions *) +let instrs_get_normal_vars instrs = + let fav = Sil.fav_new () in + let do_instr instr = + let do_e e = Sil.exp_fav_add fav e in + let exps = Sil.instr_get_exps instr in + list_iter do_e exps in + list_iter do_instr instrs; + Sil.fav_filter_ident fav Ident.is_normal; + Sil.fav_to_list fav + +(* checks that boolean conditions on a conditional are assignment *) +(* The check is done as follows: we check that the successors or a node that make an *) +(* set instruction are prune nodes, they are all at the same location and the condition on*) +(* which they prune is the variable (or the negation) which was set in the set instruction *) +(* we exclude function calls: if (g(x,y)) ....*) +(* we check that prune nodes have simple guards: a var or its negation*) +let check_assignement_guard node = + let pdesc = Cfg.Node.get_proc_desc node in + let pname = Cfg.Procdesc.get_proc_name pdesc in + let verbose = false in + let node_contains_call n = + let instrs = Cfg.Node.get_instrs n in + let is_call = function + | Sil.Call _ -> true + | _ -> false in + list_exists is_call instrs in + let is_set_instr i = + match i with + | Sil.Set _ -> true + | _ -> false in + let is_prune_instr i = + match i with + | Sil.Prune _ -> true + | _ -> false in + let is_letderef_instr i = + match i with + | Sil.Letderef _ -> true + | _ -> false in + let is_cil_tmp e = + match e with + | Sil.Lvar pv -> + Errdesc.pvar_is_cil_tmp pv + | _ -> false in + let is_edg_tmp e = + match e with + | Sil.Lvar pv -> + Errdesc.pvar_is_edg_tmp pv + | _ -> false in + let succs = Cfg.Node.get_succs node in + let l_node = Cfg.Node.get_last_loc node in + (* e is prune if in all successors prune nodes we have for some temp n$1: *) + (* n$1=*&e;Prune(n$1) or n$1=*&e;Prune(!n$1) *) + let is_prune_exp e = + let prune_var n = + let ins = Cfg.Node.get_instrs n in + let pi = list_filter is_prune_instr ins in + let leti = list_filter is_letderef_instr ins in + match pi, leti with + | [Sil.Prune (Sil.Var(e1), _, _, _)], [Sil.Letderef(e2, e', _, _)] + | [Sil.Prune (Sil.UnOp(Sil.LNot, Sil.Var(e1), _), _, _, _)], [Sil.Letderef(e2, e', _, _)] when (Ident.equal e1 e2) -> + if verbose then L.d_strln ("Found "^(Sil.exp_to_string e')^" as prune var"); + [e'] + | _ -> [] in + let prune_vars = list_flatten(list_map (fun n -> prune_var n) succs) in + list_for_all (fun e' -> Sil.exp_equal e' e) prune_vars in + let succs_loc = list_map (fun n -> Cfg.Node.get_loc n) succs in + let succs_are_all_prune_nodes () = + list_for_all (fun n -> match Cfg.Node.get_kind n with + | Cfg.Node.Prune_node(_) -> true + | _ -> false) succs in + let succs_same_loc_as_node () = + if verbose then (L.d_str ("LOCATION NODE: line: "^(string_of_int l_node.Sil.line)^" nLOC: "^(string_of_int l_node.Sil.nLOC)); L.d_strln " "); + list_for_all (fun l -> + if verbose then (L.d_str ("LOCATION l: line: "^(string_of_int l.Sil.line)^" nLOC: "^(string_of_int l.Sil.nLOC)); L.d_strln " "); + Sil.loc_equal l l_node) succs_loc in + let succs_have_simple_guards () = (* check that the guards of the succs are a var or its negation *) + let check_instr = function + | Sil.Prune (Sil.Var _, _, _, _) -> true + | Sil.Prune (Sil.UnOp(Sil.LNot, Sil.Var _, _), _, _, _) -> true + | Sil.Prune _ -> false + | _ -> true in + let check_guard n = + list_for_all check_instr (Cfg.Node.get_instrs n) in + list_for_all check_guard succs in + if !Sil.curr_language = Sil.C_CPP && succs_are_all_prune_nodes () && succs_same_loc_as_node () && succs_have_simple_guards () then + (let instr = Cfg.Node.get_instrs node in + match succs_loc with + | loc_succ:: _ -> (* at this point all successors are at the same location, so we can take the first*) + let set_instr_at_succs_loc = list_filter (fun i -> (Sil.loc_equal (Sil.instr_get_loc i) loc_succ) && is_set_instr i) instr in + (match set_instr_at_succs_loc with + | [Sil.Set(e, _, _, _)] -> (* we now check if e is the same expression used to prune*) + if (is_prune_exp e) && not ((node_contains_call node) && (is_cil_tmp e)) && not (is_edg_tmp e) then ( + let desc = Errdesc.explain_condition_is_assignment l_node in + let exn = Exceptions.Condition_is_assignment (desc, try assert false with Assert_failure x -> x) in + let pre_opt = State.get_normalized_pre (Abs.abstract_no_symop pname) in + Reporting.log_warning pname ~loc: (Some l_node) ~pre: pre_opt exn + ) + else () + | _ -> ()) + | _ -> if verbose then L.d_strln "NOT FOUND loc_succ" + ) else () + +(** Perform symbolic execution for a node starting from an initial prop *) +let do_symbolic_execution handle_exn cfg tenv + (node : Cfg.node) (prop: Prop.normal Prop.t) (path : Paths.Path.t) = + State.mark_execution_start node; + check_assignement_guard node; + let instrs = Cfg.Node.get_instrs node in + Ident.update_name_generator (instrs_get_normal_vars instrs); (* fresh normal vars must be fresh w.r.t. instructions *) + let pdesc = Cfg.Node.get_proc_desc node in + let pset = + SymExec.lifted_sym_exec handle_exn cfg tenv pdesc + (Paths.PathSet.from_renamed_list [(prop, path)]) node instrs in + L.d_strln ".... After Symbolic Execution ...."; + Propset.d prop (Paths.PathSet.to_propset pset); + L.d_ln (); L.d_ln(); + State.mark_execution_end node; + pset + +let mark_visited summary node = + let id = Cfg.Node.get_id node in + let stats = summary.Specs.stats in + if !Config.footprint + then stats.Specs.nodes_visited_fp <- IntSet.add id stats.Specs.nodes_visited_fp + else stats.Specs.nodes_visited_re <- IntSet.add id stats.Specs.nodes_visited_re + +let forward_tabulate cfg tenv = + let handled_some_exception = ref false in + let handle_exn curr_node exn = + let curr_pdesc = Cfg.Node.get_proc_desc curr_node in + let curr_pname = Cfg.Procdesc.get_proc_name curr_pdesc in + Exceptions.print_exception_html "Failure of symbolic execution: " exn; + let pre_opt = (* precondition leading to error, if any *) + State.get_normalized_pre (Abs.abstract_no_symop curr_pname) in + (match pre_opt with + | Some pre -> + L.d_strln "Precondition:"; Prop.d_prop pre; L.d_ln () + | None -> ()); + L.d_strln "SIL INSTR:"; + Cfg.Node.d_instrs ~sub_instrs: true (State.get_instr ()) curr_node; L.d_ln (); + Reporting.log_error ~pre: pre_opt curr_pname exn; + State.mark_instr_fail pre_opt exn; + handled_some_exception := true in + + while not (Worklist.is_empty ()) do + let curr_node = Worklist.remove () in + let kind_curr_node = Cfg.Node.get_kind curr_node in + let sid_curr_node = Cfg.Node.get_id curr_node in + let proc_desc = Cfg.Node.get_proc_desc curr_node in + let proc_name = Cfg.Procdesc.get_proc_name proc_desc in + let summary = Specs.get_summary_unsafe proc_name in + mark_visited summary curr_node; (* mark nodes visited in fp and re phases *) + let session = + incr summary.Specs.sessions; + !(summary.Specs.sessions) in + do_before_node session curr_node; + let pathset_todo = path_set_checkout_todo curr_node in + let succ_nodes = Cfg.Node.get_succs curr_node in + let exn_nodes = Cfg.Node.get_exn curr_node in + let exe_iter f pathset = + let ps_size = Paths.PathSet.size pathset in + let cnt = ref 0 in + let exe prop path = + State.set_path path None; + incr cnt; + f prop path !cnt ps_size in + Paths.PathSet.iter exe pathset in + let log_string proc_name = + let phase_string = (if Specs.get_phase proc_name == Specs.FOOTPRINT then "FP" else "RE") in + let summary = Specs.get_summary_unsafe proc_name in + let timestamp = Specs.get_timestamp summary in + F.sprintf "[%s:%d] %s" phase_string timestamp (Procname.to_string proc_name) in + let doit () = + handled_some_exception := false; + check_prop_size pathset_todo; + L.d_strln ("**** " ^ (log_string proc_name) ^ " " ^ + "Node: " ^ string_of_int sid_curr_node ^ ", " ^ + "Procedure: " ^ Procname.to_string proc_name ^ ", " ^ + "Session: " ^ string_of_int session ^ ", " ^ + "Todo: " ^ string_of_int (Paths.PathSet.size pathset_todo) ^ " ****"); + L.d_increase_indent 1; + Propset.d Prop.prop_emp (Paths.PathSet.to_propset pathset_todo); + L.d_strln ".... Instructions: .... "; + Cfg.Node.d_instrs ~sub_instrs: true (State.get_instr ()) curr_node; + L.d_ln (); L.d_ln (); + + match kind_curr_node with + | Cfg.Node.Join_node -> do_symexec_join proc_desc tenv curr_node pathset_todo + | Cfg.Node.Stmt_node _ + | Cfg.Node.Prune_node _ + | Cfg.Node.Exit_node _ + | Cfg.Node.Skip_node _ + | Cfg.Node.Start_node _ -> + exe_iter + (fun prop path cnt num_paths -> + try + L.d_strln ("Processing prop " ^ string_of_int cnt ^ "/" ^ string_of_int num_paths); + L.d_increase_indent 1; + State.reset_diverging_states_goto_node (); + let pset = + do_symbolic_execution (handle_exn curr_node) cfg tenv curr_node prop path in + L.d_decrease_indent 1; L.d_ln(); + propagate_nodes_divergence tenv proc_desc pset path kind_curr_node succ_nodes exn_nodes; + with exn when Exceptions.handle_exception exn && !Config.footprint -> + handle_exn curr_node exn; + if !Config.nonstop then propagate_nodes_divergence tenv proc_desc (Paths.PathSet.from_renamed_list [(prop, path)]) path kind_curr_node succ_nodes exn_nodes; + L.d_decrease_indent 1; L.d_ln ()) + pathset_todo in + try begin + doit(); + if !handled_some_exception then Printer.force_delayed_prints (); + do_after_node curr_node + end + with + | exn when Exceptions.handle_exception exn -> + handle_exn curr_node exn; + Printer.force_delayed_prints (); + do_after_node curr_node; + if not !Config.footprint then raise RE_EXE_ERROR + done; + L.d_strln ".... Work list empty. Stop ...."; L.d_ln () + +(** remove locals and formals, and check if the address of a stack variable is left in the result *) +let remove_locals_formals_and_check pdesc p = + let pname = Cfg.Procdesc.get_proc_name pdesc in + let pvars, p' = Cfg.remove_locals_formals pdesc p in + let check_pvar pvar = + let loc = Cfg.Node.get_loc (Cfg.Procdesc.get_exit_node pdesc) in + let dexp_opt, _ = Errdesc.vpath_find p (Sil.Lvar pvar) in + let desc = Errdesc.explain_stack_variable_address_escape loc pvar dexp_opt in + let exn = Exceptions.Stack_variable_address_escape (desc, try assert false with Assert_failure x -> x) in + Reporting.log_warning pname exn in + list_iter check_pvar pvars; + p' + +(* Collect the analysis results for the exit node *) +let collect_analysis_result pdesc : Paths.PathSet.t = + let exit_node = Cfg.Procdesc.get_exit_node pdesc in + let exit_sid = Cfg.Node.get_id exit_node in + let pathset = path_set_get_visited exit_sid in + Paths.PathSet.map (remove_locals_formals_and_check pdesc) pathset + +module Pmap = Map.Make (struct type t = Prop.normal Prop.t let compare = Prop.prop_compare end) + +let vset_ref_add_path vset_ref path = + Paths.Path.iter_all_nodes_nocalls (fun n -> vset_ref := Cfg.NodeSet.add n !vset_ref) path + +let vset_ref_add_pathset vset_ref pathset = + Paths.PathSet.iter (fun p path -> vset_ref_add_path vset_ref path) pathset + +let compute_visited vset = + let res = ref Specs.Visitedset.empty in + let node_get_all_lines n = + let node_loc = Cfg.Node.get_loc n in + let instrs_loc = list_map Sil.instr_get_loc (Cfg.Node.get_instrs n) in + let lines = list_map (fun loc -> loc.Sil.line) (node_loc :: instrs_loc) in + list_remove_duplicates int_compare (list_sort int_compare lines) in + let do_node n = res := Specs.Visitedset.add (Cfg.Node.get_id n, node_get_all_lines n) !res in + Cfg.NodeSet.iter do_node vset; + !res + +(** Extract specs from a pathset *) +let extract_specs tenv pdesc pathset : Prop.normal Specs.spec list = + let pname = Cfg.Procdesc.get_proc_name pdesc in + let sub = + let fav = Sil.fav_new () in + Paths.PathSet.iter (fun prop path -> Prop.prop_fav_add fav prop) pathset; + let sub_list = list_map (fun id -> (id, Sil.Var (Ident.create_fresh (Ident.knormal)))) (Sil.fav_to_list fav) in + Sil.sub_of_list sub_list in + let pre_post_visited_list = + let pplist = Paths.PathSet.elements pathset in + let f (prop, path) = + let _, prop' = Cfg.remove_locals_formals pdesc prop in + (* let () = L.out "@.BEFORE abs:@.$%a@." (Prop.pp_prop Utils.pe_text) prop' in *) + let prop'' = Abs.abstract pname tenv prop' in + (* let () = L.out "@.AFTER abs:@.$%a@." (Prop.pp_prop Utils.pe_text) prop'' in *) + let pre, post = Prop.extract_spec prop'' in + let pre' = Prop.normalize (Prop.prop_sub sub pre) in + let post' = + if Prover.check_inconsistency_base prop then None + else Some (Prop.normalize (Prop.prop_sub sub post), path) in + let visited = + let vset_ref = ref Cfg.NodeSet.empty in + vset_ref_add_path vset_ref path; + compute_visited !vset_ref in + (pre', post', visited) in + list_map f pplist in + let pre_post_map = + let add map (pre, post, visited) = + let current_posts, current_visited = try Pmap.find pre map with Not_found -> (Paths.PathSet.empty, Specs.Visitedset.empty) in + let new_posts = match post with + | None -> current_posts + | Some (post, path) -> Paths.PathSet.add_renamed_prop post path current_posts in + let new_visited = Specs.Visitedset.union visited current_visited in + Pmap.add pre (new_posts, new_visited) map in + list_fold_left add Pmap.empty pre_post_visited_list in + let specs = ref [] in + let add_spec pre ((posts : Paths.PathSet.t), visited) = + let posts' = + list_map + (fun (p, path) -> (Cfg.remove_seed_vars p, path)) + (Paths.PathSet.elements (do_join_post pname tenv posts)) in + let spec = + { Specs.pre = Specs.Jprop.Prop (1, pre); + Specs.posts = posts'; + Specs.visited = visited } in + specs := spec :: !specs in + Pmap.iter add_spec pre_post_map; + !specs + +let collect_postconditions tenv pdesc : Paths.PathSet.t * Specs.Visitedset.t = + let pname = Cfg.Procdesc.get_proc_name pdesc in + let pathset = collect_analysis_result pdesc in + L.d_strln ("#### [FUNCTION " ^ Procname.to_string pname ^ "] Analysis result ####"); + Propset.d Prop.prop_emp (Paths.PathSet.to_propset pathset); + L.d_ln (); + let res = + try + let pathset = collect_do_abstract_post pname tenv pathset in + let pathset_diverging = State.get_diverging_states_proc () in + let visited = + let vset_ref = ref Cfg.NodeSet.empty in + vset_ref_add_pathset vset_ref pathset; + vset_ref_add_pathset vset_ref pathset_diverging; (* nodes from diverging states were also visited *) + compute_visited !vset_ref in + do_join_post pname tenv pathset, visited with + | exn when (match exn with Exceptions.Leak _ -> true | _ -> false) -> + raise (Failure "Leak in post collecion") in + L.d_strln ("#### [FUNCTION " ^ Procname.to_string pname ^ "] Postconditions after join ####"); + L.d_increase_indent 1; Propset.d Prop.prop_emp (Paths.PathSet.to_propset (fst res)); L.d_decrease_indent 1; + L.d_ln (); + res + +let create_seed_vars sigma = + let sigma_seed = ref [] in + let hpred_add_seed = function + | Sil.Hpointsto (Sil.Lvar pv, se, typ) -> + sigma_seed := Sil.Hpointsto(Sil.Lvar (Sil.pvar_to_seed pv), se, typ) :: !sigma_seed + | _ -> () in + list_iter hpred_add_seed sigma; + !sigma_seed + +(** Initialize proposition for execution given formal and global +parameters. The footprint is initialized according to the +execution mode. The prop is not necessarily emp, so it +should be incorporated when the footprint is constructed. *) +let prop_init_formals_seed tenv new_formals (prop : 'a Prop.t) : Prop.exposed Prop.t = + let sigma_new_formals = + let do_formal (pv, typ) = + let texp = match !Sil.curr_language with + | Sil.C_CPP -> Sil.Sizeof (typ, Sil.Subtype.exact) + | Sil.Java -> Sil.Sizeof (typ, Sil.Subtype.subtypes) in + Prop.mk_ptsto_lvar (Some tenv) Prop.Fld_init Sil.inst_formal (pv, texp, None) in + list_map do_formal new_formals in + let sigma_seed = + create_seed_vars (Prop.get_sigma prop @ sigma_new_formals) (* formals already there plus new ones *) in + let sigma = sigma_seed @ sigma_new_formals in + let new_pi = + let pi = Prop.get_pi prop in + pi + (* inactive until it becomes necessary, as it pollutes props + let fav_ids = Sil.fav_to_list (Prop.sigma_fav sigma_locals) in + let mk_undef_atom id = Prop.mk_neq (Sil.Var id) (Sil.Const (Sil.Cattribute (Sil.Aundef "UNINITIALIZED"))) in + let pi_undef = list_map mk_undef_atom fav_ids in + pi_undef @ pi *) in + let prop' = + Prop.replace_pi new_pi (Prop.prop_sigma_star prop sigma) in + Prop.replace_sigma_footprint (Prop.get_sigma_footprint prop' @ sigma_new_formals) prop' + +(** Construct an initial prop by extending [prop] with locals, and formals if [add_formals] is true +as well as seed variables *) +let initial_prop tenv (curr_f: Cfg.Procdesc.t) (prop : 'a Prop.t) add_formals : Prop.normal Prop.t = + let construct_decl (x, typ) = + (Sil.mk_pvar (Mangled.from_string x) (Cfg.Procdesc.get_proc_name curr_f), typ) in + let new_formals = + if add_formals + then list_map construct_decl (Cfg.Procdesc.get_formals curr_f) + else [] in (** no new formals added *) + let prop1 = Prop.prop_reset_inst (fun inst_old -> Sil.update_inst inst_old Sil.inst_formal) prop in + let prop2 = prop_init_formals_seed tenv new_formals prop1 in + Prop.prop_rename_primed_footprint_vars (Prop.normalize prop2) + +(** Construct an initial prop from the empty prop *) +let initial_prop_from_emp tenv curr_f = + initial_prop tenv curr_f Prop.prop_emp true + +(** Construct an initial prop from an existing pre with formals *) +let initial_prop_from_pre tenv curr_f pre = + if !Config.footprint then + let vars = Sil.fav_to_list (Prop.prop_fav pre) in + let sub_list = list_map (fun id -> (id, Sil.Var (Ident.create_fresh (Ident.kfootprint)))) vars in + let sub = Sil.sub_of_list sub_list in + let pre2 = Prop.prop_sub sub pre in + let pre3 = Prop.replace_sigma_footprint (Prop.get_sigma pre2) (Prop.replace_pi_footprint (Prop.get_pure pre2) pre2) in + initial_prop tenv curr_f pre3 false + else + initial_prop tenv curr_f pre false + +(** Re-execute one precondition and return some spec if there was no re-execution error. *) +let execute_filter_prop cfg tenv pdesc init_node (precondition : Prop.normal Specs.Jprop.t) +: Prop.normal Specs.spec option = + let proc_name = Cfg.Procdesc.get_proc_name pdesc in + do_before_node 0 init_node; + L.d_strln ("#### Start: RE-execution for " ^ Procname.to_string proc_name ^ " ####"); + L.d_indent 1; + L.d_strln "Precond:"; Specs.Jprop.d_shallow precondition; + L.d_ln (); L.d_ln (); + let init_prop = initial_prop_from_pre tenv pdesc (Specs.Jprop.to_prop precondition) in + let init_edgeset = Paths.PathSet.add_renamed_prop init_prop (Paths.Path.start init_node) Paths.PathSet.empty in + do_after_node init_node; + try + path_set_worklist_reset pdesc; + Worklist.add init_node; + ignore (path_set_put_todo init_node init_edgeset); + forward_tabulate cfg tenv; + do_before_node 0 init_node; + L.d_strln_color Green ("#### Finished: RE-execution for " ^ Procname.to_string proc_name ^ " ####"); + L.d_increase_indent 1; + L.d_strln "Precond:"; Prop.d_prop (Specs.Jprop.to_prop precondition); + L.d_ln (); + let posts, visited = + let pset, visited = collect_postconditions tenv pdesc in + let plist = list_map (fun (p, path) -> (Cfg.remove_seed_vars p, path)) (Paths.PathSet.elements pset) in + plist, visited in + let pre = + let p = Cfg.remove_locals_ret pdesc (Specs.Jprop.to_prop precondition) in + match precondition with + | Specs.Jprop.Prop (n, _) -> Specs.Jprop.Prop (n, p) + | Specs.Jprop.Joined (n, _, jp1, jp2) -> Specs.Jprop.Joined (n, p, jp1, jp2) in + let spec = { Specs.pre = pre; Specs.posts = posts; Specs.visited = visited } in + L.d_decrease_indent 1; + do_after_node init_node; + Some spec + with RE_EXE_ERROR -> + do_before_node 0 init_node; + Printer.force_delayed_prints (); + L.d_strln_color Red ("#### [FUNCTION " ^ Procname.to_string proc_name ^ "] ...ERROR"); + L.d_increase_indent 1; + L.d_strln "when starting from pre:"; + Prop.d_prop (Specs.Jprop.to_prop precondition); + L.d_strln "This precondition is filtered out."; + L.d_decrease_indent 1; + do_after_node init_node; + None + +(** get all the nodes in the current call graph with their defined children *) +let get_procs_and_defined_children call_graph = + list_map (fun (n, ns) -> (n, Procname.Set.elements ns)) (Cg.get_nodes_and_defined_children call_graph) + +let pp_intra_stats cfg proc_desc fmt proc_name = + let nstates = ref 0 in + let nodes = Cfg.Procdesc.get_nodes proc_desc in + list_iter (fun node -> nstates := !nstates + Paths.PathSet.size (path_set_get_visited (Cfg.Node.get_id node))) nodes; + F.fprintf fmt "(%d nodes containing %d states)" (list_length nodes) !nstates + +(** Return functions to perform one phase of the analysis for a procedure. +Given [proc_name], return [do, get_results] where [go ()] performs the analysis phase +and [get_results ()] returns the results computed. +This function is architected so that [get_results ()] can be called even after +[go ()] was interrupted by and exception. *) +let perform_analysis_phase cfg tenv (pname : Procname.t) (pdesc : Cfg.Procdesc.t) +: (unit -> unit) * (unit -> Prop.normal Specs.spec list) = + let start_node = Cfg.Procdesc.get_start_node pdesc in + + let check_recursion_level () = + let summary = Specs.get_summary_unsafe pname in + let recursion_level = Specs.get_timestamp summary in + if recursion_level > !Config.max_recursion then + begin + L.err "Reached maximum level of recursion, raising a Timeout@."; + raise (Timeout_exe (TOrecursion recursion_level)) + end in + + let compute_footprint : (unit -> unit) * (unit -> Prop.normal Specs.spec list) = + let go () = + let init_prop = initial_prop_from_emp tenv pdesc in + let init_props_from_pres = (* use existing pre's (in recursion some might exist) as starting points *) + let specs = Specs.get_specs pname in + let mk_init precondition = (* rename spec vars to footrpint vars, and copy current to footprint *) + initial_prop_from_pre tenv pdesc (Specs.Jprop.to_prop precondition) in + list_map (fun spec -> mk_init spec.Specs.pre) specs in + let init_props = Propset.from_proplist (init_prop :: init_props_from_pres) in + let init_edgeset = + let add pset prop = + Paths.PathSet.add_renamed_prop prop (Paths.Path.start start_node) pset in + Propset.fold add Paths.PathSet.empty init_props in + L.out "@.#### Start: Footprint Computation for %a ####@." Procname.pp pname; + L.d_increase_indent 1; + L.d_strln "initial props ="; + Propset.d Prop.prop_emp init_props; L.d_ln (); L.d_ln(); + L.d_decrease_indent 1; + path_set_worklist_reset pdesc; + check_recursion_level (); + Worklist.add start_node; + Config.arc_mode := Hashtbl.mem (Cfg.Procdesc.get_flags pdesc) Mleak_buckets.objc_arc_flag; + ignore (path_set_put_todo start_node init_edgeset); + forward_tabulate cfg tenv; + in + let get_results () = + State.process_execution_failures Reporting.log_warning pname; + let results = collect_analysis_result pdesc in + L.out "#### [FUNCTION %a] ... OK #####@\n" Procname.pp pname; + L.out "#### Finished: Footprint Computation for %a %a ####@." + Procname.pp pname + (pp_intra_stats cfg pdesc) pname; + L.out "#### [FUNCTION %a] Footprint Analysis result ####@\n%a@." + Procname.pp pname + (Paths.PathSet.pp pe_text) results; + let specs = try extract_specs tenv pdesc results with + | Exceptions.Leak _ -> + let exn = + Exceptions.Internal_error + (Localise.verbatim_desc "Leak_while_collecting_specs_after_footprint") in + let pre_opt = State.get_normalized_pre (Abs.abstract_no_symop pname) in + Reporting.log_error pname ~pre: pre_opt exn; + [] (* retuning no specs *) in + specs in + go, get_results in + + let re_execution proc_name : (unit -> unit) * (unit -> Prop.normal Specs.spec list) = + let candidate_preconditions = list_map (fun spec -> spec.Specs.pre) (Specs.get_specs proc_name) in + let valid_specs = ref [] in + let go () = + L.out "@.#### Start: Re-Execution for %a ####@." Procname.pp proc_name; + check_recursion_level (); + let filter p = + let speco = execute_filter_prop cfg tenv pdesc start_node p in + let is_valid = match speco with + | None -> false + | Some spec -> + valid_specs := !valid_specs @ [spec]; + true in + let outcome = if is_valid then "pass" else "fail" in + L.out "Finished re-execution for precondition %d %a (%s)@." + (Specs.Jprop.to_number p) + (pp_intra_stats cfg pdesc) proc_name + outcome; + speco in + if !Config.undo_join then + ignore (Specs.Jprop.filter filter candidate_preconditions) + else + ignore (list_map filter candidate_preconditions) in + let get_results () = + let specs = !valid_specs in + L.out "#### [FUNCTION %a] ... OK #####@\n" Procname.pp proc_name; + L.out "#### Finished: Re-Execution for %a ####@." Procname.pp proc_name; + let valid_preconditions = list_map (fun spec -> spec.Specs.pre) specs in + let filename = DB.Results_dir.path_to_filename DB.Results_dir.Abs_source_dir [(Procname.to_filename proc_name)] in + if !Config.write_dotty then + Dotty.pp_speclist_dotty_file filename specs; + L.out "@.@.================================================"; + L.out "@. *** CANDIDATE PRECONDITIONS FOR %a: " Procname.pp proc_name; + L.out "@.================================================@."; + L.out "@.%a @.@." (Specs.Jprop.pp_list pe_text false) candidate_preconditions; + L.out "@.@.================================================"; + L.out "@. *** VALID PRECONDITIONS FOR %a: " Procname.pp proc_name; + L.out "@.================================================@."; + L.out "@.%a @.@." (Specs.Jprop.pp_list pe_text true) valid_preconditions; + specs in + go, get_results in + + match Specs.get_phase pname with + | Specs.FOOTPRINT -> + compute_footprint + | Specs.RE_EXECUTION -> + re_execution pname + +let set_current_language cfg proc_desc = + let language = (Cfg.Procdesc.get_attributes proc_desc).Sil.language in + Sil.curr_language := language + +(** reset counters before analysing a procedure *) +let reset_global_counters cfg proc_name proc_desc = + Ident.reset_name_generator (); + SymOp.reset_total (); + reset_prop_metrics (); + Abs.abs_rules_reset (); + set_current_language cfg proc_desc + +(* Collect all pairs of the kind (precondition, exception) from a summary *) +let exception_preconditions tenv pdesc summary = + let collect_exceptions pre exns (prop, path) = + if Tabulation.prop_is_exn pdesc prop then + let exn_name = Tabulation.prop_get_exn_name pdesc prop in + if AndroidFramework.is_runtime_exception tenv exn_name then + (pre, exn_name):: exns + else exns + else exns + and collect_errors pre errors (prop, path) = + match Tabulation.lookup_global_errors prop with + | None -> errors + | Some e -> (pre, e) :: errors in + let collect_spec errors spec = + match !Sil.curr_language with + | Sil.Java -> + list_fold_left (collect_exceptions spec.Specs.pre) errors spec.Specs.posts + | Sil.C_CPP -> + list_fold_left (collect_errors spec.Specs.pre) errors spec.Specs.posts in + list_fold_left collect_spec [] (Specs.get_specs_from_payload summary) + + +(* Remove the constrain of the form this != null which is true for all Java virtual calls *) +let remove_this_not_null prop = + let collect_hpred (var_option, hpreds) = function + | Sil.Hpointsto (Sil.Lvar pvar, Sil.Eexp (Sil.Var var, _), _) when Sil.pvar_is_this pvar -> + (Some var, hpreds) + | hpred -> (var_option, hpred:: hpreds) in + let collect_atom var atoms = function + | Sil.Aneq (Sil.Var v, e) + when Ident.equal v var && Sil.exp_equal e Sil.exp_null -> atoms + | a -> a:: atoms in + match list_fold_left collect_hpred (None, []) (Prop.get_sigma prop) with + | None, _ -> prop + | Some var, filtered_hpreds -> + let filtered_atoms = + list_fold_left (collect_atom var) [] (Prop.get_pi prop) in + let prop' = Prop.replace_pi filtered_atoms Prop.prop_emp in + let prop'' = Prop.replace_sigma filtered_hpreds prop' in + Prop.normalize prop'' + + +(** Detects if there are specs of the form {precondition} proc {runtime exception} and report +an error in that case, generating the trace that lead to the runtime exception if the method is +called in the context { precondition } *) +let report_runtime_exceptions tenv cfg proc_desc summary = + let proc_name = Specs.get_proc_name summary in + let is_public_method = + (Specs.get_attributes summary).Sil.access = Sil.Public in + let is_main = + is_public_method + (* TODO (#4559939): add check for static method *) + && Procname.is_java proc_name + && (Procname.java_get_method proc_name) = "main" in + let is_annotated = + let annotated_signature = + Annotations.get_annotated_signature + Specs.proc_get_method_annotation proc_desc proc_name in + let ret_annotation, _ = annotated_signature.Annotations.ret in + Annotations.ia_is_verify ret_annotation in + let is_unavoidable pre = + let prop = remove_this_not_null (Specs.Jprop.to_prop pre) in + match Prop.CategorizePreconditions.categorize [prop] with + | Prop.CategorizePreconditions.NoPres + | Prop.CategorizePreconditions.Empty -> true + | _ -> false in + let should_report pre = + is_main || is_annotated || is_unavoidable pre in + let report (pre, runtime_exception) = + if should_report pre then + let pre_str = + Utils.pp_to_string (Prop.pp_prop pe_text) (Specs.Jprop.to_prop pre) in + let exn_desc = Localise.java_unchecked_exn_desc proc_name runtime_exception pre_str in + let exn = Exceptions.Java_runtime_exception (runtime_exception, pre_str, exn_desc) in + Reporting.log_error proc_name ~pre: (Some (Specs.Jprop.to_prop pre)) exn in + list_iter report (exception_preconditions tenv proc_desc summary) + + +(** update a summary after analysing a procedure *) +let update_summary prev_summary specs proc_name elapsed res = + let normal_specs = list_map Specs.spec_normalize specs in + let new_specs, changed = Fork.update_specs proc_name normal_specs in + let timestamp = max 1 (prev_summary.Specs.timestamp + if changed then 1 else 0) in + let stats_time = prev_summary.Specs.stats.Specs.stats_time +. elapsed in + let symops = prev_summary.Specs.stats.Specs.symops + SymOp.get_total () in + let timeout = res == None || prev_summary.Specs.stats.Specs.stats_timeout in + let stats = + { prev_summary.Specs.stats with + Specs.stats_time = stats_time; + Specs.symops = symops; + Specs.stats_timeout = timeout } in + { prev_summary with + Specs.stats = stats; + Specs.payload = Specs.PrePosts new_specs; + Specs.timestamp = timestamp } + + +(** Analyze [proc_name] and return the updated summary. Use module +[Timeout] to call [perform_analysis_phase] with a time limit, and +then return the updated summary. Executed as a child process. *) +let analyze_proc exe_env (proc_name: Procname.t) : Specs.summary = + if !Config.trace_anal then L.err "===analyze_proc@."; + let init_time = Unix.gettimeofday () in + let tenv = Exe_env.get_tenv exe_env proc_name in + let cfg = Exe_env.get_cfg exe_env proc_name in + let proc_desc = match Cfg.Procdesc.find_from_name cfg proc_name with + | Some proc_desc -> proc_desc + | None -> assert false in + reset_global_counters cfg proc_name proc_desc; + let go, get_results = perform_analysis_phase cfg tenv proc_name proc_desc in + let res = Fork.Timeout.exe_timeout (Specs.get_iterations proc_name) go () in + let specs = get_results () in + let elapsed = Unix.gettimeofday () -. init_time in + let prev_summary = Specs.get_summary_unsafe proc_name in + let updated_summary = + update_summary prev_summary specs proc_name elapsed res in + if (Config.report_assertion_failure || !Config.report_runtime_exceptions) then + report_runtime_exceptions tenv cfg proc_desc updated_summary; + updated_summary + +(** Perform phase transition from [FOOTPRINT] to [RE_EXECUTION] for +the procedures enabled after the analysis of [proc_name] *) +let perform_transition exe_env cg proc_name = + let proc_names = Fork.should_perform_transition cg proc_name in + let transition pname = + let tenv = Exe_env.get_tenv exe_env pname in + let joined_pres = (* disable exceptions for leaks and protect against any other errors *) + let allowleak = !Config.allowleak in + let apply_start_node f = (* apply the start node to f, and do nothing in case of exception *) + try + match Cfg.Procdesc.find_from_name (Exe_env.get_cfg exe_env pname) pname with + | Some pdesc -> + let start_node = Cfg.Procdesc.get_start_node pdesc in + f start_node + | None -> () + with exn when exn_not_timeout exn -> () in + apply_start_node (do_before_node 0); + try + Config.allowleak := true; + let res = collect_preconditions proc_name tenv pname in + Config.allowleak := allowleak; + apply_start_node do_after_node; + res + with exn when exn_not_timeout exn -> + apply_start_node do_after_node; + Config.allowleak := allowleak; + L.err "Error in collect_preconditions for %a@." Procname.pp proc_name; + let err_name, _, mloco, _, _, _, _ = Exceptions.recognize_exception exn in + let err_str = "exception raised " ^ (Localise.to_string err_name) in + L.err "Error: %s %a@." err_str pp_ml_location_opt mloco; + [] in + Fork.transition_footprint_re_exe pname joined_pres in + list_iter transition proc_names + +(** Process the result of the analysis of [proc_name]: update the +returned summary and add it to the spec table. Executed in the +parent process as soon as a child process returns a result. *) +let process_result (exe_env: Exe_env.t) (proc_name, calls) (_summ: Specs.summary) : unit = + if !Config.trace_anal then L.err "===process_result@."; + Ident.reset_name_generator (); (* for consistency with multi-core mode *) + let summ = { _summ with Specs.status = Specs.INACTIVE; Specs.stats = { _summ.Specs.stats with Specs.stats_calls = calls }} in + Specs.add_summary proc_name summ; + let call_graph = Exe_env.get_cg exe_env in + perform_transition exe_env call_graph proc_name; + if !Config.only_footprint || summ.Specs.phase != Specs.FOOTPRINT then + (try Specs.store_summary proc_name summ with + Sys_error s -> + L.err "@.### System Error while writing summary of procedure %a to disk: %s@." Procname.pp proc_name s); + let procs_done = Fork.procs_become_done call_graph proc_name in + Fork.post_process_procs exe_env procs_done + +(** Return true if the analysis of [proc_name] should be +skipped. Called by the parent process before attempting to analyze a +proc. *) +let filter_out (call_graph: Cg.t) (proc_name: Procname.t) : bool = + if !Config.trace_anal then L.err "===filter_out@."; + let slice_out = (* filter out if slicing is active and [proc_name] not in slice *) + (!Config.slice_fun <> "") && + (Procname.compare (Procname.from_string !Config.slice_fun) proc_name != 0) && + (Cg.node_defined call_graph proc_name) && + not (Cg.calls_recursively call_graph (Procname.from_string !Config.slice_fun) proc_name) in + slice_out + +let check_skipped_procs procs_and_defined_children = + let skipped_procs = ref Procname.Set.empty in + let proc_check_skips (pname, dep) = + let process_skip () = + let call_stats = (Specs.get_summary_unsafe pname).Specs.stats.Specs.call_stats in + let do_tr_elem pn = function + | Specs.CallStats.CR_skip, _ -> + skipped_procs := Procname.Set.add pn !skipped_procs + | _ -> () in + let do_call (pn, _) (tr: Specs.CallStats.trace) = list_iter (do_tr_elem pn) tr in + Specs.CallStats.iter do_call call_stats in + if Specs.summary_exists pname then process_skip () in + list_iter proc_check_skips procs_and_defined_children; + let skipped_procs_with_summary = + Procname.Set.filter Specs.summary_exists !skipped_procs in + skipped_procs_with_summary + +(** create a function to filter procedures which were skips but now have a .specs file *) +let filter_skipped_procs cg procs_and_defined_children = + let skipped_procs_with_summary = check_skipped_procs procs_and_defined_children in + let filter (pname, dep) = + let calls_recurs pn = + let r = try Cg.calls_recursively cg pname pn with Not_found -> false in + L.err "calls recursively %a %a: %b@." Procname.pp pname Procname.pp pn r; + r in + Procname.Set.exists calls_recurs skipped_procs_with_summary in + filter + +(** create a function to filter procedures which were analyzed before but had no specs *) +let filter_nospecs (pname, dep) = + if Specs.summary_exists pname + then Specs.get_specs pname = [] + else false + +(** Perform the analysis of an exe_env *) +let do_analysis exe_env = + if !Config.trace_anal then L.err "do_analysis@."; + let do_parallel = !Config.num_cores > 1 || !Config.max_num_proc > 0 in + let cg = Exe_env.get_cg exe_env in + let procs_and_defined_children = get_procs_and_defined_children cg in + let get_calls caller_pdesc = + let calls = ref [] in + let f (callee_pname, loc) = calls := (callee_pname, loc) :: !calls in + Cfg.Procdesc.iter_calls f caller_pdesc; + list_rev !calls in + let init_proc (pname, dep) = + let cfg = Exe_env.get_cfg exe_env pname in + let pdesc = match Cfg.Procdesc.find_from_name cfg pname with + | Some pdesc -> pdesc + | None -> assert false in + let ret_type = Cfg.Procdesc.get_ret_type pdesc in + let formals = Cfg.Procdesc.get_formals pdesc in + let loc = Cfg.Procdesc.get_loc pdesc in + let nodes = list_map (fun n -> Cfg.Node.get_id n) (Cfg.Procdesc.get_nodes pdesc) in + let proc_flags = Cfg.Procdesc.get_flags pdesc in + let static_err_log = Cfg.Procdesc.get_err_log pdesc in (** err log from translation *) + let calls = get_calls pdesc in + let cyclomatic = Cfg.Procdesc.get_cyclomatic pdesc in + let attributes = Cfg.Procdesc.get_attributes pdesc in + + Callbacks.proc_inline_synthetic_methods cfg pdesc; + Specs.init_summary + (pname, ret_type, formals, dep, loc, nodes, proc_flags, + static_err_log, calls, cyclomatic, None, attributes) in + let filter = + if !Config.only_skips then (filter_skipped_procs cg procs_and_defined_children) + else if !Config.only_nospecs then filter_nospecs + else (fun _ -> true) in + list_iter (fun x -> if filter x then init_proc x) procs_and_defined_children; + (try Fork.parallel_iter_nodes exe_env analyze_proc process_result filter_out with + exe when do_parallel -> + L.out "@.@. ERROR exception raised in parallel execution@."; + raise exe) + +let visited_and_total_nodes cfg = + let all_nodes = + let add s n = Cfg.NodeSet.add n s in + list_fold_left add Cfg.NodeSet.empty (Cfg.Node.get_all_nodes cfg) in + let filter_node n = + Cfg.Procdesc.is_defined (Cfg.Node.get_proc_desc n) && + match Cfg.Node.get_kind n with + | Cfg.Node.Stmt_node _ | Cfg.Node.Prune_node _ + | Cfg.Node.Start_node _ | Cfg.Node.Exit_node _ -> true + | Cfg.Node.Skip_node _ | Cfg.Node.Join_node -> false in + let counted_nodes = Cfg.NodeSet.filter filter_node all_nodes in + let visited_nodes_re = Cfg.NodeSet.filter (fun node -> snd (Printer.is_visited_phase node)) counted_nodes in + Cfg.NodeSet.elements visited_nodes_re, Cfg.NodeSet.elements counted_nodes + +(** Print the stats for the given cfg; consider every defined proc unless a proc with the same name +was defined in another module, and was the one which was analyzed *) +let print_stats_cfg proc_shadowed proc_is_active cfg = + let err_table = Errlog.create_err_table () in + let active_procs = list_filter proc_is_active (Cfg.get_defined_procs cfg) in + let nvisited, ntotal = visited_and_total_nodes cfg in + let node_filter n = + let node_procname = Cfg.Procdesc.get_proc_name (Cfg.Node.get_proc_desc n) in + Specs.summary_exists node_procname && Specs.get_specs node_procname != [] in + let nodes_visited = list_filter node_filter nvisited in + let nodes_total = list_filter node_filter ntotal in + let num_proc = ref 0 in + let num_nospec_noerror_proc = ref 0 in + let num_spec_noerror_proc = ref 0 in + let num_nospec_error_proc = ref 0 in + let num_spec_error_proc = ref 0 in + let tot_specs = ref 0 in + let tot_symops = ref 0 in + let num_timeout = ref 0 in + let compute_stats_proc proc_desc = + let proc_name = Cfg.Procdesc.get_proc_name proc_desc in + if proc_shadowed proc_desc then + L.out "print_stats: ignoring function %a which is also defined in another file@." Procname.pp proc_name + else + let summary = Specs.get_summary_unsafe proc_name in + let stats = summary.Specs.stats in + incr num_proc; + let specs = Specs.get_specs_from_payload summary in + tot_specs := (list_length specs) + !tot_specs; + let () = + match specs, + Errlog.size + (fun ekind in_footprint -> ekind = Exceptions.Kerror && in_footprint) + stats.Specs.err_log with + | [], 0 -> incr num_nospec_noerror_proc + | _, 0 -> incr num_spec_noerror_proc + | [], _ -> incr num_nospec_error_proc + | _, _ -> incr num_spec_error_proc in + tot_symops := !tot_symops + stats.Specs.symops; + if stats.Specs.stats_timeout then incr num_timeout; + Errlog.extend_table err_table stats.Specs.err_log in + let print_file_stats fmt () = + let num_errors = Errlog.err_table_size_footprint Exceptions.Kerror err_table in + let num_warnings = Errlog.err_table_size_footprint Exceptions.Kwarning err_table in + let num_infos = Errlog.err_table_size_footprint Exceptions.Kinfo err_table in + let num_ok_proc = !num_spec_noerror_proc + !num_spec_error_proc in + (* F.fprintf fmt "VISITED: %a@\n" (pp_seq pp_node) nodes_visited; + F.fprintf fmt "TOTAL: %a@\n" (pp_seq pp_node) nodes_total; *) + F.fprintf fmt "@\n++++++++++++++++++++++++++++++++++++++++++++++++++@\n"; + F.fprintf fmt "+ FILE: %s LOC: %n VISITED: %d/%d SYMOPS: %d@\n" (DB.source_file_to_string !DB.current_source) !Config.nLOC (list_length nodes_visited) (list_length nodes_total) !tot_symops; + F.fprintf fmt "+ num_procs: %d (%d ok, %d timeouts, %d errors, %d warnings, %d infos)@\n" !num_proc num_ok_proc !num_timeout num_errors num_warnings num_infos; + F.fprintf fmt "+ detail procs:@\n"; + F.fprintf fmt "+ - No Errors and No Specs: %d@\n" !num_nospec_noerror_proc; + F.fprintf fmt "+ - Some Errors and No Specs: %d@\n" !num_nospec_error_proc; + F.fprintf fmt "+ - No Errors and Some Specs: %d@\n" !num_spec_noerror_proc; + F.fprintf fmt "+ - Some Errors and Some Specs: %d@\n" !num_spec_error_proc; + F.fprintf fmt "+ errors: %a@\n" (Errlog.pp_err_table_stats Exceptions.Kerror) err_table; + F.fprintf fmt "+ warnings: %a@\n" (Errlog.pp_err_table_stats Exceptions.Kwarning) err_table; + F.fprintf fmt "+ infos: %a@\n" (Errlog.pp_err_table_stats Exceptions.Kinfo) err_table; + F.fprintf fmt "+ specs: %d@\n" !tot_specs; + F.fprintf fmt "++++++++++++++++++++++++++++++++++++++++++++++++++@\n"; + Errlog.print_err_table_details fmt err_table in + let save_file_stats () = + let source_dir = DB.source_dir_from_source_file !DB.current_source in + let stats_file = DB.source_dir_get_internal_file source_dir ".stats" in + try + let outc = open_out (DB.filename_to_string stats_file) in + let fmt = F.formatter_of_out_channel outc in + print_file_stats fmt (); + close_out outc + with Sys_error _ -> () in + list_iter compute_stats_proc active_procs; + L.out "%a" print_file_stats (); + save_file_stats () + +(** Print the stats for all the files in the exe_env *) +let print_stats exe_env = + let proc_is_active proc_desc = + Exe_env.proc_is_active exe_env (Cfg.Procdesc.get_proc_name proc_desc) in + Exe_env.iter_files (fun fname tenv cfg -> + let proc_shadowed proc_desc = + (** return true if a proc with the same name in another module was analyzed instead *) + let proc_name = Cfg.Procdesc.get_proc_name proc_desc in + Exe_env.get_source exe_env proc_name <> fname in + print_stats_cfg proc_shadowed proc_is_active cfg) exe_env diff --git a/infer/src/backend/interproc.mli b/infer/src/backend/interproc.mli new file mode 100644 index 000000000..a9b0f9878 --- /dev/null +++ b/infer/src/backend/interproc.mli @@ -0,0 +1,28 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Interprocedural Analysis *) + +(** Analyze [proc_name] and return the updated summary. Use module +{!Timeout } to call {!perform_analysis_phase } with a time limit, and +then return the updated summary. Executed as a child process. *) +val analyze_proc : Exe_env.t -> Procname.t -> Specs.summary + +(** Process the result of the analysis of [proc_name]: update the +returned summary and add it to the spec table. Executed in the +parent process as soon as a child process returns a result. *) +val process_result : Exe_env.t -> (Procname.t * Cg.in_out_calls) -> Specs.summary -> unit + +(** Return true if the analysis of [proc_name] should be +skipped. Called by the parent process before attempting to analyze a +proc. *) +val filter_out : Cg.t -> Procname.t -> bool + +(** Perform the analysis of an exe_env *) +val do_analysis : Exe_env.t -> unit + +(** Print the stats for all the files in the exe_env *) +val print_stats : Exe_env.t -> unit diff --git a/infer/src/backend/io_infer.ml b/infer/src/backend/io_infer.ml new file mode 100644 index 000000000..d5d45305e --- /dev/null +++ b/infer/src/backend/io_infer.ml @@ -0,0 +1,281 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Module to handle IO. Includes html and xml modules. *) + +module F = Format +open Utils + +(* =============== START of module Html =============== *) +module Html : sig + val close : Unix.file_descr * Format.formatter -> unit (** Close an Html file *) + val create : DB.Results_dir.path_kind -> DB.Results_dir.path -> Unix.file_descr * Format.formatter (** Create a new html file *) + val modified_during_analysis : DB.Results_dir.path -> bool (** Return true if the html file was modified since the beginning of the analysis *) + val open_out : DB.Results_dir.path -> Unix.file_descr * Format.formatter (** Open an Html file to append data *) + val pp_line_link : ?with_name: bool -> ?text: (string option) -> DB.Results_dir.path -> Format.formatter -> int -> unit (** Print an html link to the given line number of the current source file *) + val pp_hline : Format.formatter -> unit -> unit (** Print a horizontal line *) + val pp_end_color : Format.formatter -> unit -> unit (** Print end color *) + + (** [pp_node_link path_to_root description isvisited isproof fmt id] prints an html link to the given node. + [path_to_root] is the path to the dir for the procedure in the spec db. + [description] is a string description. + [is_visited] indicates whether the node should be active or greyed out. + [is_proof] indicates whether the node is part of a proof and should be green. + [id] is the node identifier. *) + val pp_node_link : DB.Results_dir.path -> string -> int list -> int list -> int list -> bool -> bool -> Format.formatter -> int -> unit + val pp_proc_link : DB.Results_dir.path -> Procname.t -> Format.formatter -> string -> unit (** Print an html link to the given proc *) + val pp_session_link : ?with_name: bool -> string list -> Format.formatter -> int * int * int -> unit (** Print an html link given node id and session *) + val pp_start_color : Format.formatter -> color -> unit (** Print start color *) +end = struct + let create pk path = + let fname, dir_path = match list_rev path with + | fname:: dir_path -> fname, dir_path + | [] -> raise (Failure "Html.create") in + let fd = DB.Results_dir.create_file pk (list_rev ((fname ^ ".html") :: dir_path)) in + let outc = Unix.out_channel_of_descr fd in + let fmt = F.formatter_of_out_channel outc in + let (++) x y = x ^ "\n" ^ y in + let s = + "" ++ + "\n\n" ^ fname ^ "" ++ + "" ++ + "" ++ + "" ++ + "" in + F.fprintf fmt "%s" s; + (fd, fmt) + + (** get the full html filename from a path *) + let get_full_fname path = + let fname, dir_path = match list_rev path with + | fname:: dir_path -> fname, dir_path + | [] -> raise (Failure "Html.open_out") in + DB.Results_dir.path_to_filename DB.Results_dir.Abs_source_dir (list_rev ((fname ^ ".html") :: dir_path)) + + let open_out path = + let full_fname = get_full_fname path in + let fd = Unix.openfile (DB.filename_to_string full_fname) [Unix.O_WRONLY; Unix.O_APPEND] 0o777 in + let outc = Unix.out_channel_of_descr fd in + let fmt = F.formatter_of_out_channel outc in + (fd, fmt) + + let modified_during_analysis path = + let fname = get_full_fname path in + if DB.file_exists fname then + DB.file_modified_time fname >= initial_analysis_time + else false + + let close (fd, fmt) = + F.fprintf fmt "@\n@."; + Unix.close fd + + (** Print a horizontal line *) + let pp_hline fmt () = + F.fprintf fmt "
@\n" + + (** Print start color *) + let pp_start_color fmt color = + F.fprintf fmt "%s" ("") + + (** Print end color *) + let pp_end_color fmt () = + F.fprintf fmt "%s" "" + + let pp_link ?(name = None) ?(pos = None) path fmt text = + let pos_str = match pos with + | None -> "" + | Some s -> "#" ^ s in + let link_str = (DB.filename_to_string (DB.Results_dir.path_to_filename DB.Results_dir.Rel path)) ^ ".html" ^ pos_str in + let name_str = match name with + | None -> "" + | Some n -> "name=\"" ^ n ^ "\"" in + let pr_str = "" ^ text ^ "" in + F.fprintf fmt " %s" pr_str + + (** [pp_node_link path_to_root description isvisited isproof fmt id] prints an html link to the given node. *) + let pp_node_link path_to_root description preds succs exn isvisited isproof fmt id = + let display_name = + (if description = "" then "N" else String.sub description 0 1) ^ "_" ^ (string_of_int id) in + let node_name = "node" ^ string_of_int id in + let style_class = if not isvisited then "dangling" else if isproof then "visitedproof" else "visited" in + let node_text = + let pp fmt () = + Format.fprintf fmt "%snode%d preds:%a succs:%a exn:%a %s%s" + style_class display_name id + (pp_seq Format.pp_print_int) preds (pp_seq Format.pp_print_int) succs (pp_seq Format.pp_print_int) exn + description (if not isvisited then "\nNOT VISITED" else "") in + pp_to_string pp () in + if not isvisited then F.fprintf fmt " %s" node_text + else pp_link (path_to_root @ ["nodes"; node_name]) fmt node_text + + (** Print an html link to the given proc *) + let pp_proc_link path_to_root proc_name fmt text = + pp_link (path_to_root @ [Procname.to_filename proc_name]) fmt text + + (** Print an html link to the given line number of the current source file *) + let pp_line_link ?(with_name = false) ?(text = None) path_to_root fmt linenum = + let fname = DB.source_file_encoding !DB.current_source in + let linenum_str = string_of_int linenum in + let name = "LINE" ^ linenum_str in + pp_link ~name: (if with_name then Some name else None) (path_to_root @ [".."; fname]) ~pos: (Some name) + fmt (match text with Some s -> s | None -> linenum_str) + + (** Print an html link given node id and session *) + let pp_session_link ?(with_name = false) path_to_root fmt (node_id, session, linenum) = + let node_name = "node" ^ (string_of_int node_id) in + let path_to_node = path_to_root @ ["nodes"; node_name] in + let pos = "session" ^ (string_of_int session) in + pp_link ~name: (if with_name then Some pos else None) ~pos: (Some pos) path_to_node fmt (node_name ^ "#" ^ pos); + F.fprintf fmt "(%a)" (pp_line_link path_to_root) linenum +end +(* =============== END of module Html =============== *) + +(* =============== START of module Xml =============== *) +(** Create and print xml trees *) +module Xml = struct + let tag_branch = "branch" + let tag_call_trace = "call_trace" + let tag_callee = "callee" + let tag_callee_id = "callee_id" + let tag_caller = "caller" + let tag_caller_id = "caller_id" + let tag_cyclomatic = "cyclomatic" + let tag_class = "class" + let tag_code = "code" + let tag_description = "description" + let tag_err = "err" + let tag_flags = "flags" + let tag_file = "file" + let tag_hash = "hash" + let tag_in_calls = "in_calls" + let tag_key = "key" + let tag_kind = "kind" + let tag_level = "level" + let tag_line = "line" + let tag_loc = "loc" + let tag_name = "name" + let tag_name_id = "name_id" + let tag_node = "node" + let tag_out_calls = "out_calls" + let tag_precondition = "precondition" + let tag_procedure = "procedure" + let tag_procedure_id = "procedure_id" + let tag_proof_coverage = "proof_coverage" + let tag_proof_trace = "proof_trace" + let tag_qualifier = "qualifier" + let tag_qualifier_tags = "qualifier_tags" + let tag_rank = "rank" + let tag_severity = "severity" + let tag_signature = "signature" + let tag_specs = "specs" + let tag_symop = "symop" + let tag_time = "time" + let tag_to = "to" + let tag_top = "top" + let tag_trace = "trace" + let tag_type = "type" + let tag_weight = "weight" + + type tree = { name: string; attributes: (string * string) list; forest: node list } + and node = + | Tree of tree + | String of string + + let pp = F.fprintf + + let create_tree name attributes forest = + Tree { name = name; attributes = attributes; forest = forest } + + let pp_attribute fmt (name, value) = + pp fmt "%s=\"%s\"" name value + + let pp_attributes fmt l = + pp_seq pp_attribute fmt l + + (** print an xml node *) + let rec pp_node newline indent fmt = function + | Tree { name = name; attributes = attributes; forest = forest } -> + let indent' = if newline = "" then "" else indent ^ " " in + let space = if attributes = [] then "" else " " in + let pp_inside fmt () = match forest with + | [] -> () + | [String s] -> pp fmt "%s" s + | _ -> pp fmt "%s%a%s" newline (pp_forest newline indent') forest indent in + pp fmt "%s<%s%s%a>%a%s" indent name space pp_attributes attributes pp_inside () name newline + | String s -> + F.fprintf fmt "%s%s%s" indent s newline + and pp_forest newline indent fmt forest = + list_iter (pp_node newline indent fmt) forest + + let pp_prelude fmt = pp fmt "%s" "\n" + + let pp_open fmt name = + pp_prelude fmt; + pp fmt "<%s>@\n" name + + let pp_close fmt name = + pp fmt "@." name + + let pp_inner_node fmt node = + pp_node "\n" "" fmt node + + (** print an xml document, if the first parameter is false on a single line without preamble *) + let pp_document on_several_lines fmt node = + let newline = if on_several_lines then "\n" else "" in + if on_several_lines then pp_prelude fmt; + pp_node newline "" fmt node; + if on_several_lines then pp fmt "@." +end +(* =============== END of module Xml =============== *) diff --git a/infer/src/backend/io_infer.mli b/infer/src/backend/io_infer.mli new file mode 100644 index 000000000..4e4d83296 --- /dev/null +++ b/infer/src/backend/io_infer.mli @@ -0,0 +1,90 @@ +(* +* Copyright (c) 2009-2013 Monoidics ltd. +* Copyright (c) 2013- Facebook. +* All rights reserved. +*) + +(** Module to handle IO. Includes html and xml modules. *) + +module Html : sig + val close : Unix.file_descr * Format.formatter -> unit (** Close an Html file *) + val create : DB.Results_dir.path_kind -> DB.Results_dir.path -> Unix.file_descr * Format.formatter (** Create a new html file *) + val modified_during_analysis : DB.Results_dir.path -> bool (** Return true if the html file was modified since the beginning of the analysis *) + val open_out : DB.Results_dir.path -> Unix.file_descr * Format.formatter (** Open an Html file to append data *) + val pp_line_link : ?with_name: bool -> ?text: (string option) -> DB.Results_dir.path -> Format.formatter -> int -> unit (** Print an html link to the given line number of the current source file *) + val pp_hline : Format.formatter -> unit -> unit (** Print a horizontal line *) + val pp_end_color : Format.formatter -> unit -> unit (** Print end color *) + + (** [pp_node_link path_to_root description isvisited isproof fmt id] prints an html link to the given node. + [path_to_root] is the path to the dir for the procedure in the spec db. + [description] is a string description. + [is_visited] indicates whether the node should be active or greyed out. + [is_proof] indicates whether the node is part of a proof and should be green. + [id] is the node identifier. *) + val pp_node_link : DB.Results_dir.path -> string -> int list -> int list -> int list -> bool -> bool -> Format.formatter -> int -> unit + val pp_proc_link : DB.Results_dir.path -> Procname.t -> Format.formatter -> string -> unit (** Print an html link to the given proc *) + val pp_session_link : ?with_name: bool -> string list -> Format.formatter -> int * int * int -> unit (** Print an html link given node id and session *) + val pp_start_color : Format.formatter -> Utils.color -> unit (** Print start color *) +end + +(** Create and print xml trees *) +module Xml : sig + val tag_branch : string + val tag_call_trace : string + val tag_callee : string + val tag_callee_id : string + val tag_caller : string + val tag_caller_id : string + val tag_cyclomatic : string + val tag_class : string + val tag_code : string + val tag_description : string + val tag_err : string + val tag_file : string + val tag_flags : string + val tag_hash : string + val tag_in_calls : string + val tag_key : string + val tag_kind : string + val tag_level : string + val tag_line : string + val tag_loc : string + val tag_name : string + val tag_name_id : string + val tag_node : string + val tag_out_calls : string + val tag_precondition : string + val tag_procedure : string + val tag_procedure_id : string + val tag_proof_coverage : string + val tag_proof_trace : string + val tag_qualifier : string + val tag_qualifier_tags : string + val tag_rank : string + val tag_severity : string + val tag_signature : string + val tag_specs : string + val tag_symop : string + val tag_time : string + val tag_to : string + val tag_top : string + val tag_trace : string + val tag_type : string + val tag_weight : string + + type tree = { name: string; attributes: (string * string) list; forest: node list } + and node = + | Tree of tree + | String of string + (** create a tree *) + val create_tree : string -> (string * string) list -> node list -> node + (** print an xml document, if the first parameter is false on a single line without preamble *) + val pp_document : bool -> Format.formatter -> node -> unit + + (** print the opening lines of an xml document consisting of a main tree with the given name *) + val pp_open : Format.formatter -> string -> unit + (** print the closing lines of an xml document consisting of a main tree with the given name *) + val pp_close : Format.formatter -> string -> unit + (** print a node between a [pp_open] and a [pp_close] *) + val pp_inner_node : Format.formatter -> node -> unit +end diff --git a/infer/src/backend/jsonbug.atd b/infer/src/backend/jsonbug.atd new file mode 100644 index 000000000..92a8a729e --- /dev/null +++ b/infer/src/backend/jsonbug.atd @@ -0,0 +1,32 @@ +type tag_value_record = { + tag : string; + value : string; +} + +type json_trace_item = { + level : int; + filename : string; + line_number : int; + description : string; + node_tags : tag_value_record list; +} + +type jsonbug = { + bug_class : string; + kind : string; + bug_type : string; + qualifier : string; + severity : string; + line: int; + procedure : string; + procedure_id : string; + file : string; + bug_trace : json_trace_item list; + key : int; + qualifier_tags : tag_value_record list; + hash : int; +} + +type json_trace = { + trace : json_trace_item list; +} diff --git a/infer/src/backend/latex.ml b/infer/src/backend/latex.ml new file mode 100644 index 000000000..18b592ca4 --- /dev/null +++ b/infer/src/backend/latex.ml @@ -0,0 +1,61 @@ +(* +* Copyright (c) 2009-2013 Monoidics ltd. +* Copyright (c) 2013- Facebook. +* All rights reserved. +*) + +module F = Format +open Utils + +(** Produce output in latex *) + +type style = + | Boldface + | Roman + | Italics + +(** Convert a string to a latex-friendly format *) +let convert_string s = + if String.contains s '_' then begin + let cnt = ref 0 in + let s' = ref "" in + let f c = + if c == '_' then s' := !s' ^ "\\_" + else s' := !s' ^ Char.escaped (String.get s !cnt); + incr cnt in + String.iter f s; + !s' + end + else s + +(** Print a string in the given style, after converting it into latex-friendly format *) +let pp_string style f s = + let converted = convert_string s in + match style with + | Boldface -> F.fprintf f "\\textbf{%s}" converted + | Roman -> F.fprintf f "\\textrm{%s}" converted + | Italics -> F.fprintf f "\\textit{%s}" converted + +let color_to_string = function + | Black -> "black" + | Blue -> "blue" + | Green -> "green" + | Orange -> "orange" + | Red -> "red" + +(** Print color command *) +let pp_color f color = + F.fprintf f "\\color{%s}" (color_to_string color) + +(** Prelude for a latex file with the given author and title *) +let pp_begin f (author, title, table_of_contents) = + let pp_toc f () = if table_of_contents then F.fprintf f "\\tableofcontents@\n" else () in + F.fprintf f "\\documentclass{article}@\n\\usepackage{hyperref}@\n\\usepackage{color}@\n\\author{%s}@\n\\title{%s}@\n\\begin{document}@\n\\maketitle@\n%a" author title pp_toc () + +(** Epilogue for a latex file *) +let pp_end f () = + F.fprintf f "\\end{document}@\n" + +(** Section with the given title *) +let pp_section f title = + F.fprintf f "\\section{%s}@\n" title diff --git a/infer/src/backend/latex.mli b/infer/src/backend/latex.mli new file mode 100644 index 000000000..2fd434eec --- /dev/null +++ b/infer/src/backend/latex.mli @@ -0,0 +1,28 @@ +(* +* Copyright (c) 2009-2013 Monoidics ltd. +* Copyright (c) 2013- Facebook. +* All rights reserved. +*) + +type style = + | Boldface + | Roman + | Italics + +(** Convert a string to a latex-friendly format *) +val convert_string : string -> string + +(** Print a string in the given style, after converting it into latex-friendly format *) +val pp_string : style -> Format.formatter -> string -> unit + +(** Print color command *) +val pp_color : Format.formatter -> Utils.color -> unit + +(** Prelude for a latex file with the given author and title and table of contents flag *) +val pp_begin : Format.formatter -> (string * string * bool) -> unit + +(** Epilogue for a latex file *) +val pp_end : Format.formatter -> unit -> unit + +(** Section with the given title *) +val pp_section : Format.formatter -> string -> unit diff --git a/infer/src/backend/localise.ml b/infer/src/backend/localise.ml new file mode 100644 index 000000000..9157e8834 --- /dev/null +++ b/infer/src/backend/localise.ml @@ -0,0 +1,694 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Support for localisation *) + +module F = Format +open Utils + +(** type of string used for localisation *) +type t = string + +(** pretty print a localised string *) +let pp fmt s = Format.fprintf fmt "%s" s + +(** create a localised string from an ordinary string *) +let from_string s = s + +(** convert a localised string to an ordinary string *) +let to_string s = s + +(** compare two localised strings *) +let compare (s1: string) (s2: string) = Pervasives.compare s1 s2 + +let analysis_stops = "ANALYSIS_STOPS" +let array_out_of_bounds_l1 = "ARRAY_OUT_OF_BOUNDS_L1" +let array_out_of_bounds_l2 = "ARRAY_OUT_OF_BOUNDS_L2" +let array_out_of_bounds_l3 = "ARRAY_OUT_OF_BOUNDS_L3" +let class_cast_exception = "CLASS_CAST_EXCEPTION" +let comparing_floats_for_equality = "COMPARING_FLOAT_FOR_EQUALITY" +let condition_is_assignment = "CONDITION_IS_ASSIGNMENT" +let condition_always_false = "CONDITION_ALWAYS_FALSE" +let condition_always_true = "CONDITION_ALWAYS_TRUE" +let dangling_pointer_dereference = "DANGLING_POINTER_DEREFERENCE" +let deallocate_stack_variable = "DEALLOCATE_STACK_VARIABLE" +let deallocate_static_memory = "DEALLOCATE_STATIC_MEMORY" +let deallocation_mismatch = "DEALLOCATION_MISMATCH" +let divide_by_zero = "DIVIDE_BY_ZERO" +let field_not_null_checked = "IVAR_NOT_NULL_CHECKED" +let inherently_dangerous_function = "INHERENTLY_DANGEROUS_FUNCTION" +let memory_leak = "MEMORY_LEAK" +let null_dereference = "NULL_DEREFERENCE" +let parameter_not_null_checked = "PARAMETER_NOT_NULL_CHECKED" +let null_test_after_dereference = "NULL_TEST_AFTER_DEREFERENCE" +let pointer_size_mismatch = "POINTER_SIZE_MISMATCH" +let precondition_not_found = "PRECONDITION_NOT_FOUND" +let precondition_not_met = "PRECONDITION_NOT_MET" +let premature_nil_termination = "PREMATURE_NIL_TERMINATION_ARGUMENT" +let resource_leak = "RESOURCE_LEAK" +let retain_cycle = "RETAIN_CYCLE" +let return_value_ignored = "RETURN_VALUE_IGNORED" +let return_expression_required = "RETURN_EXPRESSION_REQUIRED" +let return_statement_missing = "RETURN_STATEMENT_MISSING" +let skip_function = "SKIP_FUNCTION" +let skip_pointer_dereference = "SKIP_POINTER_DEREFERENCE" +let stack_variable_address_escape = "STACK_VARIABLE_ADDRESS_ESCAPE" +let tainted_value_reaching_sensitive_function = "TAINTED_VALUE_REACHING_SENSITIVE_FUNCTION" +let unary_minus_applied_to_unsigned_expression = "UNARY_MINUS_APPLIED_TO_UNSIGNED_EXPRESSION" +let uninitialized_value = "UNINITIALIZED_VALUE" +let use_after_free = "USE_AFTER_FREE" + +(** description field of error messages: descriptions, advice and tags *) +type error_desc = string list * string option * (string * string) list + +(** empty error description *) +let no_desc: error_desc = [], None, [] + +(** verbatim desc from a string, not to be used for user-visible descs *) +let verbatim_desc s = [s], None, [] + +let custom_desc s tags = [s], None, tags + +let custom_desc_with_advice description advice tags = + [description], Some advice, tags + +(** pretty print an error description *) +let pp_error_desc fmt (l, _, s) = + let pp_item fmt s = F.fprintf fmt "%s" s in + pp_seq pp_item fmt l + +(** pretty print an error advice *) +let pp_error_advice fmt (_, advice, _) = + match advice with + | Some advice -> F.fprintf fmt "%s" advice + | None -> () + +(** pretty print an error description *) +let pp_error_desc fmt (l, _, _) = + let pp_item fmt s = F.fprintf fmt "%s" s in + pp_seq pp_item fmt l + +(** get tags of error description *) +let error_desc_get_tags (_, _, tags) = tags + +module Tags = struct + let accessed_line = "accessed_line" (* line where value was last accessed *) + let alloc_function = "alloc_function" (* allocation function used *) + let alloc_call = "alloc_call" (* call in the current procedure which triggers the allocation *) + let alloc_line = "alloc_line" (* line of alloc_call *) + let array_index = "array_index" (* index of the array *) + let array_size = "array_size" (* size of the array *) + let assigned_line = "assigned_line" (* line where value was last assigned *) + let bucket = "bucket" (* bucket to classify likelyhood of real bug *) + let call_procedure = "call_procedure" (* name of the procedure called *) + let call_line = "call_line" (* line of call_procedure *) + let dealloc_function = "dealloc_function" (* deallocation function used *) + let dealloc_call = "dealloc_call" (* call in the current procedure which triggers the deallocation *) + let dealloc_line = "dealloc_line" (* line of dealloc_call *) + let dereferenced_line = "dereferenced_line" (* line where value was dereferenced *) + let escape_to = "escape_to" (* expression wher a value escapes to *) + let line = "line" (* line of the error *) + let type1 = "type1" (* 1st Java type *) + let type2 = "type2" (* 2nd Java type *) + let value = "value" (* string describing a C value, e.g. "x.date" *) + let parameter_not_null_checked = "parameter_not_null_checked" (* describes a NPE that comes from parameter not nullable *) + let field_not_null_checked = "field_not_null_checked" (* describes a NPE that comes from field not nullable *) + let nullable_src = "nullable_src" (* @Nullable-annoted field/param/retval that causes a warning *) + let create () = ref [] + let add tags tag value = tags := (tag, value) :: !tags + let update tags tag value = + let tags' = list_filter (fun (t, v) -> t <> tag) tags in + (tag, value) :: tags' + let get tags tag = + try + let (_, v) = list_find (fun (t, _) -> t = tag) tags in + Some v + with Not_found -> None +end + +module BucketLevel = struct + let b1 = "B1" (* highest likelyhood *) + let b2 = "B2" + let b3 = "B3" + let b4 = "B4" + let b5 = "B5" (* lowest likelyhood *) +end + +(** takes in input a tag to extract from the given error_desc +and returns its value *) +let error_desc_extract_tag_value (_, _, tags) tag_to_extract = + let find_value tag v = + match v with + | (t, _) when t = tag -> true + | _ -> false in + try + let _, s = list_find (find_value tag_to_extract) tags in + s + with Not_found -> "" + +let error_desc_to_tag_value_pairs (_, _, tags) = tags + +(** returns the content of the value tag of the error_desc *) +let error_desc_get_tag_value error_desc = error_desc_extract_tag_value error_desc Tags.value + +(** returns the content of the call_procedure tag of the error_desc *) +let error_desc_get_tag_call_procedure error_desc = error_desc_extract_tag_value error_desc Tags.call_procedure + +(** get the bucket value of an error_desc, if any *) +let error_desc_get_bucket (_, _, tags) = + Tags.get tags Tags.bucket + +(** set the bucket value of an error_desc; the boolean indicates where the bucket should be shown in the message *) +let error_desc_set_bucket (l, advice, tags) bucket show_in_message = + let tags' = Tags.update tags Tags.bucket bucket in + let l' = + if show_in_message = false then l + else ("[" ^ bucket ^ "]") :: l in + (l', advice, tags') + +(** get the value tag, if any *) +let get_value_line_tag tags = + try + let value = snd (list_find (fun (_tag, value) -> _tag = Tags.value) tags) in + let line = snd (list_find (fun (_tag, value) -> _tag = Tags.line) tags) in + Some [value; line] + with Not_found -> None + +(** extract from desc a value on which to apply polymorphic hash and equality *) +let desc_get_comparable (sl, advice, tags) = + match get_value_line_tag tags with + | Some sl' -> sl' + | None -> sl + +(** hash function for error_desc *) +let error_desc_hash desc = + Hashtbl.hash (desc_get_comparable desc) + +(** equality for error_desc *) +let error_desc_equal desc1 desc2 = (desc_get_comparable desc1) = (desc_get_comparable desc2) + +let _line_tag tags tag loc = + let line_str = string_of_int loc.Sil.line in + Tags.add tags tag line_str; + let s = "line " ^ line_str in + if (loc.Sil.col != -1) then + let col_str = string_of_int loc.Sil.col in + s ^ ", column " ^ col_str + else s + +let at_line_tag tags tag loc = + "at " ^ _line_tag tags tag loc + +let _line tags loc = + _line_tag tags Tags.line loc + +let at_line tags loc = + at_line_tag tags Tags.line loc + +let call_to tags proc_name = + let proc_name_str = Procname.to_simplified_string proc_name in + Tags.add tags Tags.call_procedure proc_name_str; + "call to " ^ proc_name_str + +let call_to_at_line tags proc_name loc = + (call_to tags proc_name) ^ " " ^ at_line_tag tags Tags.call_line loc + +let by_call_to tags proc_name = + "by " ^ call_to tags proc_name + +let by_call_to_ra tags ra = + "by " ^ call_to_at_line tags ra.Sil.ra_pname ra.Sil.ra_loc + +let mem_dyn_allocated = "memory dynamically allocated" +let res_acquired = "resource acquired" +let lock_acquired = "lock acquired" +let released = "released" +let reachable = "reachable" + +(** dereference strings used to explain a dereference action in an error message *) +type deref_str = + { tags : (string * string) list ref; (** tags for the error description *) + value_pre: string option; (** string printed before the value being dereferenced *) + value_post: string option; (** string printed after the value being dereferenced *) + problem_str: string; (** description of the problem *) } + +let pointer_or_object () = + if !Sil.curr_language = Sil.Java then "object" else "pointer" + +let _deref_str_null proc_name_opt _problem_str tags = + let problem_str = match proc_name_opt with + | Some proc_name -> + _problem_str ^ " " ^ by_call_to tags proc_name + | None -> _problem_str in + { tags = tags; + value_pre = Some (pointer_or_object ()); + value_post = None; + problem_str = problem_str; } + +(** dereference strings for null dereference *) +let deref_str_null proc_name_opt = + let problem_str = "could be null and is dereferenced" in + _deref_str_null proc_name_opt problem_str (Tags.create ()) + +(** dereference strings for null dereference due to Nullable annotation *) +let deref_str_nullable proc_name_opt nullable_obj_str = + let tags = Tags.create () in + Tags.add tags Tags.nullable_src nullable_obj_str; + (* to be completed once we know if the deref'd expression is directly or transitively @Nullable*) + let problem_str = "" in + _deref_str_null proc_name_opt problem_str tags + +(** dereference strings for nonterminal nil arguments in c/objc variadic methods *) +let deref_str_nil_argument_in_variadic_method pn total_args arg_number = + let tags = Tags.create () in + let function_method, nil_null = + if Procname.is_objc pn then ("method", "nil") else ("function", "null") in + let problem_str = + Printf.sprintf + "could be %s which results in a call to %s with %d arguments instead of %d \ + (%s indicates that the last argument of this variadic %s has been reached)" + nil_null (Procname.to_simplified_string pn) arg_number (total_args - 1) nil_null function_method in + _deref_str_null None problem_str tags + +(** dereference strings for an undefined value coming from the given procedure *) +let deref_str_undef (proc_name, loc) = + let tags = Tags.create () in + let proc_name_str = Procname.to_simplified_string proc_name in + Tags.add tags Tags.call_procedure proc_name_str; + { tags = tags; + value_pre = Some (pointer_or_object ()); + value_post = None; + problem_str = "could be assigned by a call to skip function " ^ proc_name_str ^ + at_line_tag tags Tags.call_line loc ^ " and is dereferenced or freed"; } + +(** dereference strings for a freed pointer dereference *) +let deref_str_freed ra = + let tags = Tags.create () in + let freed_or_closed_by_call = + let freed_or_closed = match ra.Sil.ra_res with + | Sil.Rmemory _ -> "freed" + | Sil.Rfile -> "closed" + | Sil.Rignore -> "freed" + | Sil.Rlock -> "locked" in + freed_or_closed ^ " " ^ by_call_to_ra tags ra in + { tags = tags; + value_pre = Some (pointer_or_object ()); + value_post = None; + problem_str = "was " ^ freed_or_closed_by_call ^ " and is dereferenced or freed" } + +(** dereference strings for a dangling pointer dereference *) +let deref_str_dangling dangling_kind_opt = + let dangling_kind_prefix = match dangling_kind_opt with + | Some Sil.DAuninit -> "uninitialized " + | Some Sil.DAaddr_stack_var -> "deallocated stack " + | Some Sil.DAminusone -> "-1 " + | None -> "" in + { tags = Tags.create (); + value_pre = Some (dangling_kind_prefix ^ (pointer_or_object ())); + value_post = None; + problem_str = "could be dangling and is dereferenced or freed"; } + +(** dereference strings for a pointer size mismatch *) +let deref_str_pointer_size_mismatch typ_from_instr typ_of_object = + let str_from_typ typ = + let pp f () = Sil.pp_typ_full pe_text f typ in + pp_to_string pp () in + { tags = Tags.create (); + value_pre = Some (pointer_or_object ()); + value_post = Some ("of type " ^ str_from_typ typ_from_instr); + problem_str = "could be used to access an object of smaller type " ^ str_from_typ typ_of_object; } + +(** dereference strings for an array out of bound access *) +let deref_str_array_bound size_opt index_opt = + let tags = Tags.create () in + let size_str_opt = match size_opt with + | Some n -> + let n_str = Sil.Int.to_string n in + Tags.add tags Tags.array_size n_str; + Some ("of size " ^ n_str) + | None -> None in + let index_str = match index_opt with + | Some n -> + let n_str = Sil.Int.to_string n in + Tags.add tags Tags.array_index n_str; + "index " ^ n_str + | None -> "an index" in + { tags = tags; + value_pre = Some "array"; + value_post = size_str_opt; + problem_str = "could be accessed with " ^ index_str ^ " out of bounds"; } + +(** dereference strings for an uninitialized access whose lhs has the given attribute *) +let deref_str_uninitialized alloc_att_opt = + let tags = Tags.create () in + let creation_str = match alloc_att_opt with + | Some (Sil.Aresource ({ Sil.ra_kind = Sil.Racquire } as ra)) -> + "after allocation " ^ by_call_to_ra tags ra + | _ -> "after declaration" in + { tags = tags; + value_pre = Some "value"; + value_post = None; + problem_str = "was not initialized " ^ creation_str ^ " and is used"; } + +(** Java unchecked exceptions errors *) +let java_unchecked_exn_desc proc_name exn_name pre_str : error_desc = + ([Procname.to_string proc_name; + "can throw "^(Mangled.to_string exn_name); + "whenever "^pre_str], None, []) + +let desc_assertion_failure loc : error_desc = + (["could be raised"; at_line (Tags.create ()) loc], None, []) + +(** type of access *) +type access = + | Last_assigned of int * bool (* line, null_case_flag *) + | Last_accessed of int * bool (* line, is_nullable flag *) + | Initialized_automatically + | Returned_from_call of int + +let dereference_string deref_str value_str access_opt loc = + let tags = deref_str.tags in + Tags.add tags Tags.value value_str; + let is_call_access = match access_opt with + | Some (Returned_from_call _) -> true + | _ -> false in + let value_desc = + String.concat "" [ + (match deref_str.value_pre with Some s -> s ^ " " | _ -> ""); + (if is_call_access then "returned by " else ""); + value_str; + (match deref_str.value_post with Some s -> " " ^ s | _ -> "")] in + let access_desc = match access_opt with + | None -> + [] + | Some (Last_accessed (n, _)) -> + let line_str = string_of_int n in + Tags.add tags Tags.accessed_line line_str; + ["last accessed on line " ^ line_str] + | Some (Last_assigned (n, ncf)) -> + let line_str = string_of_int n in + Tags.add tags Tags.assigned_line line_str; + ["last assigned on line " ^ line_str] + | Some (Returned_from_call _) -> [] + | Some Initialized_automatically -> + ["initialized automatically"] in + let problem_desc = + let problem_str = + match Tags.get !tags Tags.nullable_src with + | Some nullable_src -> + if nullable_src = value_str then "is annotated with @Nullable and is dereferenced" + else "may hold @Nullable-annotated object " ^ nullable_src ^ " and is dereferenced" + | None -> deref_str.problem_str in + [(problem_str ^ " " ^ at_line tags loc)] in + value_desc:: access_desc @ problem_desc, None, !tags + +let parameter_field_not_null_checked_desc desc exp = + let parameter_not_nullable_desc var = + let var_s = Sil.pvar_to_string var in + let param_not_null_desc = + "Parameter "^var_s^" is not checked for null, there could be a null pointer dereference:" in + match desc with + | descriptions, advice, tags -> + param_not_null_desc:: descriptions, advice, (Tags.parameter_not_null_checked, var_s):: tags in + let field_not_nullable_desc exp = + let rec exp_to_string exp = + match exp with + | Sil.Lfield (exp', field, typ) -> (exp_to_string exp')^" -> "^(Ident.fieldname_to_string field) + | Sil.Lvar pvar -> Mangled.to_string (Sil.pvar_get_name pvar) + | _ -> "" in + let var_s = exp_to_string exp in + let field_not_null_desc = + "Instance variable "^var_s^" is not checked for null, there could be a null pointer dereference:" in + match desc with + | descriptions, advice, tags -> + field_not_null_desc:: descriptions, advice, (Tags.field_not_null_checked, var_s):: tags in + match exp with + | Sil.Lvar var -> parameter_not_nullable_desc var + | Sil.Lfield _ -> field_not_nullable_desc exp + | _ -> desc + +let has_tag desc tag = + match desc with + | descriptions, advice, tags -> + list_exists (fun (tag', value) -> tag = tag') tags + +let is_parameter_not_null_checked_desc desc = has_tag desc Tags.parameter_not_null_checked + +let is_field_not_null_checked_desc desc = has_tag desc Tags.field_not_null_checked + +let is_parameter_field_not_null_checked_desc desc = + is_parameter_not_null_checked_desc desc || + is_field_not_null_checked_desc desc + +let desc_allocation_mismatch alloc dealloc = + let tags = Tags.create () in + let using is_alloc (primitive_pname, called_pname, loc) = + let tag_fun, tag_call, tag_line = + if is_alloc then Tags.alloc_function, Tags.alloc_call, Tags.alloc_line + else Tags.dealloc_function, Tags.dealloc_call, Tags.dealloc_line in + Tags.add tags tag_fun (Procname.to_simplified_string primitive_pname); + Tags.add tags tag_call (Procname.to_simplified_string called_pname); + Tags.add tags tag_line (string_of_int loc.Sil.line); + let by_call = + if Procname.equal primitive_pname called_pname then "" + else " by call to " ^ Procname.to_simplified_string called_pname in + "using " ^ Procname.to_simplified_string primitive_pname ^ by_call ^ " " ^ at_line (Tags.create ()) (* ignore the tag *) loc in + let description = Format.sprintf + "%s %s is deallocated %s" + mem_dyn_allocated + (using true alloc) + (using false dealloc) in + [description], None, !tags + +let desc_comparing_floats_for_equality loc = + let tags = Tags.create () in + ["Comparing floats for equality " ^ at_line tags loc], None, !tags + +let desc_condition_is_assignment loc = + let tags = Tags.create () in + ["Boolean condition is an assignment " ^ at_line tags loc], None, !tags + +let desc_condition_always_true_false i cond_str_opt loc = + let tags = Tags.create () in + let value = match cond_str_opt with + | None -> "" + | Some s -> s in + let tt_ff = if Sil.Int.iszero i then "false" else "true" in + Tags.add tags Tags.value value; + let description = Format.sprintf + "Boolean condition %s is always %s %s" + (if value = "" then "" else " " ^ value) + tt_ff + (at_line tags loc) in + [description], None, !tags + +let desc_deallocate_stack_variable var_str proc_name loc = + let tags = Tags.create () in + Tags.add tags Tags.value var_str; + let description = Format.sprintf + "Stack variable %s is freed by a %s" + var_str + (call_to_at_line tags proc_name loc) in + [description], None, !tags + +let desc_deallocate_static_memory const_str proc_name loc = + let tags = Tags.create () in + Tags.add tags Tags.value const_str; + let description = Format.sprintf + "Constant string %s is freed by a %s" + const_str + (call_to_at_line tags proc_name loc) in + [description], None, !tags + +let desc_class_cast_exception pname_opt typ_str1 typ_str2 exp_str_opt loc = + let tags = Tags.create () in + Tags.add tags Tags.type1 typ_str1; + Tags.add tags Tags.type2 typ_str2; + let in_expression = match exp_str_opt with + | Some exp_str -> + Tags.add tags Tags.value exp_str; + " in expression " ^ exp_str ^ " " + | None -> " " in + let at_line' () = match pname_opt with + | Some proc_name -> "in " ^ call_to_at_line tags proc_name loc + | None -> at_line tags loc in + let description = Format.sprintf + "%s cannot be cast to %s %s %s" + typ_str1 + typ_str2 + in_expression + (at_line' ()) in + [description], None, !tags + +let desc_divide_by_zero expr_str loc = + let tags = Tags.create () in + Tags.add tags Tags.value expr_str; + let description = Format.sprintf + "Expression %s could be zero %s" + expr_str + (at_line tags loc) in + [description], None, !tags + +let desc_leak value_str_opt resource_opt resource_action_opt loc bucket_opt = + let tags = Tags.create () in + let () = match bucket_opt with + | Some bucket -> + Tags.add tags Tags.bucket bucket; + | None -> () in + let value_str = match value_str_opt with + | None -> "" + | Some s -> + Tags.add tags Tags.value s; + s in + let xxx_allocated_to = + let desc_str = + let _to = if value_str_opt = None then "" else " to " in + let _on = if value_str_opt = None then "" else " on " in + match resource_opt with + | Some Sil.Rmemory _ -> mem_dyn_allocated ^ _to ^ value_str + | Some Sil.Rfile -> res_acquired ^ _to ^ value_str + | Some Sil.Rlock -> lock_acquired ^ _on ^ value_str + | Some Sil.Rignore + | None -> if value_str_opt = None then "memory" else value_str in + if desc_str = "" then [] else [desc_str] in + let by_call_to = match resource_action_opt with + | Some ra -> [(by_call_to_ra tags ra)] + | None -> [] in + let is_not_rxxx_after = + let rxxx = match resource_opt with + | Some Sil.Rmemory _ -> reachable + | Some Sil.Rfile + | Some Sil.Rlock -> released + | Some Sil.Rignore + | None -> reachable in + [("is not " ^ rxxx ^ " after " ^ _line tags loc)] in + let bucket_str = + match bucket_opt with + | Some bucket when !Config.show_ml_buckets -> bucket + | _ -> "" in + bucket_str :: xxx_allocated_to @ by_call_to @ is_not_rxxx_after, None, !tags + +(** kind of precondition not met *) +type pnm_kind = + | Pnm_bounds + | Pnm_dangling + +let desc_precondition_not_met kind proc_name loc = + let tags = Tags.create () in + let kind_str = match kind with + | None -> [] + | Some Pnm_bounds -> ["possible array out of bounds"] + | Some Pnm_dangling -> ["possible dangling pointer dereference"] in + kind_str @ ["in " ^ call_to_at_line tags proc_name loc], None, !tags + +let desc_null_test_after_dereference expr_str line loc = + let tags = Tags.create () in + Tags.add tags Tags.dereferenced_line (string_of_int line); + Tags.add tags Tags.value expr_str; + let description = Format.sprintf + "Pointer %s was dereferenced at line %d and is tested for null %s" + expr_str + line + (at_line tags loc) in + [description], None, !tags + +let desc_return_expression_required typ_str loc = + let tags = Tags.create () in + Tags.add tags Tags.value typ_str; + let description = Format.sprintf + "Return statement requires an expression of type %s %s" + typ_str + (at_line tags loc) in + [description], None, !tags + +let desc_retain_cycle prop cycle loc = + Logging.d_strln "Proposition with retain cycle:"; + Prop.d_prop prop; Logging.d_strln ""; + let ct = ref 1 in + let tags = Tags.create () in + let str_cycle = ref "" in + let remove_old s = + match Str.split_delim (Str.regexp_string "&old_") s with + | [_; s'] -> s' + | _ -> s in + let do_edge ((se,_), f, se') = + match se with + | Sil.Eexp(Sil.Lvar pvar, _) when Sil.pvar_equal pvar Sil.block_pvar -> + str_cycle:=!str_cycle^" ("^(string_of_int !ct)^") a block capturing "^(Ident.fieldname_to_string f)^"; "; + ct:=!ct +1; + | Sil.Eexp(Sil.Lvar pvar as e, _) -> + let e_str = Sil.exp_to_string e in + let e_str = if Sil.pvar_is_seed pvar then + remove_old e_str + else e_str in + str_cycle:=!str_cycle^" ("^(string_of_int !ct)^") object "^e_str^" retaining "^e_str^"."^(Ident.fieldname_to_string f)^", "; + ct:=!ct +1 + | Sil.Eexp(Sil.Sizeof(typ, _), _) -> + str_cycle:=!str_cycle^" ("^(string_of_int !ct)^") an object of "^(Sil.typ_to_string typ)^" retaining another object via instance variable "^(Ident.fieldname_to_string f)^", "; + ct:=!ct +1 + | _ -> () in + list_iter do_edge cycle; + let desc = Format.sprintf "Retain cycle involving the following objects: %s %s" + !str_cycle (at_line tags loc) in + [desc], None, !tags + +let desc_return_statement_missing loc = + let tags = Tags.create () in + ["Return statement missing " ^ at_line tags loc], None, !tags + +let desc_return_value_ignored proc_name loc = + let tags = Tags.create () in + ["after " ^ call_to_at_line tags proc_name loc], None, !tags + +let desc_unary_minus_applied_to_unsigned_expression expr_str_opt typ_str loc = + let tags = Tags.create () in + let expression = match expr_str_opt with + | Some s -> + Tags.add tags Tags.value s; + "expression " ^ s + | None -> "an expression" in + let description = Format.sprintf + "A unary minus is applied to %s of type %s %s" + expression + typ_str + (at_line tags loc) in + [description], None, !tags + +let desc_skip_function proc_name = + let tags = Tags.create () in + let proc_name_str = Procname.to_string proc_name in + Tags.add tags Tags.value proc_name_str; + [proc_name_str], None, !tags + +let desc_inherently_dangerous_function proc_name = + let proc_name_str = Procname.to_string proc_name in + let tags = Tags.create () in + Tags.add tags Tags.value proc_name_str; + [proc_name_str], None, !tags + +let desc_stack_variable_address_escape expr_str addr_dexp_str loc = + let tags = Tags.create () in + Tags.add tags Tags.value expr_str; + let escape_to_str = match addr_dexp_str with + | Some s -> + Tags.add tags Tags.escape_to s; + "to " ^ s ^ " " + | None -> "" in + let description = Format.sprintf + "Address of stack variable %s escapes %s%s" + expr_str + escape_to_str + (at_line tags loc) in + [description], None, !tags + +let desc_tainted_value_reaching_sensitive_function expr_str loc = + let tags = Tags.create () in + Tags.add tags Tags.value expr_str; + let description = Format.sprintf + "Value %s can be tainted and is reaching sensitive function %s" + expr_str + (at_line tags loc) in + [description], None, !tags diff --git a/infer/src/backend/localise.mli b/infer/src/backend/localise.mli new file mode 100644 index 000000000..727b49947 --- /dev/null +++ b/infer/src/backend/localise.mli @@ -0,0 +1,214 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Support for localisation *) + +(** type of string used for localisation *) +type t + +(** pretty print a localised string *) +val pp : Format.formatter -> t -> unit + +(** create a localised string from an ordinary string *) +val from_string : string -> t + +(** convert a localised string to an ordinary string *) +val to_string : t -> string + +(** compare two localised strings *) +val compare : t -> t -> int + +val analysis_stops : t +val array_out_of_bounds_l1 : t +val array_out_of_bounds_l2 : t +val array_out_of_bounds_l3 : t +val class_cast_exception : t +val condition_is_assignment : t +val condition_always_false : t +val condition_always_true : t +val comparing_floats_for_equality : t +val dangling_pointer_dereference : t +val deallocate_stack_variable : t +val deallocate_static_memory : t +val deallocation_mismatch : t +val divide_by_zero : t +val field_not_null_checked : t +val inherently_dangerous_function : t +val memory_leak : t +val null_dereference : t +val parameter_not_null_checked : t +val null_test_after_dereference : t +val pointer_size_mismatch : t +val precondition_not_found : t +val precondition_not_met : t +val premature_nil_termination : t +val retain_cycle : t +val resource_leak : t +val return_value_ignored : t +val return_expression_required : t +val return_statement_missing : t +val stack_variable_address_escape : t +val unary_minus_applied_to_unsigned_expression : t +val uninitialized_value : t +val use_after_free : t +val skip_function : t +val skip_pointer_dereference : t +val tainted_value_reaching_sensitive_function : t + +(** description field of error messages *) +type error_desc + +(** empty error description *) +val no_desc: error_desc + +(** verbatim desc from a string, not to be used for user-visible descs *) +val verbatim_desc : string -> error_desc + +(** verbatim desc with custom tags *) +val custom_desc : string -> (string * string) list -> error_desc + +(** verbatim desc with advice and custom tags *) +val custom_desc_with_advice : string -> string -> (string * string) list -> error_desc + +module BucketLevel : sig + val b1 : string (* highest likelyhood *) + val b2 : string + val b3 : string + val b4 : string + val b5 : string (* lowest likelyhood *) +end + +(** returns the value of a tag or the empty string *) +val error_desc_extract_tag_value : error_desc -> string -> string + +(** returns all the tuples (tag, value) of an error_desc *) +val error_desc_to_tag_value_pairs : error_desc -> (string * string) list + +(** returns the content of the value tag of the error_desc *) +val error_desc_get_tag_value : error_desc -> string + +(** returns the content of the call_procedure tag of the error_desc *) +val error_desc_get_tag_call_procedure : error_desc -> string + +(** get the bucket value of an error_desc, if any *) +val error_desc_get_bucket : error_desc -> string option + +(** set the bucket value of an error_desc; the boolean indicates where the bucket should be shown in the message *) +val error_desc_set_bucket : error_desc -> string -> bool -> error_desc + +(** hash function for error_desc *) +val error_desc_hash : error_desc -> int + +(** equality for error_desc *) +val error_desc_equal : error_desc -> error_desc -> bool + +(** pretty print an error description *) +val pp_error_desc : Format.formatter -> error_desc -> unit + +(** pretty print an error advice *) +val pp_error_advice : Format.formatter -> error_desc -> unit + +(** get tags of error description *) +val error_desc_get_tags : error_desc -> (string * string) list + +(** Description functions for error messages *) + +(** dereference strings used to explain a dereference action in an error message *) +type deref_str + +(** dereference strings for null dereference *) +val deref_str_null : Procname.t option -> deref_str + +(** dereference strings for null dereference due to Nullable annotation *) +val deref_str_nullable : Procname.t option -> string -> deref_str + +(** dereference strings for an undefined value coming from the given procedure *) +val deref_str_undef : Procname.t * Sil.location -> deref_str + +(** dereference strings for a freed pointer dereference *) +val deref_str_freed : Sil.res_action -> deref_str + +(** dereference strings for a dangling pointer dereference *) +val deref_str_dangling : Sil.dangling_kind option -> deref_str + +(** dereference strings for an array out of bound access *) +val deref_str_array_bound : Sil.Int.t option -> Sil.Int.t option -> deref_str + +(** dereference strings for an uninitialized access whose lhs has the given attribute *) +val deref_str_uninitialized : Sil.attribute option -> deref_str + +(** dereference strings for nonterminal nil arguments in c/objc variadic methods *) +val deref_str_nil_argument_in_variadic_method : Procname.t -> int -> int -> deref_str + +(** dereference strings for a pointer size mismatch *) +val deref_str_pointer_size_mismatch : Sil.typ -> Sil.typ -> deref_str + +(** type of access *) +type access = + | Last_assigned of int * bool (* line, null_case_flag *) + | Last_accessed of int * bool (* line, is_nullable flag *) + | Initialized_automatically + | Returned_from_call of int + +val dereference_string : deref_str -> string -> access option -> Sil.location -> error_desc + +val parameter_field_not_null_checked_desc : error_desc -> Sil.exp -> error_desc + +val is_parameter_not_null_checked_desc : error_desc -> bool + +val is_field_not_null_checked_desc : error_desc -> bool + +val is_parameter_field_not_null_checked_desc : error_desc -> bool + +val desc_allocation_mismatch : Procname.t * Procname.t * Sil.location -> Procname.t * Procname.t * Sil.location -> error_desc + +val desc_class_cast_exception : Procname.t option -> string -> string -> string option -> Sil.location -> error_desc + +val desc_comparing_floats_for_equality : Sil.location -> error_desc + +val desc_condition_is_assignment : Sil.location -> error_desc + +val desc_condition_always_true_false : Sil.Int.t -> string option -> Sil.location -> error_desc + +val desc_deallocate_stack_variable : string -> Procname.t -> Sil.location -> error_desc + +val desc_deallocate_static_memory : string -> Procname.t -> Sil.location -> error_desc + +val desc_divide_by_zero : string -> Sil.location -> error_desc + +val desc_leak : string option -> Sil.resource option -> Sil.res_action option -> Sil.location -> string option -> error_desc + +val desc_null_test_after_dereference : string -> int -> Sil.location -> error_desc + +val java_unchecked_exn_desc : Procname.t -> Mangled.t -> string -> error_desc + +(* Create human-readable error description for assertion failures *) +val desc_assertion_failure : Sil.location -> error_desc + +(** kind of precondition not met *) +type pnm_kind = + | Pnm_bounds + | Pnm_dangling + +val desc_precondition_not_met : pnm_kind option -> Procname.t -> Sil.location -> error_desc + +val desc_return_expression_required : string -> Sil.location -> error_desc + +val desc_retain_cycle : Prop.normal Prop.t -> ((Sil.strexp * Sil.typ) * Ident.fieldname * Sil.strexp) list -> Sil.location -> error_desc + +val desc_return_statement_missing : Sil.location -> error_desc + +val desc_return_value_ignored : Procname.t -> Sil.location -> error_desc + +val desc_stack_variable_address_escape : string -> string option -> Sil.location -> error_desc + +val desc_skip_function : Procname.t -> error_desc + +val desc_inherently_dangerous_function : Procname.t -> error_desc + +val desc_unary_minus_applied_to_unsigned_expression : string option -> string -> Sil.location -> error_desc + +val desc_tainted_value_reaching_sensitive_function : string -> Sil.location -> error_desc diff --git a/infer/src/backend/logging.ml b/infer/src/backend/logging.ml new file mode 100644 index 000000000..bb6f20183 --- /dev/null +++ b/infer/src/backend/logging.ml @@ -0,0 +1,220 @@ +(* +* Copyright (c) 2009-2013 Monoidics ltd. +* Copyright (c) 2013- Facebook. +* All rights reserved. +*) + +(** log messages at different levels of verbosity *) + +module F = Format +open Utils + +type colour = + C30 | C31 | C32 | C33 | C34 | C35 | C36 + +let black = C30 +let red = C31 +let green = C32 +let yellow = C33 +let blue = C34 +let magenta = C35 +let cyan = C36 + +let next_c = function + | C30 -> assert false + | C31 -> C32 + | C32 -> C33 + | C33 -> C34 + | C34 -> C35 + | C35 -> C36 + | C36 -> C31 + +let current_thread_colour = ref C31 + +let next_colour () = + let c = !current_thread_colour in + current_thread_colour := next_c c; + c + +let _set_print_colour fmt = function + | C30 -> F.fprintf fmt "\027[30m" + | C31 -> F.fprintf fmt "\027[31m" + | C32 -> F.fprintf fmt "\027[32m" + | C33 -> F.fprintf fmt "\027[33m" + | C34 -> F.fprintf fmt "\027[34m" + | C35 -> F.fprintf fmt "\027[35m" + | C36 -> F.fprintf fmt "\027[36m" + +let change_terminal_colour c = _set_print_colour F.std_formatter c +let change_terminal_colour_err c = _set_print_colour F.err_formatter c + +(** Can be applied to any number of arguments and throws them all away *) +let rec throw_away x = Obj.magic throw_away + +let use_colours = ref false + +(* =============== START of module MyErr =============== *) +(** type of printable elements *) +type print_type = + | PTatom + | PTdecrease_indent + | PTexp + | PTexp_list + | PThpred + | PTincrease_indent + | PTinstr + | PTinstr_list + | PTjprop_list + | PTjprop_short + | PTloc + | PTnode_instrs + | PToff + | PToff_list + | PTpath + | PTprop + | PTproplist + | PTprop_list_with_typ + | PTprop_with_typ + | PTpvar + | PTspec + | PTstr + | PTstr_color + | PTstrln + | PTstrln_color + | PTpathset + | PTpi + | PTsexp + | PTsexp_list + | PTsigma + | PTtexp_full + | PTsub + | PTtyp_full + | PTtyp_list + | PTwarning + | PTerror + | PTinfo + +(** delayable print action *) +type print_action = + print_type * Obj.t (** data to be printed *) + +let delayed_actions = ref [] + +(** hook for the current printer of delayed print actions *) +let printer_hook = ref (Obj.magic ()) + +(** Current formatter for the out stream *) +let current_out_formatter = ref F.std_formatter + +(** Current formatter for the err stream *) +let current_err_formatter = ref F.err_formatter + +(** Get the current out formatter *) +let get_out_formatter () = !current_out_formatter + +(** Get the current err formatter *) +let get_err_formatter fmt = !current_err_formatter + +(** Set the current out formatter *) +let set_out_formatter fmt = + current_out_formatter := fmt + +(** Set the current err formatter *) +let set_err_formatter fmt = + current_err_formatter := fmt + +(** Flush the current streams *) +let flush_streams () = + F.fprintf !current_out_formatter "@?"; + F.fprintf !current_err_formatter "@?" + +(** extend the current print log *) +let add_print_action pact = + if !Config.write_html then delayed_actions := pact :: !delayed_actions + else if not !Config.test then !printer_hook !current_out_formatter pact + +(** reset the delayed print actions *) +let reset_delayed_prints () = + delayed_actions := [] + +(** return the delayed print actions *) +let get_delayed_prints () = + !delayed_actions + +let current_colour = ref black + +let set_colour c = + use_colours := true; + current_colour := c + +let do_print fmt fmt_string = + begin + if !Config.num_cores > 1 then + begin + if !Config.in_child_process + then change_terminal_colour !current_thread_colour + else change_terminal_colour black + end + else if !use_colours then + change_terminal_colour !current_colour + end; + F.fprintf fmt fmt_string + +(** print on the out stream *) +let out fmt_string = + do_print !current_out_formatter fmt_string + +(** print on the err stream *) +let err fmt_string = + do_print !current_err_formatter fmt_string + +(** print immediately to standard error *) +let stderr fmt_string = + do_print F.err_formatter fmt_string + +(** print immediately to standard output *) +let stdout fmt_string = + do_print F.std_formatter fmt_string + +(** print a warning with information of the position in the ml source where it oririnated. +use as: warning_position "description" (try assert false with Assert_failure x -> x); *) +let warning_position (s: string) (mloc: ml_location) = + err "WARNING: %s in %a@." s pp_ml_location_opt (Some mloc) + +(** dump a string *) +let d_str (s: string) = add_print_action (PTstr, Obj.repr s) + +(** dump a string with the given color *) +let d_str_color (c: color) (s: string) = add_print_action (PTstr_color, Obj.repr (s, c)) + +(** dump an error string *) +let d_error (s: string) = add_print_action (PTerror, Obj.repr s) + +(** dump a warning string *) +let d_warning (s: string) = add_print_action (PTwarning, Obj.repr s) + +(** dump an info string *) +let d_info (s: string) = add_print_action (PTinfo, Obj.repr s) + +(** dump a string plus newline *) +let d_strln (s: string) = add_print_action (PTstrln, Obj.repr s) + +(** dump a string plus newline with the given color *) +let d_strln_color (c: color) (s: string) = add_print_action (PTstrln_color, Obj.repr (s, c)) + +(** dump a newline *) +let d_ln () = add_print_action (PTstrln, Obj.repr "") + +(** dump an indentation *) +let d_indent indent = + let s = ref "" in + for i = 1 to indent do s := " " ^ !s done; + if indent <> 0 then add_print_action (PTstr, Obj.repr !s) + +(** dump command to increase the indentation level *) +let d_increase_indent (indent: int) = + add_print_action (PTincrease_indent, Obj.repr indent) + +(** dump command to decrease the indentation level *) +let d_decrease_indent (indent: int) = + add_print_action (PTdecrease_indent, Obj.repr indent) diff --git a/infer/src/backend/logging.mli b/infer/src/backend/logging.mli new file mode 100644 index 000000000..7c5796b21 --- /dev/null +++ b/infer/src/backend/logging.mli @@ -0,0 +1,148 @@ +(* +* Copyright (c) 2009-2013 Monoidics ltd. +* Copyright (c) 2013- Facebook. +* All rights reserved. +*) + +open Utils + +(** log messages at different levels of verbosity *) + +type colour + +val black : colour +val red : colour +val green : colour +val yellow : colour +val blue : colour +val magenta : colour +val cyan : colour + +(** Return the next "coloured" (i.e. not black) colour *) +val next_colour : unit -> colour + +(** Print escape code to change the terminal's colour *) +val change_terminal_colour : colour -> unit + +(** type of printable elements *) +type print_type = + | PTatom + | PTdecrease_indent + | PTexp + | PTexp_list + | PThpred + | PTincrease_indent + | PTinstr + | PTinstr_list + | PTjprop_list + | PTjprop_short + | PTloc + | PTnode_instrs + | PToff + | PToff_list + | PTpath + | PTprop + | PTproplist + | PTprop_list_with_typ + | PTprop_with_typ + | PTpvar + | PTspec + | PTstr + | PTstr_color + | PTstrln + | PTstrln_color + | PTpathset + | PTpi + | PTsexp + | PTsexp_list + | PTsigma + | PTtexp_full + | PTsub + | PTtyp_full + | PTtyp_list + | PTwarning + | PTerror + | PTinfo + +(** delayable print action *) +type print_action = + print_type * Obj.t (** data to be printed *) + +(** hook for the current printer of delayed print actions *) +val printer_hook : (Format.formatter -> print_action -> unit) ref + +(** extend he current print log *) +val add_print_action : print_action -> unit + +(** return the delayed print actions *) +val get_delayed_prints : unit -> print_action list + +(** reset the delayed print actions *) +val reset_delayed_prints : unit -> unit + +(** Set the colours of the printer *) +val set_colour : colour -> unit + +(** print to the current out stream *) +val out : ('a, Format.formatter, unit) format -> 'a + +(** print to the current err stream *) +val err : ('a, Format.formatter, unit) format -> 'a + +(** print immediately to standard error *) +val stderr : ('a, Format.formatter, unit) format -> 'a + +(** print immediately to standard output *) +val stdout : ('a, Format.formatter, unit) format -> 'a + +(** Get the current out formatter *) +val get_out_formatter : unit -> Format.formatter + +(** Get the current err formatter *) +val get_err_formatter : unit -> Format.formatter + +(** Set the current out formatter *) +val set_out_formatter : Format.formatter -> unit + +(** Set the current err formatter *) +val set_err_formatter : Format.formatter -> unit + +(** Flush the current streams *) +val flush_streams : unit -> unit + +(** print a warning with information of the position in the ml source where it oririnated. +use as: warning_position "description" (try assert false with Assert_failure x -> x); *) +val warning_position: string -> ml_location -> unit + +(** dump a string *) +val d_str : string -> unit + +(** dump a string with the given color *) +val d_str_color : color -> string -> unit + +(** dump a string plus newline *) +val d_strln : string -> unit + +(** dump a string plus newline with the given color *) +val d_strln_color : color -> string -> unit + +(** dump a newline *) +val d_ln : unit -> unit + +(** dump an error string *) +val d_error : string -> unit + +(** dump a warning string *) +val d_warning : string -> unit + +(** dump an info string *) +val d_info : string -> unit + +(** dump an indentation *) +val d_indent : int -> unit + +(** dump command to increase the indentation level *) +val d_increase_indent : int -> unit + +(** dump command to decrease the indentation level *) +val d_decrease_indent : int -> unit diff --git a/infer/src/backend/mangled.ml b/infer/src/backend/mangled.ml new file mode 100644 index 000000000..98f7e7325 --- /dev/null +++ b/infer/src/backend/mangled.ml @@ -0,0 +1,68 @@ +(* +* Copyright (c) 2009-2013 Monoidics ltd. +* Copyright (c) 2013- Facebook. +* All rights reserved. +*) + +(** Module for Mangled Names *) + +module F = Format +open Utils + +type t = + { plain: string; + mangled: string option } + +let mangled_compare so1 so2 = match so1, so2 with + | None, None -> 0 + | None, Some _ -> -1 + | Some _, None -> 1 + | Some s1, Some s2 -> string_compare s1 s2 + +let compare pn1 pn2 = + let n = string_compare pn1.plain pn2.plain in + if n <> 0 then n else mangled_compare pn1.mangled pn2.mangled + +let equal pn1 pn2 = + compare pn1 pn2 = 0 + +(** Convert a string to a mangled name *) +let from_string (s: string) = + { plain = s; + mangled = None } + +(** Create a mangled name from a plain and mangled string *) +let mangled (plain: string) (mangled: string) = + { plain = plain; + mangled = Some (plain ^ "{" ^ mangled ^ "}") } + +(** Convert a mangled name to a string *) +let to_string (pn: t) = + pn.plain + +(** Convert a full mangled name to a string *) +let to_string_full (pn: t) = + match pn.mangled with + | Some mangled -> pn.plain ^ "{" ^ mangled ^ "}" + | None -> pn.plain + +(** Get mangled string if given *) +let get_mangled pn = match pn.mangled with + | Some s -> s + | None -> pn.plain + +(** Create a mangled type name from a package name and a class name *) +let from_package_class package_name class_name = + from_string (package_name ^ "." ^ class_name) + +(** Pretty print a mangled name *) +let pp f pn = + F.fprintf f "%s" (to_string pn) + + +type mangled_t = t +module MangledSet = Set.Make + (struct + type t = mangled_t + let compare = compare + end) diff --git a/infer/src/backend/mangled.mli b/infer/src/backend/mangled.mli new file mode 100644 index 000000000..c226b94d2 --- /dev/null +++ b/infer/src/backend/mangled.mli @@ -0,0 +1,42 @@ +(* +* Copyright (c) 2009-2013 Monoidics ltd. +* Copyright (c) 2013- Facebook. +* All rights reserved. +*) + +(** Module for Mangled Names *) + +open Utils + +(** Type of mangled names *) +type t + +(** Comparison for mangled names *) +val compare : t -> t -> int + +(** Equality for mangled names *) +val equal : t -> t -> bool + +(** Convert a string to a mangled name *) +val from_string : string -> t + +(** Create a mangled type name from a package name and a class name *) +val from_package_class : string -> string -> t + +(** Create a mangled name from a plain and mangled string *) +val mangled : string -> string -> t + +(** Convert a mangled name to a string *) +val to_string : t -> string + +(** Convert a full mangled name to a string *) +val to_string_full : t -> string + +(** Get mangled string if given *) +val get_mangled : t -> string + +(** Pretty print a mangled name *) +val pp : Format.formatter -> t -> unit + +(** Set of Mangled. *) +module MangledSet : Set.S with type elt = t diff --git a/infer/src/backend/match.ml b/infer/src/backend/match.ml new file mode 100644 index 000000000..8f4d00e47 --- /dev/null +++ b/infer/src/backend/match.ml @@ -0,0 +1,776 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Functions for "Smart" Pattern Matching *) + +module L = Logging +module F = Format +open Utils + +let mem_idlist i l = + list_exists (Ident.equal i) l + +(** Type for a hpred pattern. flag=false means that the implication +between hpreds is not considered, and flag = true means that it is +considered during pattern matching *) +type hpred_pat = { hpred : Sil.hpred; flag : bool } + +let pp_hpat pe f hpat = + F.fprintf f "%a" (Sil.pp_hpred pe) hpat.hpred + +let rec pp_hpat_list pe f = function + | [] -> () + | [hpat] -> + F.fprintf f "%a" (pp_hpat pe) hpat + | hpat:: hpats -> + F.fprintf f "%a * %a" (pp_hpat pe) hpat (pp_hpat_list pe) hpats + +(** Checks e1 = e2[sub ++ sub'] for some sub' with dom(sub') subseteq vars. +Returns (sub ++ sub', vars - dom(sub')). *) +let rec exp_match e1 sub vars e2 : (Sil.subst * Ident.t list) option = + let check_equal sub vars e1 e2 = + let e2_inst = Sil.exp_sub sub e2 + in if (Sil.exp_equal e1 e2_inst) then Some(sub, vars) else None in + match e1, e2 with + | _, Sil.Var id2 when (Ident.is_primed id2 && mem_idlist id2 vars) -> + let vars_new = list_filter (fun id -> not (Ident.equal id id2)) vars in + let sub_new = match (Sil.extend_sub sub id2 e1) with + | None -> assert false (* happens when vars contains the same variable twice. *) + | Some sub_new -> sub_new + in Some (sub_new, vars_new) + | _, Sil.Var _ -> + check_equal sub vars e1 e2 + | Sil.Var _, _ -> + None + | Sil.Const _, _ | _, Sil.Const _ -> + check_equal sub vars e1 e2 + | Sil.Sizeof _, _ | _, Sil.Sizeof _ -> + check_equal sub vars e1 e2 + | Sil.Cast (t1, e1'), Sil.Cast (t2, e2') -> (* we are currently ignoring cast *) + exp_match e1' sub vars e2' + | Sil.Cast _, _ | _, Sil.Cast _ -> + None + | Sil.UnOp(o1, e1', _), Sil.UnOp(o2, e2', _) when Sil.unop_equal o1 o2 -> + exp_match e1' sub vars e2' + | Sil.UnOp _, _ | _, Sil.UnOp _ -> + None (* Naive *) + | Sil.BinOp(b1, e1', e1''), Sil.BinOp(b2, e2', e2'') when Sil.binop_equal b1 b2 -> + (match exp_match e1' sub vars e2' with + | None -> None + | Some (sub', vars') -> exp_match e1'' sub' vars' e2'') + | Sil.BinOp _, _ | _, Sil.BinOp _ -> + None (* Naive *) + | Sil.Lvar _, _ | _, Sil.Lvar _ -> + check_equal sub vars e1 e2 + | Sil.Lfield(e1', fld1, t1), Sil.Lfield(e2', fld2, t2) when (Sil.fld_equal fld1 fld2) -> + exp_match e1' sub vars e2' + | Sil.Lfield _, _ | _, Sil.Lfield _ -> + None + | Sil.Lindex(base1, idx1), Sil.Lindex(base2, idx2) -> + (match exp_match base1 sub vars base2 with + | None -> None + | Some (sub', vars') -> exp_match idx1 sub' vars' idx2) + +let exp_list_match es1 sub vars es2 = + let f res_acc (e1, e2) = match res_acc with + | None -> None + | Some (sub_acc, vars_leftover) -> exp_match e1 sub_acc vars_leftover e2 in + let es_combined = try list_combine es1 es2 with Invalid_argument _ -> assert false in + let es_match_res = list_fold_left f (Some (sub, vars)) es_combined + in es_match_res + +(** Checks sexp1 = sexp2[sub ++ sub'] for some sub' with +dom(sub') subseteq vars. Returns (sub ++ sub', vars - dom(sub')). +WARNING: This function does not consider the fact that the analyzer +sometimes forgets fields of hpred. It can possibly cause a problem. *) +let rec strexp_match sexp1 sub vars sexp2 : (Sil.subst * Ident.t list) option = + match sexp1, sexp2 with + | Sil.Eexp (exp1, inst1), Sil.Eexp (exp2, inst2) -> + exp_match exp1 sub vars exp2 + | Sil.Eexp _, _ | _, Sil.Eexp _ -> + None + | Sil.Estruct (fsel1, _), Sil.Estruct (fsel2, _) -> + fsel_match fsel1 sub vars fsel2 + | Sil.Estruct _, _ | _, Sil.Estruct _ -> + None + | Sil.Earray (size1, isel1, _), Sil.Earray (size2, isel2, _) -> + (match exp_match size1 sub vars size2 with + | Some (sub', vars') -> isel_match isel1 sub' vars' isel2 + | None -> None) + + +(** Checks fsel1 = fsel2[sub ++ sub'] for some sub' with +dom(sub') subseteq vars. Returns (sub ++ sub', vars - dom(sub')). *) +and fsel_match fsel1 sub vars fsel2 = + match fsel1, fsel2 with + | [], [] -> Some (sub, vars) + | [], _ -> None + | _, [] -> + if (!Config.abs_struct <= 0) then None + else Some (sub, vars) (* This can lead to great information loss *) + | (fld1, se1') :: fsel1', (fld2, se2') :: fsel2' -> + let n = Sil.fld_compare fld1 fld2 in + if (n = 0) then begin + match strexp_match se1' sub vars se2' with + | None -> None + | Some (sub', vars') -> fsel_match fsel1' sub' vars' fsel2' + end + else if (n < 0 && !Config.abs_struct > 0) then + fsel_match fsel1' sub vars fsel2 + (* This can lead to great information loss *) + else None + +(** Checks isel1 = isel2[sub ++ sub'] for some sub' with +dom(sub') subseteq vars. Returns (sub ++ sub', vars - dom(sub')). *) +and isel_match isel1 sub vars isel2 = + match isel1, isel2 with + | [], [] -> Some (sub, vars) + | [], _ | _, [] -> None + | (idx1, se1') :: isel1', (idx2, se2') :: isel2' -> + let idx2 = Sil.exp_sub sub idx2 in + let sanity_check = not (list_exists (fun id -> Sil.ident_in_exp id idx2) vars) in + if (not sanity_check) then begin + let pe = pe_text in + L.out "@[.... Sanity Check Failure while Matching Index-Strexps ....@."; + L.out "@[<4> IDX1: %a, STREXP1: %a@." (Sil.pp_exp pe) idx1 (Sil.pp_sexp pe) se1'; + L.out "@[<4> IDX2: %a, STREXP2: %a@\n@." (Sil.pp_exp pe) idx2 (Sil.pp_sexp pe) se2'; + assert false + end + else if Sil.exp_equal idx1 idx2 then begin + match strexp_match se1' sub vars se2' with + | None -> None + | Some (sub', vars') -> isel_match isel1' sub' vars' isel2' + end + else None + + +(* extends substitution sub by creating a new substitution for vars *) +let sub_extend_with_ren (sub: Sil.subst) vars = + (* + let check_precondition () = + let dom = Sil.sub_domain sub in + let overlap = list_exists (fun id -> list_exists (Ident.equal id) dom) vars in + if overlap then assert false in + check_precondition (); + *) + let f id = (id, Sil.Var (Ident.create_fresh Ident.kprimed)) in + let renaming_for_vars = Sil.sub_of_list (list_map f vars) in + Sil.sub_join sub renaming_for_vars + +type sidecondition = Prop.normal Prop.t -> Sil.subst -> bool + +let rec execute_with_backtracking = function + | [] -> None + | [f] -> f () + | f:: fs -> + let res_f = f () + in match res_f with + | None -> execute_with_backtracking fs + | Some _ -> res_f + +let rec instantiate_to_emp p condition sub vars = function + | [] -> if condition p sub then Some(sub, p) else None + | hpat:: hpats -> + if not hpat.flag then None + else match hpat.hpred with + | Sil.Hpointsto _ | Sil.Hlseg (Sil.Lseg_NE, _, _, _, _) | Sil.Hdllseg (Sil.Lseg_NE, _, _, _, _, _, _) -> None + | Sil.Hlseg (k, _, e1, e2, _) -> + let fully_instantiated = not (list_exists (fun id -> Sil.ident_in_exp id e1) vars) + in if (not fully_instantiated) then None else + let e1' = Sil.exp_sub sub e1 + in begin + match exp_match e1' sub vars e2 with + | None -> None + | Some (sub_new, vars_leftover) -> + instantiate_to_emp p condition sub_new vars_leftover hpats + end + | Sil.Hdllseg (k, _, iF, oB, oF, iB, _) -> + let fully_instantiated = + not (list_exists (fun id -> Sil.ident_in_exp id iF || Sil.ident_in_exp id oB) vars) + in if (not fully_instantiated) then None else + let iF' = Sil.exp_sub sub iF in + let oB' = Sil.exp_sub sub oB + in match exp_list_match [iF'; oB'] sub vars [oF; iB] with + | None -> None + | Some (sub_new, vars_leftover) -> + instantiate_to_emp p condition sub_new vars_leftover hpats + +(* This function has to be changed in order to +* implement the idea "All lsegs outside are NE, and all lsegs inside +* are PE" *) +let rec iter_match_with_impl iter condition sub vars hpat hpats = + + (* + L.out "@[.... iter_match_with_impl ....@."; + L.out "@[<4> sub: %a@\n@." pp_sub sub; + L.out "@[<4> PROP: %a@\n@." pp_prop (Prop.prop_iter_to_prop iter); + L.out "@[<4> hpred: %a@\n@." pp_hpat hpat; + L.out "@[<4> hpred_rest: %a@\n@." pp_hpat_list hpats; + *) + + let do_next iter_cur _ = match Prop.prop_iter_next iter_cur with + | None -> None + | Some iter_next -> iter_match_with_impl iter_next condition sub vars hpat hpats + in + let do_empty_hpats iter_cur _ = + let (sub_new, vars_leftover) = match Prop.prop_iter_current iter_cur with + | _, (sub_new, vars_leftover) -> (sub_new, vars_leftover) in + let sub_res = sub_extend_with_ren sub_new vars_leftover in + let p_leftover = Prop.prop_iter_remove_curr_then_to_prop iter_cur in + (* + L.out "@[.... iter_match_with_impl (final condtion check) ....@\n@."; + L.out "@[<4> sub_res : %a@\n@." pp_sub sub_res; + L.out "@[<4> p_leftover : %a@\n@." pp_prop p_leftover; + *) + if condition p_leftover sub_res then Some (sub_res, p_leftover) else None + in + let do_nonempty_hpats iter_cur _ = + let (sub_new, vars_leftover) = match Prop.prop_iter_current iter_cur with + | _, (sub_new, vars_leftover) -> (sub_new, vars_leftover) in + let (hpat_next, hpats_rest) = match hpats with + | [] -> assert false + | hpat_next :: hpats_rest -> (hpat_next, hpats_rest) in + let p_rest = Prop.prop_iter_remove_curr_then_to_prop iter_cur + in prop_match_with_impl_sub p_rest condition sub_new vars_leftover hpat_next hpats_rest + in + let gen_filter_pointsto lexp2 strexp2 te2 = function + | Sil.Hpointsto (lexp1, strexp1, te1) when Sil.exp_equal te1 te2 -> + (match (exp_match lexp1 sub vars lexp2) with + | None -> None + | Some (sub', vars_leftover) -> strexp_match strexp1 sub' vars_leftover strexp2) + | _ -> None + in + let gen_filter_lseg k2 para2 e_start2 e_end2 es_shared2 = function + | Sil.Hpointsto _ -> None + | Sil.Hlseg (k1, para1, e_start1, e_end1, es_shared1) -> + let do_kinds_match = match k1, k2 with + | Sil.Lseg_NE, Sil.Lseg_NE | Sil.Lseg_NE, Sil.Lseg_PE | Sil.Lseg_PE, Sil.Lseg_PE -> true + | Sil.Lseg_PE, Sil.Lseg_NE -> false in + (* let do_paras_match = hpara_match_with_impl hpat.flag para1 para2 *) + let do_paras_match = hpara_match_with_impl true para1 para2 + in if not (do_kinds_match && do_paras_match) then None + else + let es1 = [e_start1; e_end1]@es_shared1 in + let es2 = [e_start2; e_end2]@es_shared2 + in exp_list_match es1 sub vars es2 + | Sil.Hdllseg _ -> None + in + let gen_filter_dllseg k2 para2 iF2 oB2 oF2 iB2 es_shared2 = function + | Sil.Hpointsto _ | Sil.Hlseg _ -> None + | Sil.Hdllseg (k1, para1, iF1, oB1, oF1, iB1, es_shared1) -> + let do_kinds_match = match k1, k2 with + | Sil.Lseg_NE, Sil.Lseg_NE | Sil.Lseg_NE, Sil.Lseg_PE | Sil.Lseg_PE, Sil.Lseg_PE -> true + | Sil.Lseg_PE, Sil.Lseg_NE -> false in + (* let do_paras_match = hpara_dll_match_with_impl hpat.flag para1 para2 *) + let do_paras_match = hpara_dll_match_with_impl true para1 para2 + in if not (do_kinds_match && do_paras_match) then None + else + let es1 = [iF1; oB1; oF1; iB1]@es_shared1 in + let es2 = [iF2; oB2; oF2; iB2]@es_shared2 + in exp_list_match es1 sub vars es2 + + in match hpat.hpred with + | Sil.Hpointsto (lexp2, strexp2, te2) -> + let filter = gen_filter_pointsto lexp2 strexp2 te2 + in begin match (Prop.prop_iter_find iter filter), hpats with + | (None, _) -> None + | (Some iter_cur, []) -> + do_empty_hpats iter_cur () + | (Some iter_cur, _) -> + execute_with_backtracking [do_nonempty_hpats iter_cur; do_next iter_cur] + end + | Sil.Hlseg (k2, para2, e_start2, e_end2, es_shared2) -> + let filter = gen_filter_lseg k2 para2 e_start2 e_end2 es_shared2 in + let do_emp_lseg _ = + let fully_instantiated_start2 = not (list_exists (fun id -> Sil.ident_in_exp id e_start2) vars) in + if (not fully_instantiated_start2) then None + else + let e_start2' = Sil.exp_sub sub e_start2 in + match (exp_match e_start2' sub vars e_end2, hpats) with + | None, _ -> + (* + L.out "@.... iter_match_with_impl (empty_case, fail) ....@\n@."; + L.out "@[<4> sub: %a@\n@." pp_sub sub; + L.out "@[<4> e_start2': %a@\n@." pp_exp e_start2'; + L.out "@[<4> e_end2: %a@\n@." pp_exp e_end2; + *) + None + | Some (sub_new, vars_leftover), [] -> + let sub_res = sub_extend_with_ren sub_new vars_leftover in + let p_leftover = Prop.prop_iter_to_prop iter in + if condition p_leftover sub_res then Some(sub_res, p_leftover) else None + | Some (sub_new, vars_leftover), hpat_next:: hpats_rest -> + let p = Prop.prop_iter_to_prop iter in + prop_match_with_impl_sub p condition sub_new vars_leftover hpat_next hpats_rest in + let do_para_lseg _ = + let (para2_exist_vars, para2_inst) = Sil.hpara_instantiate para2 e_start2 e_end2 es_shared2 in + (* let allow_impl hpred = {hpred=hpred; flag=hpat.flag} in *) + let allow_impl hpred = { hpred = hpred; flag = true } in + let (para2_hpat, para2_hpats) = match list_map allow_impl para2_inst with + | [] -> assert false (* the body of a parameter should contain at least one * conjunct *) + | para2_pat :: para2_pats -> (para2_pat, para2_pats) in + let new_vars = para2_exist_vars @ vars in + let new_hpats = para2_hpats @ hpats + in match (iter_match_with_impl iter condition sub new_vars para2_hpat new_hpats) with + | None -> None + | Some (sub_res, p_leftover) when condition p_leftover sub_res -> + let not_in_para2_exist_vars id = + not (list_exists (fun id' -> Ident.equal id id') para2_exist_vars) in + let sub_res' = Sil.sub_filter not_in_para2_exist_vars sub_res + in Some (sub_res', p_leftover) + | Some _ -> None + in begin match ((Prop.prop_iter_find iter filter), hpats) with + | (None, _) when not hpat.flag -> + (* L.out "@[.... iter_match_with_impl (lseg not-matched) ....@\n@."; *) + None + | (None, _) when Sil.lseg_kind_equal k2 Sil.Lseg_NE -> + (* L.out "@[.... iter_match_with_impl (lseg not-matched) ....@\n@."; *) + do_para_lseg () + | (None, _) -> + (* L.out "@[.... iter_match_with_impl (lseg not-matched) ....@\n@."; *) + execute_with_backtracking [do_emp_lseg; do_para_lseg] + | (Some iter_cur, []) -> + (* L.out "@[.... iter_match_with_impl (lseg matched) ....@\n@."; *) + do_empty_hpats iter_cur () + | (Some iter_cur, _) -> + (* L.out "@[.... iter_match_with_impl (lseg matched) ....@\n@."; *) + execute_with_backtracking [do_nonempty_hpats iter_cur; do_next iter_cur] + end + | Sil.Hdllseg (k2, para2, iF2, oB2, oF2, iB2, es_shared2) -> + let filter = gen_filter_dllseg k2 para2 iF2 oB2 oF2 iB2 es_shared2 in + let do_emp_dllseg _ = + let fully_instantiated_iFoB2 = + not (list_exists (fun id -> Sil.ident_in_exp id iF2 || Sil.ident_in_exp id oB2) vars) + in if (not fully_instantiated_iFoB2) then None else + let iF2' = Sil.exp_sub sub iF2 in + let oB2' = Sil.exp_sub sub oB2 + in match (exp_list_match [iF2'; oB2'] sub vars [oF2; iB2], hpats) with + | None, _ -> None + | Some (sub_new, vars_leftover), [] -> + let sub_res = sub_extend_with_ren sub_new vars_leftover in + let p_leftover = Prop.prop_iter_to_prop iter + in if condition p_leftover sub_res then Some(sub_res, p_leftover) else None + | Some (sub_new, vars_leftover), hpat_next:: hpats_rest -> + let p = Prop.prop_iter_to_prop iter + in prop_match_with_impl_sub p condition sub_new vars_leftover hpat_next hpats_rest in + let do_para_dllseg _ = + let fully_instantiated_iF2 = not (list_exists (fun id -> Sil.ident_in_exp id iF2) vars) + in if (not fully_instantiated_iF2) then None else + let iF2' = Sil.exp_sub sub iF2 + in match exp_match iF2' sub vars iB2 with + | None -> None + | Some (sub_new, vars_leftover) -> + let (para2_exist_vars, para2_inst) = Sil.hpara_dll_instantiate para2 iF2 oB2 oF2 es_shared2 in + (* let allow_impl hpred = {hpred=hpred; flag=hpat.flag} in *) + let allow_impl hpred = { hpred = hpred; flag = true } in + let (para2_hpat, para2_hpats) = match list_map allow_impl para2_inst with + | [] -> assert false (* the body of a parameter should contain at least one * conjunct *) + | para2_pat :: para2_pats -> (para2_pat, para2_pats) in + let new_vars = para2_exist_vars @ vars_leftover in + let new_hpats = para2_hpats @ hpats + in match (iter_match_with_impl iter condition sub_new new_vars para2_hpat new_hpats) with + | None -> None + | Some (sub_res, p_leftover) when condition p_leftover sub_res -> + let not_in_para2_exist_vars id = + not (list_exists (fun id' -> Ident.equal id id') para2_exist_vars) in + let sub_res' = Sil.sub_filter not_in_para2_exist_vars sub_res + in Some (sub_res', p_leftover) + | Some _ -> None + in begin match ((Prop.prop_iter_find iter filter), hpats) with + | (None, _) when not hpat.flag -> None + | (None, _) when Sil.lseg_kind_equal k2 Sil.Lseg_NE -> do_para_dllseg () + | (None, _) -> execute_with_backtracking [do_emp_dllseg; do_para_dllseg] + | (Some iter_cur, []) -> do_empty_hpats iter_cur () + | (Some iter_cur, _) -> execute_with_backtracking [do_nonempty_hpats iter_cur; do_next iter_cur] + end + +and prop_match_with_impl_sub p condition sub vars hpat hpats = + (* + L.out "@[.... prop_match_with_impl_sub ....@."; + L.out "@[<4> sub: %a@\n@." pp_sub sub; + L.out "@[<4> PROP: %a@\n@." pp_prop p; + L.out "@[<4> hpat: %a@\n@." pp_hpat hpat; + L.out "@[<4> hpred_rest: %a@\n@." pp_hpat_list hpats; + *) + match Prop.prop_iter_create p with + | None -> + instantiate_to_emp p condition sub vars (hpat:: hpats) + | Some iter -> + iter_match_with_impl iter condition sub vars hpat hpats + +and hpara_common_match_with_impl impl_ok ids1 sigma1 eids2 ids2 sigma2 = + try + let sub_ids = + let ren_ids = list_combine ids2 ids1 in + let f (id2, id1) = (id2, Sil.Var id1) in + list_map f ren_ids in + let (sub_eids, eids_fresh) = + let f id = (id, Ident.create_fresh Ident.kprimed) in + let ren_eids = list_map f eids2 in + let eids_fresh = list_map snd ren_eids in + let sub_eids = list_map (fun (id2, id1) -> (id2, Sil.Var id1)) ren_eids in + (sub_eids, eids_fresh) in + let sub = Sil.sub_of_list (sub_ids @ sub_eids) in + match sigma2 with + | [] -> if sigma1 == [] then true else false + | hpred2 :: sigma2 -> + let (hpat2, hpats2) = + let (hpred2_ren, sigma2_ren) = (Sil.hpred_sub sub hpred2, Prop.sigma_sub sub sigma2) in + let allow_impl hpred = { hpred = hpred; flag = impl_ok } in + (allow_impl hpred2_ren, list_map allow_impl sigma2_ren) in + let condition _ _ = true in + let p1 = Prop.normalize (Prop.from_sigma sigma1) in + begin + match (prop_match_with_impl_sub p1 condition Sil.sub_empty eids_fresh hpat2 hpats2) with + | None -> false + | Some (_, p1') when Prop.prop_is_emp p1' -> true + | _ -> false + end + with + | Invalid_argument _ -> false + +and hpara_match_with_impl impl_ok para1 para2 : bool = + (* + L.out "@[.... hpara_match_with_impl_sub ....@."; + L.out "@[<4> HPARA1: %a@\n@." pp_hpara para1; + L.out "@[<4> HPARA2: %a@\n@." pp_hpara para2; + *) + let ids1 = para1.Sil.root :: para1.Sil.next :: para1.Sil.svars in + let ids2 = para2.Sil.root :: para2.Sil.next :: para2.Sil.svars in + let eids2 = para2.Sil.evars + in hpara_common_match_with_impl impl_ok ids1 para1.Sil.body eids2 ids2 para2.Sil.body + +and hpara_dll_match_with_impl impl_ok para1 para2 : bool = + (* + L.out "@[.... hpara_dll_match_with_impl_sub ....@."; + L.out "@[<4> HPARA1: %a@\n@." pp_hpara_dll para1; + L.out "@[<4> HPARA2: %a@\n@." pp_hpara_dll para2; + *) + let ids1 = para1.Sil.cell :: para1.Sil.blink :: para1.Sil.flink :: para1.Sil.svars_dll in + let ids2 = para2.Sil.cell :: para2.Sil.blink :: para2.Sil.flink :: para2.Sil.svars_dll in + let eids2 = para2.Sil.evars_dll in + hpara_common_match_with_impl impl_ok ids1 para1.Sil.body_dll eids2 ids2 para2.Sil.body_dll + + +(** [prop_match_with_impl p condition vars hpat hpats] +returns [(subst, p_leftover)] such that +1) [dom(subst) = vars] +2) [p |- (hpat.hpred * hpats.hpred)[subst] * p_leftover]. +Using the flag [field], we can control the strength of |-. *) +let prop_match_with_impl p condition vars hpat hpats = + prop_match_with_impl_sub p condition Sil.sub_empty vars hpat hpats + +let sigma_remove_hpred eq sigma e = + let filter = function + | Sil.Hpointsto (root, _, _) + | Sil.Hlseg (_, _, root, _, _) + | Sil.Hdllseg (_, _, root, _, _, _, _) -> eq root e in + let sigma_e, sigma_no_e = list_partition filter sigma in + match sigma_e with + | [] -> (None, sigma) + | [hpred_e] -> (Some hpred_e, sigma_no_e) + | _ -> assert false + +(** {2 Routines used when finding disjoint isomorphic sigmas from a single sigma} *) + +type iso_mode = Exact | LFieldForget | RFieldForget + + +let rec generate_todos_from_strexp mode todos sexp1 sexp2 = + match sexp1, sexp2 with + | Sil.Eexp (exp1, inst1), Sil.Eexp (exp2, inst2) -> + let new_todos = (exp1, exp2) :: todos in + Some new_todos + | Sil.Eexp _, _ -> + None + | Sil.Estruct (fel1, _), Sil.Estruct (fel2, _) -> (* assume sorted w.r.t. fields *) + if (list_length fel1 <> list_length fel2) && mode == Exact + then None + else generate_todos_from_fel mode todos fel1 fel2 + | Sil.Estruct _, _ -> + None + | Sil.Earray (size1, iel1, _), Sil.Earray (size2, iel2, _) -> + if (not (Sil.exp_equal size1 size2) || list_length iel1 <> list_length iel2) + then None + else generate_todos_from_iel mode todos iel1 iel2 + | Sil.Earray _, _ -> + None + +and generate_todos_from_fel mode todos fel1 fel2 = + match fel1, fel2 with + | [], [] -> + Some todos + | [], _ -> + if mode == RFieldForget then Some todos else None + | _, [] -> + if mode == LFieldForget then Some todos else None + | (fld1, strexp1) :: fel1', (fld2, strexp2) :: fel2' -> + let n = Sil.fld_compare fld1 fld2 in + if (n = 0) then + begin + match generate_todos_from_strexp mode todos strexp1 strexp2 with + | None -> None + | Some todos' -> generate_todos_from_fel mode todos' fel1' fel2' + end + else if (n < 0 && mode == LFieldForget) then + generate_todos_from_fel mode todos fel1' fel2 + else if (n > 0 && mode == RFieldForget) then + generate_todos_from_fel mode todos fel1 fel2' + else + None + +and generate_todos_from_iel mode todos iel1 iel2 = + match iel1, iel2 with + | [],[] -> + Some todos + | (idx1, strexp1) :: iel1', (idx2, strexp2) :: iel2' -> + begin + match generate_todos_from_strexp mode todos strexp1 strexp2 with + | None -> None + | Some todos' -> + let new_todos = (idx1, idx2) :: todos' in + generate_todos_from_iel mode new_todos iel1' iel2' + end + | _ -> + None + +(** add (e1,e2) at the front of corres, if necessary. *) +let corres_extend_front e1 e2 corres = + let filter (e1', e2') = (Sil.exp_equal e1 e1') || (Sil.exp_equal e2 e2') in + let checker e1' e2' = (Sil.exp_equal e1 e1') && (Sil.exp_equal e2 e2') + in match (list_filter filter corres) with + | [] -> Some ((e1, e2) :: corres) + | [(e1', e2')] when checker e1' e2' -> Some corres + | _ -> None + +let corres_extensible corres e1 e2 = + let predicate (e1', e2') = (Sil.exp_equal e1 e1') || (Sil.exp_equal e2 e2') + in not (list_exists predicate corres) && not (Sil.exp_equal e1 e2) + +let corres_related corres e1 e2 = + let filter (e1', e2') = (Sil.exp_equal e1 e1') || (Sil.exp_equal e2 e2') in + let checker e1' e2' = (Sil.exp_equal e1 e1') && (Sil.exp_equal e2 e2') in + match (list_filter filter corres) with + | [] -> Sil.exp_equal e1 e2 + | [(e1', e2')] when checker e1' e2' -> true + | _ -> false + +(* TO DO. Perhaps OK. Need to implemenet a better isomorphism check later.*) +let hpara_iso para1 para2 = + hpara_match_with_impl false para1 para2 && hpara_match_with_impl false para2 para1 + +let hpara_dll_iso para1 para2 = + hpara_dll_match_with_impl false para1 para2 && hpara_dll_match_with_impl false para2 para1 + + +(** [generic_find_partial_iso] finds isomorphic subsigmas of [sigma_todo]. +The function [update] is used to get rid of hpred pairs from [sigma_todo]. +[sigma_corres] records the isormophic copies discovered so far. The first +parameter determines how much flexibility we will allow during this partial +isomorphism finding. *) +let rec generic_find_partial_iso mode update corres sigma_corres todos sigma_todo = + match todos with + | [] -> + let sigma1, sigma2 = sigma_corres in + Some (list_rev corres, list_rev sigma1, list_rev sigma2, sigma_todo) + | (e1, e2) :: todos' when corres_related corres e1 e2 -> + begin + match corres_extend_front e1 e2 corres with + | None -> assert false + | Some new_corres -> generic_find_partial_iso mode update new_corres sigma_corres todos' sigma_todo + end + | (e1, e2) :: todos' when corres_extensible corres e1 e2 -> + let hpredo1, hpredo2, new_sigma_todo = update e1 e2 sigma_todo in + begin + match hpredo1, hpredo2 with + | None, None -> + begin + match corres_extend_front e1 e2 corres with + | None -> assert false + | Some new_corres -> generic_find_partial_iso mode update new_corres sigma_corres todos' sigma_todo + end + | None, _ | _, None -> + None + | Some (Sil.Hpointsto (_, _, te1)), Some (Sil.Hpointsto (_, _, te2)) + when not (Sil.exp_equal te1 te2) -> + None + | Some (Sil.Hpointsto (_, se1, _) as hpred1), + Some (Sil.Hpointsto (_, se2, _) as hpred2) -> + begin + match generate_todos_from_strexp mode [] se1 se2 with + | None -> None + | Some todos'' -> + let new_corres = match corres_extend_front e1 e2 corres with + | None -> assert false + | Some new_corres -> new_corres in + let new_sigma_corres = + let sigma1, sigma2 = sigma_corres in + let new_sigma1 = hpred1 :: sigma1 in + let new_sigma2 = hpred2 :: sigma2 in + (new_sigma1, new_sigma2) in + let new_todos = todos'' @ todos' in + generic_find_partial_iso mode update new_corres new_sigma_corres new_todos new_sigma_todo + + end + | Some (Sil.Hlseg (k1, para1, root1, next1, shared1) as hpred1), + Some (Sil.Hlseg (k2, para2, root2, next2, shared2) as hpred2) -> + if k1 <> k2 || not (hpara_iso para1 para2) then None + else + (try + let new_corres = match corres_extend_front e1 e2 corres with + | None -> assert false + | Some new_corres -> new_corres in + let new_sigma_corres = + let sigma1, sigma2 = sigma_corres in + let new_sigma1 = hpred1 :: sigma1 in + let new_sigma2 = hpred2 :: sigma2 in + (new_sigma1, new_sigma2) in + let new_todos = + let shared12 = list_combine shared1 shared2 in + (root1, root2) :: (next1, next2) :: shared12 @ todos' in + generic_find_partial_iso mode update new_corres new_sigma_corres new_todos new_sigma_todo + with Invalid_argument _ -> None) + | Some (Sil.Hdllseg(k1, para1, iF1, oB1, oF1, iB1, shared1) as hpred1), + Some (Sil.Hdllseg(k2, para2, iF2, oB2, oF2, iB2, shared2) as hpred2) -> + if k1 <> k2 || not (hpara_dll_iso para1 para2) then None + else + (try + let new_corres = match corres_extend_front e1 e2 corres with + | None -> assert false + | Some new_corres -> new_corres in + let new_sigma_corres = + let sigma1, sigma2 = sigma_corres in + let new_sigma1 = hpred1 :: sigma1 in + let new_sigma2 = hpred2 :: sigma2 in + (new_sigma1, new_sigma2) in + let new_todos = + let shared12 = list_combine shared1 shared2 in + (iF1, iF2):: (oB1, oB2):: (oF1, oF2):: (iB1, iB2):: shared12@todos' in + generic_find_partial_iso mode update new_corres new_sigma_corres new_todos new_sigma_todo + with Invalid_argument _ -> None) + | _ -> None + end + | _ -> None + +(** [find_partial_iso] finds disjoint isomorphic sub-sigmas inside a given sigma. +The function returns a partial iso and three sigmas. The first sigma is the first +copy of the two isomorphic sigmas, so it uses expressions in the domain of +the returned isomorphism. The second is the second copy of the two isomorphic sigmas, +and it uses expressions in the range of the isomorphism. The third is the unused +part of the input sigma. *) +let find_partial_iso eq corres todos sigma = + let update e1 e2 sigma0 = + let (hpredo1, sigma0_no_e1) = sigma_remove_hpred eq sigma0 e1 in + let (hpredo2, sigma0_no_e12) = sigma_remove_hpred eq sigma0_no_e1 e2 in + (hpredo1, hpredo2, sigma0_no_e12) in + let init_sigma_corres = ([], []) in + let init_sigma_todo = sigma in + generic_find_partial_iso Exact update corres init_sigma_corres todos init_sigma_todo + +(** [find_partial_iso_from_two_sigmas] finds isomorphic sub-sigmas inside two +given sigmas. The function returns a partial iso and four sigmas. The first +sigma is the first copy of the two isomorphic sigmas, so it uses expressions in the domain of +the returned isomorphism. The second is the second copy of the two isomorphic sigmas, +and it uses expressions in the range of the isomorphism. The third and fourth +are the unused parts of the two input sigmas. *) +let find_partial_iso_from_two_sigmas mode eq corres todos sigma1 sigma2 = + let update e1 e2 sigma_todo = + let sigma_todo1, sigma_todo2 = sigma_todo in + let hpredo1, sigma_todo1_no_e1 = sigma_remove_hpred eq sigma_todo1 e1 in + let hpredo2, sigma_todo2_no_e2 = sigma_remove_hpred eq sigma_todo2 e2 in + let new_sigma_todo = sigma_todo1_no_e1, sigma_todo2_no_e2 in + (hpredo1, hpredo2, new_sigma_todo) in + let init_sigma_corres = ([], []) in + let init_sigma_todo = (sigma1, sigma2) in + generic_find_partial_iso mode update corres init_sigma_corres todos init_sigma_todo + +(** Lift the kind of list segment predicates to PE *) +let hpred_lift_to_pe hpred = + match hpred with + | Sil.Hpointsto _ -> hpred + | Sil.Hlseg (_, para, root, next, shared) -> Sil.Hlseg (Sil.Lseg_PE, para, root, next, shared) + | Sil.Hdllseg(_, para, iF, oB, oF, iB, shared) -> Sil.Hdllseg (Sil.Lseg_PE, para, iF, oB, oF, iB, shared) + +(** Lift the kind of list segment predicates to PE in a given sigma *) +let sigma_lift_to_pe sigma = + list_map hpred_lift_to_pe sigma + +(** [generic_para_create] takes a correspondence, and a sigma +and a list of expressions for the first part of this +correspondence. Then, it creates a renaming of expressions +in the domain of the given correspondence, and applies this +renaming to the given sigma. The result is a tuple of the renaming, +the renamed sigma, ids for existentially quantified expressions, +ids for shared expressions, and shared expressions. *) +let generic_para_create corres sigma1 elist1 = + let corres_ids = + let not_same_consts = function + | Sil.Const c1, Sil.Const c2 -> not (Sil.const_equal c1 c2) + | _ -> true in + let new_corres' = list_filter not_same_consts corres in + let add_fresh_id pair = (pair, Ident.create_fresh Ident.kprimed) in + list_map add_fresh_id new_corres' in + let (es_shared, ids_shared, ids_exists) = + let not_in_elist1 ((e1, _), _) = not (list_exists (Sil.exp_equal e1) elist1) in + let corres_ids_no_elist1 = list_filter not_in_elist1 corres_ids in + let should_be_shared ((e1, e2), _) = Sil.exp_equal e1 e2 in + let shared, exists = list_partition should_be_shared corres_ids_no_elist1 in + let es_shared = list_map (fun ((e1, _), _) -> e1) shared in + (es_shared, list_map snd shared, list_map snd exists) in + let renaming = list_map (fun ((e1, _), id) -> (e1, id)) corres_ids in + let body = + let sigma1' = sigma_lift_to_pe sigma1 in + let renaming_exp = list_map (fun (e1, id) -> (e1, Sil.Var id)) renaming in + Prop.sigma_replace_exp renaming_exp sigma1' in + (renaming, body, ids_exists, ids_shared, es_shared) + +(** [hpara_create] takes a correspondence, and a sigma, a root +and a next for the first part of this correspondence. Then, it creates a +hpara and discovers a list of shared expressions that are +passed as arguments to hpara. Both of them are returned as a result. *) +let hpara_create corres sigma1 root1 next1 = + let renaming, body, ids_exists, ids_shared, es_shared = + generic_para_create corres sigma1 [root1; next1] in + let get_id1 e1 = + try + let is_equal_to_e1 (e1', _) = Sil.exp_equal e1 e1' in + let _, id = list_find is_equal_to_e1 renaming in + id + with Not_found -> assert false in + let id_root = get_id1 root1 in + let id_next = get_id1 next1 in + let hpara = + { Sil.root = id_root; + Sil.next = id_next; + Sil.svars = ids_shared; + Sil.evars = ids_exists; + Sil.body = body } in + (hpara, es_shared) + +(** [hpara_dll_create] takes a correspondence, and a sigma, a root, +a blink and a flink for the first part of this correspondence. Then, it creates a +hpara_dll and discovers a list of shared expressions that are +passed as arguments to hpara. Both of them are returned as a result. *) +let hpara_dll_create corres sigma1 root1 blink1 flink1 = + let renaming, body, ids_exists, ids_shared, es_shared = + generic_para_create corres sigma1 [root1; blink1; flink1] in + let get_id1 e1 = + try + let is_equal_to_e1 (e1', _) = Sil.exp_equal e1 e1' in + let _, id = list_find is_equal_to_e1 renaming in + id + with Not_found -> assert false in + let id_root = get_id1 root1 in + let id_blink = get_id1 blink1 in + let id_flink = get_id1 flink1 in + let hpara_dll = + { Sil.cell = id_root; + Sil.blink = id_blink; + Sil.flink = id_flink; + Sil.svars_dll = ids_shared; + Sil.evars_dll = ids_exists; + Sil.body_dll = body } in + (hpara_dll, es_shared) diff --git a/infer/src/backend/match.mli b/infer/src/backend/match.mli new file mode 100644 index 000000000..7e58e7d21 --- /dev/null +++ b/infer/src/backend/match.mli @@ -0,0 +1,98 @@ +(* +* Copyright (c) 2009-2013 Monoidics ltd. +* Copyright (c) 2013- Facebook. +* All rights reserved. +*) + +(** Implementation of "Smart" Pattern Matching for higher order singly-linked list predicate. + +Used for detecting on a given program if some data scructures are matching some predefined higher-order list predicates. When it is the case, these predicates can be used as possible candidates for abstracting the data-structures. +See {{: http://dx.doi.org/10.1007/978-3-540-73368-3_22 } CAV 2007 } for the therory involved. + *) + +open Utils + +(* TODO: missing documentation *) +val hpara_match_with_impl : bool -> Sil.hpara -> Sil.hpara -> bool +val hpara_dll_match_with_impl : bool -> Sil.hpara_dll -> Sil.hpara_dll -> bool + +(** Type for a hpred pattern. [flag=false] means that the implication +between hpreds is not considered, and [flag = true] means that it is +considered during pattern matching. *) +type hpred_pat = { hpred : Sil.hpred; flag : bool } + +val pp_hpat : printenv -> Format.formatter -> hpred_pat -> unit + +val pp_hpat_list : printenv -> Format.formatter -> hpred_pat list -> unit + +type sidecondition = Prop.normal Prop.t -> Sil.subst -> bool + +(** [prop_match_with_impl p condition vars hpat hpats] +returns [(subst, p_leftover)] such that +1) [dom(subst) = vars] +2) [p |- (hpat.hpred * hpats.hpred)[subst] * p_leftover]. +Using the flag [field], we can control the strength of |-. *) +val prop_match_with_impl : Prop.normal Prop.t -> sidecondition -> Ident.t list -> hpred_pat -> hpred_pat list -> (Sil.subst * Prop.normal Prop.t) option + +(** [find_partial_iso] finds disjoint isomorphic sub-sigmas inside a given sigma. +The first argument is an equality checker. +The function returns a partial iso and three sigmas. The first sigma is the first +copy of the two isomorphic sigmas, so it uses expressions in the domain of +the returned isomorphism. The second is the second copy of the two isomorphic sigmas, +and it uses expressions in the range of the isomorphism. The third is the unused +part of the input sigma. *) +val find_partial_iso : +(Sil.exp -> Sil.exp -> bool) -> +(Sil.exp * Sil.exp) list -> +(Sil.exp * Sil.exp) list -> +Sil.hpred list -> +((Sil.exp * Sil.exp) list * Sil.hpred list * Sil.hpred list * Sil.hpred list) option + +(** This mode expresses the flexibility allowed during the isomorphism check *) +type iso_mode = Exact | LFieldForget | RFieldForget + +(** [find_partial_iso_from_two_sigmas] finds isomorphic sub-sigmas inside two +given sigmas. The second argument is an equality checker. +The function returns a partial iso and four sigmas. The first +sigma is the first copy of the two isomorphic sigmas, so it uses expressions in the domain of +the returned isomorphism. The second is the second copy of the two isomorphic sigmas, +and it uses expressions in the range of the isomorphism. The third and fourth +are the unused parts of the two input sigmas. *) +val find_partial_iso_from_two_sigmas : +iso_mode -> +(Sil.exp -> Sil.exp -> bool) -> +(Sil.exp * Sil.exp) list -> +(Sil.exp * Sil.exp) list -> +Sil.hpred list -> +Sil.hpred list -> +((Sil.exp * Sil.exp) list * Sil.hpred list * Sil.hpred list * (Sil.hpred list * Sil.hpred list)) option + +(** [hpara_iso] soundly checks whether two hparas are isomorphic. *) +val hpara_iso : Sil.hpara -> Sil.hpara -> bool + +(** [hpara_dll_iso] soundly checks whether two hpara_dlls are isomorphic. *) +val hpara_dll_iso : Sil.hpara_dll -> Sil.hpara_dll -> bool + + +(** [hpara_create] takes a correspondence, and a sigma, a root +and a next for the first part of this correspondence. Then, it creates a +hpara and discovers a list of shared expressions that are +passed as arguments to hpara. Both of them are returned as a result. *) +val hpara_create : +(Sil.exp * Sil.exp) list -> +Sil.hpred list -> +Sil.exp -> +Sil.exp -> +Sil.hpara * Sil.exp list + +(** [hpara_dll_create] takes a correspondence, and a sigma, a root, +a blink and a flink for the first part of this correspondence. Then, +it creates a hpara_dll and discovers a list of shared expressions that are +passed as arguments to hpara. Both of them are returned as a result. *) +val hpara_dll_create : +(Sil.exp * Sil.exp) list -> +Sil.hpred list -> +Sil.exp -> +Sil.exp -> +Sil.exp -> +Sil.hpara_dll * Sil.exp list diff --git a/infer/src/backend/mleak_buckets.ml b/infer/src/backend/mleak_buckets.ml new file mode 100644 index 000000000..0f5784e8e --- /dev/null +++ b/infer/src/backend/mleak_buckets.ml @@ -0,0 +1,97 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) +(** This module handles buckets of memory leaks in Objective-C *) + +open Utils + +let objc_arc_flag = "objc_arc" + +let bucket_delimiter = "," + +type mleak_bucket = + | MLeak_cf + | MLeak_arc + | MLeak_no_arc + +let objc_ml_buckets = ref [] + +let bucket_from_string bucket_s = + match bucket_s with + | "cf" -> MLeak_cf + | "arc" -> MLeak_arc + | "narc" -> MLeak_no_arc + | _ -> assert false + +let bucket_to_string bucket = + match bucket with + | MLeak_cf -> "Core Foundation" + | MLeak_arc -> "Arc" + | MLeak_no_arc -> "No arc" + +let bucket_to_message bucket = + match bucket with + | MLeak_cf -> "[CF]" + | MLeak_arc -> "[ARC]" + | MLeak_no_arc -> "[NO ARC]" + +let mleak_bucket_compare b1 b2 = + match b1, b2 with + | MLeak_cf, MLeak_cf -> 0 + | MLeak_cf, _ -> -1 + | _, MLeak_cf -> 1 + | MLeak_arc, MLeak_arc -> 0 + | MLeak_arc, _ -> -1 + | _, MLeak_arc -> 1 + | MLeak_no_arc, MLeak_no_arc -> 0 + +let mleak_bucket_eq b1 b2 = + mleak_bucket_compare b1 b2 = 0 + +let init_buckets objc_ml_buckets_arg = + let buckets = + Str.split (Str.regexp bucket_delimiter) objc_ml_buckets_arg in + let buckets = + match buckets with + | ["all"] -> [] + | _ -> list_map bucket_from_string buckets in + objc_ml_buckets := buckets + +let contains_cf ml_buckets = + list_mem mleak_bucket_eq MLeak_cf ml_buckets + +let contains_arc ml_buckets = + list_mem mleak_bucket_eq MLeak_arc ml_buckets + +let contains_narc ml_buckets = + list_mem mleak_bucket_eq MLeak_no_arc ml_buckets + +let should_raise_leak_cf typ = + if contains_cf !objc_ml_buckets then + Objc_models.is_core_lib_type typ + else false + +let should_raise_leak_arc () = + if contains_arc !objc_ml_buckets then + !Config.arc_mode + else false + +let should_raise_leak_no_arc () = + if contains_narc !objc_ml_buckets then + not (!Config.arc_mode) + else false + +(* Returns whether a memory leak should be raised. If objc_ml_buckets is not there, *) +(* then raise all memory leaks. *) +(* If cf is passed, then check leaks from Core Foundation. *) +(* If arc is passed, check leaks from code that compiles with arc*) +(* If no arc is passed check the leaks from code that compiles without arc *) +let should_raise_leak typ = + if list_length !objc_ml_buckets = 0 then Some "" + else + if should_raise_leak_cf typ then Some (bucket_to_message MLeak_cf) + else if should_raise_leak_arc () then Some (bucket_to_message MLeak_arc) + else if should_raise_leak_no_arc () then Some (bucket_to_message MLeak_no_arc) + else None diff --git a/infer/src/backend/mleak_buckets.mli b/infer/src/backend/mleak_buckets.mli new file mode 100644 index 000000000..082ce7e97 --- /dev/null +++ b/infer/src/backend/mleak_buckets.mli @@ -0,0 +1,18 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** This module handles buckets of memory leaks in Objective-C *) + +val objc_arc_flag : string + +val init_buckets : string -> unit + +(* Returns whether a memory leak should be raised. If objc_ml_buckets is not there, *) +(* then raise all memory leaks. *) +(* If cf is passed, then check leaks from Core Foundation. *) +(* If arc is passed, check leaks from code that compiles with arc*) +(* If no arc is passed check the leaks from code that compiles without arc *) +val should_raise_leak : Sil.typ -> string option + diff --git a/infer/src/backend/objc_models.ml b/infer/src/backend/objc_models.ml new file mode 100644 index 000000000..624606de8 --- /dev/null +++ b/infer/src/backend/objc_models.ml @@ -0,0 +1,251 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** This module handles C or Objective-C types for which there are special rules for memory management *) + +open Utils + +(** This module models special c struct types from the Apple's Core Foundation libraries +for which there are particular rules for memory management. *) + +module Core_foundation_model = +struct + + let core_foundation = [ + "__CFArray"; + "__CFAttributedString"; + "__CFBag"; + "__CFNull"; + "__CFAllocator"; + "__CFBinaryHeap"; + "__CFBitVector"; + "__CFBundle"; + "__CFCalendar"; + "__CFCharacterSet"; + "__CFDate"; + "__CFDateFormatter"; + "__CFDictionary"; + "__CFError"; + "__CFFileDescriptor"; + "__CFFileSecurity"; + "__CFLocale"; + "__CFMachPort"; + "__CFMessagePort"; + "__CFNotificationCenter"; + "__CFBoolean"; + "__CFNumber"; + "__CFNumberFormatter"; + "__CFPlugInInstance"; + "__CFReadStream"; + "__CFWriteStream"; + "__CFRunLoop"; + "__CFRunLoopSource"; + "__CFRunLoopObserver"; + "__CFRunLoopTimer"; + "__CFSet"; + "__CFStringTokenizer"; + "__CFSocket"; + "__CFReadStream"; + "__CFWriteStream"; + "__CFTimeZone"; + "__CFTree"; + "__CFURLEnumerator"; + "__CFUUID" + ] + + let cf_network = [ + "_CFHTTPAuthentication"; + "__CFHTTPMessage"; + "__CFHost"; + "__CFNetDiagnostic"; + "__CFNetService"; + "__CFNetServiceMonitor"; + "__CFNetServiceBrowser" + ] + + let core_media = [ + "OpaqueCMBlockBuffer"; + "opaqueCMBufferQueue"; + "opaqueCMBufferQueueTriggerToken"; + "opaqueCMFormatDescription"; + "OpaqueCMMemoryPool"; + "opaqueCMSampleBuffer"; + "opaqueCMSimpleQueue"; + "OpaqueCMClock"; + "OpaqueCMTimebase" + ] + + let core_text = [ + "__CTFont"; + "__CTFontCollection"; + "__CTFontDescriptor"; + "__CTFrame"; + "__CTFramesetter"; + "__CTGlyphInfo"; + "__CTLine"; + "__CTParagraphStyle"; + "__CTRubyAnnotation"; + "__CTRun"; + "__CTRunDelegate"; + "__CTTextTab"; + "__CTTypesetter" + ] + + let core_video = [ + "__CVBuffer"; + "__CVMetalTextureCache"; + "__CVOpenGLESTextureCache"; + "__CVPixelBufferPool" + ] + + let image_io = [ + "CGImageDestination"; + "CGImageMetadata"; + "CGImageMetadataTag"; + "CGImageSource" + ] + + let security = [ + "__SecCertificate"; + "__SecIdentity"; + "__SecKey"; + "__SecPolicy"; + "__SecAccessControl"; + "__SecRandom"; + "__SecCode"; + "__SecTrust"; + "__SecRequirement" + ] + + let system_configuration = [ + "__SCDynamicStore"; + "__SCNetworkInterface"; + "__SCBondStatus"; + "__SCNetworkProtocol"; + "__SCNetworkService"; + "__SCNetworkSet"; + "__SCNetworkConnection"; + "__SCNetworkReachability"; + "__SCPreferences" + ] + + let core_graphics_types = [ + "CGAffineTransform"; + "CGBase"; + "CGBitmapContext"; + "CGColor"; + "CGColorSpace"; + "CGContext"; + "CGDataConsumer"; + "CGDataProvider"; + "CGError"; + "CGFont"; + "CGFunction"; + "CGGeometry"; + "CGGradient"; + "CGImage"; + "CGLayer"; + "CGPath"; + "CGPattern"; + "CGPDFArray"; + "CGPDFContentStream"; + "CGPDFContext"; + "CGPDFDictionary"; + "CGPDFDocument"; + "CGPDFObject"; + "CGPDFOperatorTable"; + "CGPDFPage"; + "CGPDFScanner"; + "CGPDFStream"; + "CGPDFString"; + "CGShading" + ] + + let core_foundation_types = + core_foundation @ + cf_network @ + core_media @ + core_text @ + core_video @ + image_io @ + security @ + system_configuration + + let copy = "Copy" + + let create = "Create" + + let cf_retain = "CFRetain" + + let cf_release = "CFRelease" + + let upper_release = "Release" + + let ref = "Ref" + + let cf_type = "CFTypeRef" + + type core_lib = + | Core_foundation + | Core_graphics + + let core_lib_to_type_list lib = + match lib with + | Core_foundation -> core_foundation_types + | Core_graphics -> core_graphics_types + + let is_objc_memory_model_controlled o = + list_mem (string_equal) o core_foundation_types || + list_mem (string_equal) o core_graphics_types + + let rec is_core_lib lib typ = + match typ with + | Sil.Tptr (styp, _ ) -> + is_core_lib lib styp + | Sil.Tvar (Sil.TN_csu (_, name) ) + | Sil.Tstruct(_, _, _, (Some name), _, _, _) -> + let core_lib_types = core_lib_to_type_list lib in + list_mem (=) (Mangled.to_string name) core_lib_types + | _ -> false + + let is_core_foundation_type typ = + is_core_lib Core_foundation typ + + let is_core_graphics_type typ = + is_core_lib Core_graphics typ + + let is_core_lib_type typ = + is_core_foundation_type typ || + is_core_graphics_type typ + + let is_core_lib_create typ funct = + is_core_lib_type typ && + ((string_contains create funct) || + (string_contains copy funct )) + + let function_arg_is_cftype typ = + (string_contains cf_type typ) + + let function_arg_is_core_pgraphics typ = + let res = (string_contains cf_type typ) in + res + + let is_core_lib_retain typ funct = + function_arg_is_cftype typ && funct = cf_retain + + let is_core_lib_release typ funct = + function_arg_is_cftype typ && funct = cf_release + + let is_core_graphics_release typ funct = + try + let cg_typ = list_find + (fun lib -> (funct = (lib^upper_release))) core_graphics_types in + (string_contains (cg_typ^ref) typ) + with Not_found -> false + +end + +let is_core_lib_type typ = + Core_foundation_model.is_core_lib_type typ diff --git a/infer/src/backend/objc_models.mli b/infer/src/backend/objc_models.mli new file mode 100644 index 000000000..25559e9a4 --- /dev/null +++ b/infer/src/backend/objc_models.mli @@ -0,0 +1,30 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** This module models special c struct types from the Apple's Core Foundation libraries +for which there are particular rules for memory management. *) + +open Utils + +(** This module models special c struct types from the Apple's Core Foundation libraries +for which there are particular rules for memory management. *) + +module Core_foundation_model : +sig + + val is_core_lib_release : string -> string -> bool + + val is_core_lib_create : Sil.typ -> string -> bool + + val is_core_lib_retain : string -> string -> bool + + val is_core_graphics_release : string -> string -> bool + + val is_objc_memory_model_controlled : string -> bool + + +end + +val is_core_lib_type : Sil.typ -> bool diff --git a/infer/src/backend/paths.ml b/infer/src/backend/paths.ml new file mode 100644 index 000000000..de2c34379 --- /dev/null +++ b/infer/src/backend/paths.ml @@ -0,0 +1,660 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Execution Paths *) + +module L = Logging +module F = Format +open Utils + +(* =============== START of the Path module ===============*) + +module Path : sig +(** type for paths *) + type t + + type session = int + + (** add a call with its sub-path, the boolean indicates whether the subtrace for the procedure should be included *) + val add_call : bool -> t -> Procname.t -> t -> t + + (** check whether a path contains another path *) + val contains : t -> t -> bool + + (** check wether the path contains the given position *) + val contains_position : t -> Sil.path_pos -> bool + + (** Create the location trace of the path, up to the path position if specified *) + val create_loc_trace : t -> Sil.path_pos option -> Errlog.loc_trace + + (** return the current node of the path *) + val curr_node : t -> Cfg.node + + (** dump a path *) + val d : t -> unit + + (** dump statistics of the path *) + val d_stats : t -> unit + + (** equality for paths *) + val equal : t -> t -> bool + + (** extend a path with a new node reached from the given session, with an optional string for exceptions *) + val extend : Cfg.node -> Mangled.t option -> session -> t -> t + + (** extend a path with a new node reached from the given session, with an optional string for exceptions *) + val add_description : t -> string -> t + + val get_description : t -> string option + + (** iterate over each node in the path, excluding calls, once *) + val iter_all_nodes_nocalls : (Cfg.node -> unit) -> t -> unit + + (** iterate over the longest sequence belonging to the path, restricting to those containing the given position if given. + Do not iterate past the given position. + [f level path session exn_opt] is passed the current nesting [level] and [path] and previous [session] *) + val iter_longest_sequence : (int -> t -> int -> Mangled.t option -> unit) -> Sil.path_pos option -> t -> unit + + (** join two paths *) + val join : t -> t -> t + + (** pretty print a path *) + val pp : Format.formatter -> t -> unit + + (** pretty print statistics of the path *) + val pp_stats : Format.formatter -> t -> unit + + (** create a new path with given start node *) + val start : Cfg.node -> t +end = struct + type session = int + type stats = + { mutable max_length : int; (* length of the longest linear sequence *) + mutable linear_num : float; (* number of linear sequences described by the path *) } + + type path = + (* INVARIANT: stats are always set to dummy_stats unless we are in the middle of a traversal *) + (* in particular: a new traversal cannot be initiated during an existing traversal *) + | Pstart of Cfg.node * stats (** start node *) + | Pnode of Cfg.node * Mangled.t option * session * path * stats * string option (** we got to [node] from last [session] perhaps propagating exception [exn_opt], and continue with [path]. *) + | Pjoin of path * path * stats (** join of two paths *) + | Pcall of path * Procname.t * path * stats (** add a sub-path originating from a call *) + + type t = path + + let get_dummy_stats () = + { max_length = - 1; + linear_num = - 1.0 } + + let get_description path = + match path with + | Pnode (node, exn_opt, session, path, stats, descr_opt) -> + descr_opt + | _ -> None + + let add_description path description = + let add_descr descr_option description = + match descr_option with + | Some descr -> descr^" "^description + | None -> description in + match path with + | Pnode (node, exn_opt, session, path, stats, descr_opt) -> + let description = add_descr descr_opt description in + Pnode (node, exn_opt, session, path, stats, Some description) + | _ -> path + + let set_dummy_stats stats = + stats.max_length <- - 1; + stats.linear_num <- - 1.0 + + let rec curr_node = function + | Pstart (node, _) -> node + | Pnode (node, _, _, _, _, _) -> node + | Pcall(p1, _, _, _) -> curr_node p1 + | Pjoin _ -> assert false + + let exname_opt_compare eo1 eo2 = match eo1, eo2 with + | None, None -> 0 + | None, _ -> -1 + | _, None -> 1 + | Some n1, Some n2 -> Mangled.compare n1 n2 + + let rec compare p1 p2 : int = + if p1 == p2 then 0 else match p1, p2 with + | Pstart (n1, _), Pstart (n2, _) -> + Cfg.Node.compare n1 n2 + | Pstart _, _ -> - 1 + | _, Pstart _ -> 1 + | Pnode (n1, eo1, s1, p1, _, _), Pnode (n2, eo2, s2, p2, _, _) -> + let n = Cfg.Node.compare n1 n2 in + if n <> 0 then n else let n = exname_opt_compare eo1 eo2 in + if n <> 0 then n else let n = int_compare s1 s2 in + if n <> 0 then n else compare p1 p2 + | Pnode _, _ -> - 1 + | _, Pnode _ -> 1 + | Pjoin (p1, q1, _), Pjoin (p2, q2, _) -> + let n = compare p1 p2 in + if n <> 0 then n else compare q1 q2 + | Pjoin _, _ -> -1 + | _, Pjoin _ -> 1 + | Pcall(p1, _, sub1, _), Pcall(p2, _, sub2, _) -> + let n = compare p1 p2 in + if n <> 0 then n else compare sub1 sub2 + + let equal p1 p2 = + compare p1 p2 = 0 + + let start node = Pstart (node, get_dummy_stats ()) + + let extend (node: Cfg.node) exn_opt session path = + Pnode (node, exn_opt, session, path, get_dummy_stats (), None) + + let join p1 p2 = + Pjoin (p1, p2, get_dummy_stats ()) + + let add_call include_subtrace p pname p_sub = + if include_subtrace then Pcall(p, pname, p_sub, get_dummy_stats ()) + else p + + module Invariant = (** functions in this module either do not assume, or do not re-establish, the invariant on dummy stats *) + struct + (** check whether a stats is the dummy stats *) + let stats_is_dummy stats = + stats.max_length == - 1 + + (** return the stats of the path *) + (** assumes that the stats are computed *) + let get_stats = function + | Pstart (_, stats) -> stats + | Pnode (_, _, _, _, stats, _) -> stats + | Pjoin (_, _, stats) -> stats + | Pcall (_, _, _, stats) -> stats + + (** restore the invariant that all the stats are dummy, so the path is ready for another traversal *) + (** assumes that the stats are computed beforehand, and ensures that the invariant holds afterwards *) + let rec reset_stats = function + | Pstart (node, stats) -> + if not (stats_is_dummy stats) then set_dummy_stats stats + | Pnode (node, exn_opt, session, path, stats, _) -> + if not (stats_is_dummy stats) then + begin + reset_stats path; + set_dummy_stats stats + end + | Pjoin (path1, path2, stats) -> + if not (stats_is_dummy stats) then + begin + reset_stats path1; + reset_stats path2; + set_dummy_stats stats + end + | Pcall (path1, pname, path2, stats) -> + if not (stats_is_dummy stats) then + begin + reset_stats path1; + reset_stats path2; + set_dummy_stats stats + end + + (** Iterate [f] over the path and compute the stats, assuming the invariant: all the stats are dummy. *) + (** Function [f] (typically with side-effects) is applied once to every node, and max_length in the stats + is the length of a longest sequence of nodes in the path where [f] returned [true] on at least one node. + max_length is 0 if the path was visited but no node satisfying [f] was found. *) + (** Assumes that the invariant holds beforehand, and ensures that all the stats are computed afterwards. *) + (** Since this breaks the invariant, it must be followed by reset_stats. *) + let rec compute_stats do_calls (f : Cfg.Node.t -> bool) = + let nodes_found stats = stats.max_length > 0 in + function + | Pstart (node, stats) -> + if stats_is_dummy stats then + begin + let found = f node in + stats.max_length <- if found then 1 else 0; + stats.linear_num <- 1.0; + end + | Pnode (node, exn_opt, session, path, stats, _) -> + if stats_is_dummy stats then + begin + compute_stats do_calls f path; + let stats1 = get_stats path in + let found = f node || nodes_found stats1 (* the order is important as f has side-effects *) in + stats.max_length <- if found then 1 + stats1.max_length else 0; + stats.linear_num <- stats1.linear_num; + end + | Pjoin (path1, path2, stats) -> + if stats_is_dummy stats then + begin + compute_stats do_calls f path1; + compute_stats do_calls f path2; + let stats1, stats2 = get_stats path1, get_stats path2 in + stats.max_length <- max stats1.max_length stats2.max_length; + stats.linear_num <- stats1.linear_num +. stats2.linear_num + end + | Pcall (path1, pname, path2, stats) -> + if stats_is_dummy stats then + begin + let stats2 = match do_calls with + | true -> + compute_stats do_calls f path2; + get_stats path2 + | false -> + { max_length = 0; + linear_num = 0.0 } in + let stats1 = + let f' = + if nodes_found stats2 + then fun _ -> true (* already found in call, no need to search before the call *) + else f in + compute_stats do_calls f' path1; + get_stats path1 in + stats.max_length <- stats1.max_length + stats2.max_length; + stats.linear_num <- stats1.linear_num; + end + end (* End of module Invariant *) + + (** iterate over each node in the path, excluding calls, once *) + let iter_all_nodes_nocalls f path = + Invariant.compute_stats false (fun node -> f node; true) path; + Invariant.reset_stats path + + let get_path_pos node = + let pn = Cfg.Procdesc.get_proc_name (Cfg.Node.get_proc_desc node) in + let n_id = Cfg.Node.get_id node in + (pn, n_id) + + let contains_position path pos = + let found = ref false in + let f node = + if Sil.path_pos_equal (get_path_pos node) pos then found := true; + true in + Invariant.compute_stats true f path; + Invariant.reset_stats path; + !found + + (** iterate over the longest sequence belonging to the path, restricting to those where [filter] holds of some element. + if a node is reached via an exception, pass the exception information to [f] on the previous node *) + let iter_longest_sequence_filter (f : int -> t -> int -> Mangled.t option -> unit) (filter: Cfg.Node.t -> bool) (path: t) : unit = + let rec doit level session path prev_exn_opt = match path with + | Pstart _ -> f level path session prev_exn_opt + | Pnode (node, exn_opt, session', p, _, _) -> + let next_exn_opt = if prev_exn_opt <> None then None else exn_opt in (* no two consecutive exceptions *) + doit level session' p next_exn_opt; + f level path session prev_exn_opt + | Pjoin (p1, p2, _) -> + if (Invariant.get_stats p1).max_length >= (Invariant.get_stats p2).max_length then doit level session p1 prev_exn_opt else doit level session p2 prev_exn_opt + | Pcall (p1, _, p2, _) -> + let next_exn_opt = None in (* exn must already be inside the call *) + doit level session p1 next_exn_opt; + doit (level +1) session p2 next_exn_opt in + Invariant.compute_stats true filter path; + doit 0 0 path None; + Invariant.reset_stats path + + (** iterate over the longest sequence belonging to the path, restricting to those containing the given position if given. + Do not iterate past the last occurrence of the given position. + [f level path session exn_opt] is passed the current nesting [level] and [path] and previous [session] and possible exception [exn_opt] *) + let iter_longest_sequence (f : int -> t -> int -> Mangled.t option -> unit) (pos_opt : Sil.path_pos option) (path: t) : unit = + let filter node = match pos_opt with + | None -> true + | Some pos -> Sil.path_pos_equal (get_path_pos node) pos in + let path_pos_at_path p = + try + let node = curr_node p in + pos_opt <> None && filter node + with exn when exn_not_timeout exn -> false in + let position_seen = ref false in + let inverse_sequence = + let log = ref [] in + let g level p session exn_opt = + if path_pos_at_path p then position_seen := true; + log := (level, p, session, exn_opt) :: !log in + iter_longest_sequence_filter g filter path; + !log in + let sequence_up_to_last_seen = + if !position_seen then + let rec remove_until_seen = function + | ((level, p, session, exn_opt) as x):: l -> + if path_pos_at_path p then list_rev (x :: l) + else remove_until_seen l + | [] -> [] in + remove_until_seen inverse_sequence + else list_rev inverse_sequence in + list_iter (fun (level, p, session, exn_opt) -> f level p session exn_opt) sequence_up_to_last_seen + + module NodeMap = Map.Make (Cfg.Node) + + (** return the node visited most, and number of visits, in the longest linear sequence *) + let repetitions path = + let map = ref NodeMap.empty in + let add_node node = + try + let n = NodeMap.find node !map in + map := NodeMap.add node (n + 1) !map + with Not_found -> + map := NodeMap.add node 1 !map in + iter_longest_sequence (fun level p s exn_opt -> add_node (curr_node p)) None path; + let max_rep_node = ref (Cfg.Node.dummy ()) in + let max_rep_num = ref 0 in + NodeMap.iter (fun node num -> if num > !max_rep_num then (max_rep_node := node; max_rep_num := num)) !map; + (!max_rep_node, !max_rep_num) + + let stats_string path = + Invariant.compute_stats true (fun _ -> true) path; + let node, repetitions = repetitions path in + let str = + "linear paths: " ^ string_of_float (Invariant.get_stats path).linear_num ^ + " max length: " ^ string_of_int (Invariant.get_stats path).max_length ^ + " has repetitions: " ^ string_of_int repetitions ^ + " of node " ^ (string_of_int (Cfg.Node.get_id node)) in + Invariant.reset_stats path; + str + + let pp_stats fmt path = + F.fprintf fmt "%s" (stats_string path) + + let d_stats path = + L.d_str (stats_string path) + + module PathMap = Map.Make (struct + type t = path + let compare = compare + end) + + let pp fmt path = + let delayed_num = ref 0 in + let delayed = ref PathMap.empty in + let add_path p = + try ignore (PathMap.find p !delayed) with Not_found -> + incr delayed_num; + delayed := PathMap.add p !delayed_num !delayed in + let path_seen p = (* path seen before *) + PathMap.mem p !delayed in + let rec add_delayed path = + if not (path_seen path) (* avoid exponential blowup *) + then match path with (* build a map from delayed paths to a unique number *) + | Pstart _ -> () + | Pnode (_, _, _, p, _, _) -> add_delayed p + | Pjoin (p1, p2, _) | Pcall(p1, _, p2, _) -> (* delay paths occurring in a join *) + add_delayed p1; + add_delayed p2; + add_path p1; + add_path p2 in + let rec doit n fmt path = + try + if n > 0 then raise Not_found; + let num = PathMap.find path !delayed in + F.fprintf fmt "P%d" num + with Not_found -> + match path with + | Pstart (node, _) -> F.fprintf fmt "n%a" Cfg.Node.pp node + | Pnode (node, exn_top, session, path, _, _) -> F.fprintf fmt "%a(s%d).n%a" (doit (n - 1)) path session Cfg.Node.pp node + | Pjoin (path1, path2, _) -> F.fprintf fmt "(%a + %a)" (doit (n - 1)) path1 (doit (n - 1)) path2 + | Pcall (path1, _, path2, _) -> F.fprintf fmt "(%a{%a})" (doit (n - 1)) path1 (doit (n - 1)) path2 in + let print_delayed () = + if not (PathMap.is_empty !delayed) then begin + let f path num = F.fprintf fmt "P%d = %a@\n" num (doit 1) path in + F.fprintf fmt "where@\n"; + PathMap.iter f !delayed + end in + add_delayed path; + doit 0 fmt path; + print_delayed () + + let d p = + L.add_print_action (L.PTpath, Obj.repr p) + + let rec contains p1 p2 = match p2 with + | Pjoin (p2', p2'', _) -> + contains p1 p2' || contains p1 p2'' + | _ -> p1 == p2 + + let create_loc_trace path pos_opt : Errlog.loc_trace = + let trace = ref [] in + let mk_trace_elem level loc descr node_tags = + { Errlog.lt_level = level; + Errlog.lt_loc = loc; + Errlog.lt_description = descr; + Errlog.lt_node_tags = node_tags } in + let g level path session exn_opt = + let curr_node = curr_node path in + let curr_loc = Cfg.Node.get_loc curr_node in + match Cfg.Node.get_kind curr_node with + | Cfg.Node.Join_node -> () (* omit join nodes from error traces *) + | Cfg.Node.Start_node pdesc -> + let pname = Cfg.Procdesc.get_proc_name pdesc in + let name = Procname.to_string pname in + let name_id = Procname.to_filename pname in + let descr = "start of procedure " ^ (Procname.to_simplified_string pname) in + let node_tags = [(Io_infer.Xml.tag_kind,"procedure_start"); (Io_infer.Xml.tag_name, name); (Io_infer.Xml.tag_name_id, name_id)] in + trace := mk_trace_elem level curr_loc descr node_tags :: !trace + | Cfg.Node.Prune_node (is_true_branch, if_kind, _) -> + let descr = match is_true_branch, if_kind with + | true, Sil.Ik_if -> "Taking true branch" + | false, Sil.Ik_if -> "Taking false branch" + | true, (Sil.Ik_for | Sil.Ik_while | Sil.Ik_dowhile) -> "Loop condition is true. Entering loop body" + | false, (Sil.Ik_for | Sil.Ik_while | Sil.Ik_dowhile) -> "Loop condition is false. Leaving loop" + | true, Sil.Ik_switch -> "Switch condition is true. Entering switch case" + | false, Sil.Ik_switch -> "Switch condition is false. Skipping switch case" + | true, (Sil.Ik_bexp | Sil.Ik_land_lor) -> "Condition is true" + | false, (Sil.Ik_bexp | Sil.Ik_land_lor) -> "Condition is false" in + let node_tags = [(Io_infer.Xml.tag_kind,"condition"); (Io_infer.Xml.tag_branch, if is_true_branch then "true" else "false")] in + trace := mk_trace_elem level curr_loc descr node_tags :: !trace + | Cfg.Node.Exit_node pdesc -> + let pname = Cfg.Procdesc.get_proc_name pdesc in + let descr = "return from a call to " ^ (Procname.to_string pname) in + let name = Procname.to_string pname in + let name_id = Procname.to_filename pname in + let node_tags = [(Io_infer.Xml.tag_kind,"procedure_end"); (Io_infer.Xml.tag_name, name); (Io_infer.Xml.tag_name_id, name_id)] in + trace := mk_trace_elem level curr_loc descr node_tags :: !trace + | _ -> + let descr, node_tags = + match exn_opt with + | None -> "", [] + | Some exn_name -> + let exn_str = Mangled.to_string exn_name in + if exn_str = "" + then "exception", [(Io_infer.Xml.tag_kind,"exception")] + else "exception " ^ exn_str, [(Io_infer.Xml.tag_kind,"exception"); (Io_infer.Xml.tag_name, exn_str)] in + let descr = + match get_description path with + | Some path_descr -> + if String.length descr > 0 then descr^" "^path_descr else path_descr + | None -> descr in + trace := mk_trace_elem level curr_loc descr node_tags :: !trace in + iter_longest_sequence g pos_opt path; + let compare lt1 lt2 = + let n = int_compare lt1.Errlog.lt_level lt2.Errlog.lt_level in + if n <> 0 then n else Sil.loc_compare lt1.Errlog.lt_loc lt2.Errlog.lt_loc in + let relevant lt = lt.Errlog.lt_node_tags <> [] in + list_remove_irrelevant_duplicates compare relevant (list_rev !trace) + (* list_remove_duplicates compare (list_sort compare !trace) *) + +end +(* =============== END of the Path module ===============*) + +module PropMap = Map.Make (struct + type t = Prop.normal Prop.t + let compare = Prop.prop_compare + end) + +(* =============== START of the PathSet module ===============*) +module PathSet : sig + type t + + (** It's the caller's resposibility to ensure that Prop.prop_rename_primed_footprint_vars was called on the prop *) + val add_renamed_prop : Prop.normal Prop.t -> Path.t -> t -> t + + (** dump the pathset *) + val d : t -> unit + + (** difference between two pathsets *) + val diff : t -> t -> t + + (** empty pathset *) + val empty : t + + (** list of elements in a pathset *) + val elements : t -> (Prop.normal Prop.t * Path.t) list + + (** equality for pathsets *) + val equal : t -> t -> bool + + (** filter a pathset on the prop component *) + val filter : (Prop.normal Prop.t -> bool) -> t -> t + + (** find the list of props whose associated path contains the given path *) + val filter_path : Path.t -> t -> Prop.normal Prop.t list + + (** fold over a pathset *) + val fold : (Prop.normal Prop.t -> Path.t -> 'a -> 'a) -> t -> 'a -> 'a + + (** It's the caller's resposibility to ensure that Prop.prop_rename_primed_footprint_vars was called on the list *) + val from_renamed_list: (Prop.normal Prop.t * Path.t) list -> t + + (** check whether the pathset is empty *) + val is_empty : t -> bool + + (** iterate over a pathset *) + val iter : (Prop.normal Prop.t -> Path.t -> unit) -> t -> unit + + (** map over the prop component of a pathset *) + val map : (Prop.normal Prop.t -> Prop.normal Prop.t) -> t -> t + + (** map over the prop component of a pathset using a partial function; elements mapped to None are discarded *) + val map_option : (Prop.normal Prop.t -> Prop.normal Prop.t option) -> t -> t + + (** partition a pathset on the prop component *) + val partition : (Prop.normal Prop.t -> bool) -> t -> t * t + + (** pretty print the pathset *) + val pp : printenv -> Format.formatter -> t -> unit + + (** number of elements in the pathset *) + val size : t -> int + + (** convert to a list of props *) + val to_proplist : t -> Prop.normal Prop.t list + + (** convert to a set of props *) + val to_propset : t -> Propset.t + + (** union of two pathsets *) + val union : t -> t -> t +end = struct + type t = Path.t PropMap.t + + let equal = PropMap.equal (fun p1 p2 -> true) (* only discriminate props, and ignore paths *) (* Path.equal *) + + let empty : t = PropMap.empty + + let elements ps = + let plist = ref [] in + let f prop path = plist := (prop, path) :: !plist in + PropMap.iter f ps; + !plist + + let to_proplist ps = + list_map fst (elements ps) + + let to_propset ps = + Propset.from_proplist (to_proplist ps) + + let filter f ps = + let elements = ref [] in + PropMap.iter (fun p _ -> elements := p :: !elements) ps; + elements := list_filter (fun p -> not (f p)) !elements; + let filtered_map = ref ps in + list_iter (fun p -> filtered_map := PropMap.remove p !filtered_map) !elements; + !filtered_map + + let partition f ps = + let elements = ref [] in + PropMap.iter (fun p _ -> elements := p :: !elements) ps; + let el1, el2 = ref ps, ref ps in + list_iter (fun p -> if f p then el2 := PropMap.remove p !el2 else el1 := PropMap.remove p !el1) !elements; + !el1, !el2 + + (** It's the caller's resposibility to ensure that Prop.prop_rename_primed_footprint_vars was called on the prop *) + let add_renamed_prop (p: Prop.normal Prop.t) (path: Path.t) (ps: t) : t = + let path_new = + try + let path_old = PropMap.find p ps in + Path.join path_old path + with Not_found -> path in + PropMap.add p path_new ps + + let union (ps1: t) (ps2: t) : t = + PropMap.fold add_renamed_prop ps1 ps2 + + (** check if the nodes in path p1 are a subset of those in p2 (not trace subset) *) + let path_nodes_subset p1 p2 = + let get_nodes p = + let s = ref Cfg.NodeSet.empty in + Path.iter_all_nodes_nocalls (fun n -> s := Cfg.NodeSet.add n !s) p; + !s in + Cfg.NodeSet.subset (get_nodes p1) (get_nodes p2) + + (** difference between pathsets for the differential fixpoint *) + let diff (ps1: t) (ps2: t) : t = + let res = ref ps1 in + let rem p path = + try + let path_old = PropMap.find p !res in + if path_nodes_subset path path_old (* do not propagate new path if it has no new nodes *) + then res := PropMap.remove p !res + with Not_found -> + res := PropMap.remove p !res in + PropMap.iter rem ps2; + !res + + let is_empty = PropMap.is_empty + + let iter = PropMap.iter + + let fold = PropMap.fold + + let map_option f ps = + let res = ref empty in + let do_elem prop path = match f prop with + | None -> () + | Some prop' -> res := add_renamed_prop prop' path !res in + iter do_elem ps; + !res + + let map f ps = + map_option (fun p -> Some (f p)) ps + + let size ps = + let res = ref 0 in + let add p _ = incr res in + let () = PropMap.iter add ps + in !res + + let pp pe fmt ps = + let count = ref 0 in + let pp_path fmt path = + F.fprintf fmt "[path: %a@\n%a]" Path.pp_stats path Path.pp path in + let f prop path = + incr count; + F.fprintf fmt "PROP %d:%a@\n%a@\n" !count pp_path path (Prop.pp_prop pe) prop in + iter f ps + + let d (ps: t) = L.add_print_action (L.PTpathset, Obj.repr ps) + + let filter_path path ps = + let plist = ref [] in + let f prop path' = + if Path.contains path path' + then plist := prop :: !plist in + iter f ps; + !plist + + (** It's the caller's resposibility to ensure that Prop.prop_rename_primed_footprint_vars was called on the list *) + let from_renamed_list (pl : ('a Prop.t * Path.t) list) : t = + list_fold_left (fun ps (p, pa) -> add_renamed_prop p pa ps) empty pl +end +(* =============== END of the PathSet module ===============*) + diff --git a/infer/src/backend/paths.mli b/infer/src/backend/paths.mli new file mode 100644 index 000000000..0c473e2f2 --- /dev/null +++ b/infer/src/backend/paths.mli @@ -0,0 +1,127 @@ +(* +* Copyright (c) 2009-2013 Monoidics ltd. +* Copyright (c) 2013- Facebook. +* All rights reserved. +*) + +(** Execution Paths *) + +open Utils + +module Path : sig +(** type for paths *) + type t + + type session = int + + (** add a call with its sub-path, the boolean indicates whether the subtrace for the procedure should be included *) + val add_call : bool -> t -> Procname.t -> t -> t + + (** check whether a path contains another path *) + val contains : t -> t -> bool + + (** check wether the path contains the given position *) + val contains_position : t -> Sil.path_pos -> bool + + (** Create the location trace of the path, up to the path position if specified *) + val create_loc_trace : t -> Sil.path_pos option -> Errlog.loc_trace + + (** return the current node of the path *) + val curr_node : t -> Cfg.node + + (** dump a path *) + val d : t -> unit + + (** dump statistics of the path *) + val d_stats : t -> unit + + (** extend a path with a new node reached from the given session, with an optional string for exceptions *) + val extend : Cfg.node -> Mangled.t option -> session -> t -> t + + val add_description : t -> string -> t + + (** iterate over each node in the path, excluding calls, once *) + val iter_all_nodes_nocalls : (Cfg.node -> unit) -> t -> unit + + (** iterate over the longest sequence belonging to the path, restricting to those containing the given position if given. + Do not iterate past the given position. + [f level path session exn_opt] is passed the current nesting [level] and [path] and previous [session] and possible exception [exn_opt] *) + val iter_longest_sequence : (int -> t -> int -> Mangled.t option -> unit) -> Sil.path_pos option -> t -> unit + + (** join two paths *) + val join : t -> t -> t + + (** pretty print a path *) + val pp : Format.formatter -> t -> unit + + (** pretty print statistics of the path *) + val pp_stats : Format.formatter -> t -> unit + + (** create a new path with given start node *) + val start : Cfg.node -> t +end + +(** Set of (prop,path) pairs, where the identity is given by prop *) +module PathSet : sig + type t + + (** It's the caller's resposibility to ensure that Prop.prop_rename_primed_footprint_vars was called on the prop *) + val add_renamed_prop : Prop.normal Prop.t -> Path.t -> t -> t + + (** dump the pathset *) + val d : t -> unit + + (** difference between two pathsets *) + val diff : t -> t -> t + + (** empty pathset *) + val empty : t + + (** list of elements in a pathset *) + val elements : t -> (Prop.normal Prop.t * Path.t) list + + (** equality for pathsets *) + val equal : t -> t -> bool + + (** filter a pathset on the prop component *) + val filter : (Prop.normal Prop.t -> bool) -> t -> t + + (** find the list of props whose associated path contains the given path *) + val filter_path : Path.t -> t -> Prop.normal Prop.t list + + (** fold over a pathset *) + val fold : (Prop.normal Prop.t -> Path.t -> 'a -> 'a) -> t -> 'a -> 'a + + (** It's the caller's resposibility to ensure that Prop.prop_rename_primed_footprint_vars was called on the list *) + val from_renamed_list: (Prop.normal Prop.t * Path.t) list -> t + + (** check whether the pathset is empty *) + val is_empty : t -> bool + + (** iterate over a pathset *) + val iter : (Prop.normal Prop.t -> Path.t -> unit) -> t -> unit + + (** map over the prop component of a pathset. *) + val map : (Prop.normal Prop.t -> Prop.normal Prop.t) -> t -> t + + (** map over the prop component of a pathset using a partial function; elements mapped to None are discarded *) + val map_option : (Prop.normal Prop.t -> Prop.normal Prop.t option) -> t -> t + + (** partition a pathset on the prop component *) + val partition : (Prop.normal Prop.t -> bool) -> t -> t * t + + (** pretty print the pathset *) + val pp : printenv -> Format.formatter -> t -> unit + + (** number of elements in the pathset *) + val size : t -> int + + (** convert to a list of props *) + val to_proplist : t -> Prop.normal Prop.t list + + (** convert to a set of props *) + val to_propset : t -> Propset.t + + (** union of two pathsets *) + val union : t -> t -> t +end diff --git a/infer/src/backend/preanal.ml b/infer/src/backend/preanal.ml new file mode 100644 index 000000000..9b8dc58b1 --- /dev/null +++ b/infer/src/backend/preanal.ml @@ -0,0 +1,381 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +module L = Logging +open Utils + +(** find all the predecessors of nodes, using exception links *) +module AllPreds = struct + module NodeHash = Cfg.NodeHash + let preds_table = NodeHash.create 3 (* table from node to set of predecessors *) + + let clear_table () = + NodeHash.clear preds_table + + let mk_table cfg = + let do_pdesc pname pdesc = + let exit_node = Cfg.Procdesc.get_exit_node pdesc in + let add_edge is_exn nfrom nto = + if is_exn && Cfg.Node.equal nto exit_node then () + else + try + let preds = NodeHash.find preds_table nto in + let preds' = Cfg.NodeSet.add nfrom preds in + NodeHash.replace preds_table nto preds' + with Not_found -> + NodeHash.add preds_table nto (Cfg.NodeSet.singleton nfrom) in + let do_node n = + list_iter (add_edge false n) (Cfg.Node.get_succs n); + list_iter (add_edge true n) (Cfg.Node.get_exn n) in + let proc_nodes = Cfg.Procdesc.get_nodes pdesc in + list_iter do_node proc_nodes in + clear_table (); + Cfg.iter_proc_desc cfg do_pdesc + + let get_preds n = + try + let preds = NodeHash.find preds_table n in + Cfg.NodeSet.elements preds + with Not_found -> + Cfg.Node.get_preds n +end + +module Vset = Set.Make (struct + type t = Sil.pvar + let compare = Sil.pvar_compare + end) + +let aliased_var = ref Vset.empty + +let captured_var = ref Vset.empty + +let is_not_function cfg x = + let pname = Procname.from_string (Mangled.to_string (Sil.pvar_get_name x)) in + Cfg.Procdesc.find_from_name cfg pname = None + +let is_captured_pvar pdesc x = + let captured = Cfg.Procdesc.get_captured pdesc in + list_exists (fun (m, _) -> (Sil.pvar_to_string x) = (Mangled.to_string m)) captured + +(** variables read in the expression *) +let rec use_exp cfg pdesc (exp: Sil.exp) acc = + match exp with + | Sil.Var _ | Sil.Sizeof _ -> acc + | Sil.Const (Sil.Ctuple((Sil.Const (Sil.Cfun pname)):: _)) -> + (* for tuples representing the assignment of a block we take the block name *) + (* look for its procdesc and add its captured vars to the set of captured vars. *) + let found_pd = ref None in + Cfg.iter_proc_desc cfg (fun pn pd -> if Procname.equal pn pname then found_pd:= Some pd); + let defining_proc = Cfg.Procdesc.get_proc_name pdesc in + (match !found_pd with + | Some pd -> + list_iter (fun (x, _) -> + captured_var:= Vset.add (Sil.mk_pvar x defining_proc) !captured_var + ) (Cfg.Procdesc.get_captured pd) + | _ -> ()); + acc + | Sil.Const _ -> acc + | Sil.Lvar x -> + (* If x is a captured var in the current procdesc don't add it to acc *) + if is_captured_pvar pdesc x then acc else Vset.add x acc + | Sil.Cast (_, e) | Sil.UnOp (_, e, _) | Sil.Lfield (e, _, _) -> use_exp cfg pdesc e acc + | Sil.BinOp (_, e1, e2) | Sil.Lindex (e1, e2) -> use_exp cfg pdesc e1 (use_exp cfg pdesc e2 acc) + +and use_etl cfg pdesc (etl: (Sil.exp * Sil.typ) list) acc = + list_fold_left (fun acc (e, _) -> use_exp cfg pdesc e acc) acc etl + +and use_instrl cfg tenv (pdesc: Cfg.Procdesc.t) (il : Sil.instr list) acc = + list_fold_left (fun acc instr -> use_instr cfg tenv pdesc instr acc) acc il + +and use_instr cfg tenv (pdesc: Cfg.Procdesc.t) (instr: Sil.instr) acc = + match instr with + | Sil.Set (_, _, e, _) + | Sil.Letderef (_, e, _, _) -> use_exp cfg pdesc e acc + | Sil.Prune (e, _, _, _) -> use_exp cfg pdesc e acc + | Sil.Call (_, e, etl, _, _) -> use_etl cfg pdesc etl acc + | Sil.Nullify _ -> acc + | Sil.Abstract _ | Sil.Remove_temps _ | Sil.Stackop _ | Sil.Declare_locals _ -> acc + | Sil.Goto_node (e, _) -> use_exp cfg pdesc e acc + +(** variables written in the expression *) +let rec def_exp cfg (exp: Sil.exp) acc = + match exp with + | Sil.Lvar x -> if is_not_function cfg x then Vset.add x acc else acc + | Sil.Cast (_, e) -> def_exp cfg e acc + | _ -> acc + +let rec def_instr cfg (instr: Sil.instr) acc = + match instr with + | Sil.Set (e, _, _, _) -> def_exp cfg e acc + | Sil.Call _ | Sil.Letderef _ | Sil.Prune _ -> acc + | Sil.Nullify (x, _, _) -> + if is_not_function cfg x then Vset.add x acc else acc + | Sil.Abstract _ | Sil.Remove_temps _ | Sil.Stackop _ | Sil.Declare_locals _ -> acc + | Sil.Goto_node _ -> acc + +and def_instrl cfg instrs acc = + list_fold_left (fun acc' i -> def_instr cfg i acc') acc instrs + +(* computes the addresses that are assigned to something or passed as parameters to*) +(* a functions. These will be considered becoming possibly aliased *) +let rec aliasing_instr cfg pdesc (instr: Sil.instr) acc = + match instr with + | Sil.Set (_, _, e, _) -> use_exp cfg pdesc e acc + | Sil.Call (_, _, argl, _, _) -> + let argl'= fst (list_split argl) in + list_fold_left (fun acc' e' -> use_exp cfg pdesc e' acc') acc argl' + | Sil.Letderef _ | Sil.Prune _ -> acc + | Sil.Nullify _ -> acc + | Sil.Abstract _ | Sil.Remove_temps _ | Sil.Stackop _ | Sil.Declare_locals _ -> acc + | Sil.Goto_node _ -> acc + +and aliasing_instrl cfg pdesc (il : Sil.instr list) acc = + list_fold_left (fun acc instr -> aliasing_instr cfg pdesc instr acc) acc il + +(* computes possible alisased var *) +let def_aliased_var cfg pdesc instrs acc = + list_fold_left (fun acc' i -> aliasing_instr cfg pdesc i acc') acc instrs + +(** variables written by instructions in the node *) +let def_node cfg node acc = + match Cfg.Node.get_kind node with + | Cfg.Node.Start_node _ | Cfg.Node.Exit_node _ | Cfg.Node.Join_node | Cfg.Node.Skip_node _ -> acc + | Cfg.Node.Prune_node _ + | Cfg.Node.Stmt_node _ -> + def_instrl cfg (Cfg.Node.get_instrs node) acc + +let compute_live_instr cfg tenv pdesc s instr = + use_instr cfg tenv pdesc instr (Vset.diff s (def_instr cfg instr Vset.empty)) + +let compute_live_instrl cfg tenv pdesc instrs livel = + list_fold_left (compute_live_instr cfg tenv pdesc) livel (list_rev instrs) + +module Worklist = struct + module S = Cfg.NodeSet + + let worklist = ref S.empty + + let reset _ = worklist := S.empty + let add node = worklist := S.add node !worklist + let add_list = list_iter add + let pick () = + let min = S.min_elt !worklist in + worklist := S.remove min !worklist; + min +end + +(** table of live variables *) +module Table: sig + val reset: unit -> unit + val get_live: Cfg.node -> Vset.t (** variables live after the last instruction in the current node *) + val replace: Cfg.node -> Vset.t -> unit + val propagate_to_preds: Vset.t -> Cfg.node list -> unit (** propagate live variables to predecessor nodes *) + val iter: Vset.t -> (Cfg.node -> Vset.t -> Vset.t -> unit) -> unit +end = struct + module H = Cfg.NodeHash + let table = H.create 1024 + let reset _ = H.clear table + let get_live node = try H.find table node with Not_found -> Vset.empty + let replace node set = H.replace table node set + + let propagate_to_preds set preds = + let do_node node = + try + let oldset = H.find table node in + let newset = Vset.union set oldset in + replace node newset; + if not (Vset.equal oldset newset) then Worklist.add node + with Not_found -> + replace node set; Worklist.add node in + list_iter do_node preds + + let iter init f = + let get_live_preds init node = (** nodes live at predecessors *) + match AllPreds.get_preds node with + | [] -> init + | preds -> list_fold_left Vset.union Vset.empty (list_map get_live preds) in + H.iter (fun node live -> f node (get_live_preds init node) live) table +end + +(** compute the variables which are possibly aliased in node n *) +let compute_aliased cfg n aliased_var = + match Cfg.Node.get_kind n with + | Cfg.Node.Start_node _ | Cfg.Node.Exit_node _ | Cfg.Node.Join_node | Cfg.Node.Skip_node _ -> aliased_var + | Cfg.Node.Prune_node _ + | Cfg.Node.Stmt_node _ -> + def_aliased_var cfg (Cfg.Node.get_proc_desc n) (Cfg.Node.get_instrs n) aliased_var + +(** Compute condidate nullable variables amongst formals and locals *) +let compute_candidates procdesc : Vset.t * (Vset.t -> Vset.elt list) = + let candidates = ref Vset.empty in + let struct_array_cand = ref Vset.empty in + let typ_is_struct_array = function + | Sil.Tstruct _ | Sil.Tarray _ -> true + | _ -> false in + let add_vi (pvar, typ) = + let pv = Sil.mk_pvar pvar (Cfg.Procdesc.get_proc_name procdesc) in + if is_captured_pvar procdesc pv then () (* don't add captured vars of the current pdesc to candidates *) + else ( + candidates := Vset.add pv !candidates; + if typ_is_struct_array typ then struct_array_cand := Vset.add pv !struct_array_cand + ) in + list_iter add_vi (list_map (fun (var, typ) -> Mangled.from_string var, typ) (Cfg.Procdesc.get_formals procdesc)); + list_iter add_vi (Cfg.Procdesc.get_locals procdesc); + let get_sorted_candidates vs = + let priority, no_pri = list_partition (fun pv -> Vset.mem pv !struct_array_cand) (Vset.elements vs) in + list_rev_append (list_rev priority) no_pri in + !candidates, get_sorted_candidates + +(** Construct a table wich associates to each node a set of live variables *) +let analyze_proc cfg tenv pdesc cand = + let exit_node = Cfg.Procdesc.get_exit_node pdesc in + Worklist.reset (); + Table.reset (); + Worklist.add exit_node; + try + while true do + let node = Worklist.pick () in + aliased_var := Vset.union (compute_aliased cfg node !aliased_var) !aliased_var; + let curr_live = Table.get_live node in + let preds = AllPreds.get_preds node in + let live_at_predecessors = + match Cfg.Node.get_kind node with + | Cfg.Node.Start_node _ | Cfg.Node.Exit_node _ | Cfg.Node.Join_node | Cfg.Node.Skip_node _ -> curr_live + | Cfg.Node.Prune_node _ + | Cfg.Node.Stmt_node _ -> + compute_live_instrl cfg tenv pdesc (Cfg.Node.get_instrs node) curr_live in + Table.propagate_to_preds (Vset.inter live_at_predecessors cand) preds + done + with Not_found -> () + +(* Printing function useful for debugging *) +let print_aliased_var s al_var = + L.out s; + Vset.iter (fun v -> L.out " %a, " (Sil.pp_pvar pe_text) v) al_var; + L.out "@." + +(* Printing function useful for debugging *) +let print_aliased_var_l s al_var = + L.out s; + list_iter (fun v -> L.out " %a, " (Sil.pp_pvar pe_text) v) al_var; + L.out "@." + +(* Instruction i is nullifying a block variable *) +let is_block_nullify i = + match i with + | Sil.Nullify(pvar, _, true) -> Sil.is_block_pvar pvar + | _ -> false + +(** Add nullify instructions to the node given dead program variables *) +let node_add_nullify_instrs n dead_vars_after dead_vars_before = + let loc = Cfg.Node.get_last_loc n in + let move_tmp_pvars_first pvars = + let pvars_tmp, pvars_notmp = list_partition Errdesc.pvar_is_frontend_tmp pvars in + pvars_tmp @ pvars_notmp in + let instrs_after = + list_map (fun pvar -> Sil.Nullify (pvar, loc, false)) (move_tmp_pvars_first dead_vars_after) in + let instrs_before = + list_map (fun pvar -> Sil.Nullify (pvar, loc, false)) (move_tmp_pvars_first dead_vars_before) in + (* Nullify(bloc_var,_,true) can be placed in the middle of the block because when we add this instruction*) + (* we don't have already all the instructions of the node. Here we reorder the instructions to move *) + (* nullification of blocks at the end of existing instructions. *) + let block_nullify, no_block_nullify = list_partition is_block_nullify (Cfg.Node.get_instrs n) in + Cfg.Node.replace_instrs n (no_block_nullify @ block_nullify); + Cfg.Node.append_instrs_temps n instrs_after []; + Cfg.Node.prepend_instrs_temps n instrs_before [] + +(** return true if the node does not assign any variables *) +let node_assigns_no_variables cfg node = + let instrs = Cfg.Node.get_instrs node in + let assign_set = def_instrl cfg instrs (Vset.empty) in + Vset.is_empty assign_set + +(** Set the dead variables of a node, by default as dead_after. +If the node is a prune or a join node, propagate as dead_before in the successors *) +let add_dead_pvars_after_conditionals_join cfg n dead_pvars = + (* L.out " node %d: %a@." (Cfg.Node.get_id n) (Sil.pp_pvar_list pe_text) dead_pvars; *) + let seen = ref Cfg.NodeSet.empty in + let rec add_after_prune_join is_after node = + if Cfg.NodeSet.mem node !seen (* gone through a loop in the cfg *) + then Cfg.Node.set_dead_pvars n true dead_pvars + else + begin + seen := Cfg.NodeSet.add node !seen; + let node_is_exit n = match Cfg.Node.get_kind n with + | Cfg.Node.Exit_node _ -> true + | _ -> false in + let next_is_exit n = match Cfg.Node.get_succs n with + | [n'] -> node_is_exit n' + | _ -> false in + match Cfg.Node.get_kind node with + | Cfg.Node.Prune_node _ | Cfg.Node.Join_node when node_assigns_no_variables cfg node && not (next_is_exit node) -> + (* cannot push nullify instructions after an assignment, as they could nullify the same variable *) + let succs = Cfg.Node.get_succs node in + list_iter (add_after_prune_join false) succs + | _ -> + let new_dead_pvs = + let old_pvs = Cfg.Node.get_dead_pvars node is_after in + let pv_is_new pv = not (list_exists (Sil.pvar_equal pv) old_pvs) in + (list_filter pv_is_new dead_pvars) @ old_pvs in + Cfg.Node.set_dead_pvars node is_after new_dead_pvs + end in + add_after_prune_join true n + +(** Find the set of dead variables for the procedure pname and add nullify instructions. +The variables that are possibly aliased are only considered just before the exit node. *) +let analyze_and_annotate_proc cfg tenv pname pdesc = + let exit_node = Cfg.Procdesc.get_exit_node pdesc in + let exit_node_is_succ node = + match Cfg.Node.get_succs node with + | [en] -> Cfg.Node.equal en exit_node + | _ -> false in + let cand, get_sorted_cand = compute_candidates pdesc in + aliased_var:= Vset.empty; + captured_var:= Vset.empty; + analyze_proc cfg tenv pdesc cand; (* as side effect it coputes the set aliased_var *) + (* print_aliased_var "@.@.Aliased variable computed: " !aliased_var; + L.out " PROCEDURE %s@." (Procname.to_string pname); *) + let dead_pvars_added = ref 0 in + let dead_pvars_limit = 100000 in + let incr_dead_pvars_added pvars = + let num = list_length pvars in + dead_pvars_added := num + !dead_pvars_added; + if !dead_pvars_added > dead_pvars_limit && !dead_pvars_added - num <= dead_pvars_limit + then L.err "WARNING: liveness: more than %d dead pvars added in procedure %a, stopping@." dead_pvars_limit Procname.pp pname in + Table.iter cand (fun n live_at_predecessors live_current -> (* set dead variables on nodes *) + let nonnull_pvars = Vset.inter (def_node cfg n live_at_predecessors) cand in (* live before, or assigned to *) + let dead_pvars = Vset.diff nonnull_pvars live_current in (* only nullify when variable become live *) + (* L.out " Node %s " (string_of_int (Cfg.Node.get_id n)); *) + let dead_pvars_no_captured = Vset.diff dead_pvars !captured_var in + (* print_aliased_var "@.@.Non-nullable variable computed: " nonnull_pvars; + print_aliased_var "@.Dead variable computed: " dead_pvars; + print_aliased_var "@.Captured variable computed: " !captured_var; + print_aliased_var "@.Dead variable excluding captured computed: " dead_pvars_no_captured; *) + let dead_pvars_no_alias = get_sorted_cand (Vset.diff dead_pvars_no_captured !aliased_var) in + (* print_aliased_var_l "@. Final Dead variable computed: " dead_pvars_no_alias; *) + let dead_pvars_to_add = + if exit_node_is_succ n (* add dead aliased vars just before the exit node *) + then dead_pvars_no_alias @ (get_sorted_cand (Vset.inter cand !aliased_var)) + else dead_pvars_no_alias in + incr_dead_pvars_added dead_pvars_to_add; + if !dead_pvars_added < dead_pvars_limit then add_dead_pvars_after_conditionals_join cfg n dead_pvars_to_add); + list_iter (fun n -> (* generate nullify instructions *) + let dead_pvs_after = Cfg.Node.get_dead_pvars n true in + let dead_pvs_before = Cfg.Node.get_dead_pvars n false in + node_add_nullify_instrs n dead_pvs_after dead_pvs_before) + (Cfg.Procdesc.get_nodes pdesc); + Table.reset () + +let time = ref 0.0 +let doit cfg tenv = + let init = Unix.gettimeofday () in + (* L.out "#### Dead variable nullification ####"; *) + AllPreds.mk_table cfg; + Cfg.iter_proc_desc cfg (analyze_and_annotate_proc cfg tenv); + AllPreds.clear_table (); + time := !time +. (Unix.gettimeofday () -. init) + +let gettime () = !time diff --git a/infer/src/backend/preanal.mli b/infer/src/backend/preanal.mli new file mode 100644 index 000000000..52aa706bf --- /dev/null +++ b/infer/src/backend/preanal.mli @@ -0,0 +1,13 @@ +(* +* Copyright (c) 2009-2013 Monoidics ltd. +* Copyright (c) 2013- Facebook. +* All rights reserved. +*) + +(** Preanalysis for eliminating dead local variables *) + +(** Perform liveness analysis *) +val doit : Cfg.cfg -> Sil.tenv -> unit + +(** Return the time for the last execution of the analysis *) +val gettime : unit -> float diff --git a/infer/src/backend/printer.ml b/infer/src/backend/printer.ml new file mode 100644 index 000000000..8f9bea203 --- /dev/null +++ b/infer/src/backend/printer.ml @@ -0,0 +1,422 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Printers for the analysis results *) + +module L = Logging +module F = Format +open Utils (* No abbreviation for Util, as every module can depend on it *) + +(** return true if the node was visited during footprint and during re-execution*) +let is_visited_phase node = + let proc_desc = Cfg.Node.get_proc_desc node in + let proc_name = Cfg.Procdesc.get_proc_name proc_desc in + match Specs.get_summary proc_name with + | None -> false, false + | Some summary -> + let stats = summary.Specs.stats in + let is_visited_fp = IntSet.mem (Cfg.Node.get_id node) stats.Specs.nodes_visited_fp in + let is_visited_re = IntSet.mem (Cfg.Node.get_id node) stats.Specs.nodes_visited_re in + is_visited_fp, is_visited_re + +(** return true if the node was visited during analysis *) +let is_visited node = + let visited_fp, visited_re = is_visited_phase node in + visited_fp || visited_re + +(** current formatter for the html output *) +let html_formatter = ref F.std_formatter + +(* =============== START of module Log_nodes =============== *) + +(** Print information when starting and finishing the processing of a node *) +module Log_nodes : sig + val start_node : int -> Sil.location -> Procname.t -> Cfg.node list -> Cfg.node list -> Cfg.node list -> bool + val finish_node : int -> unit +end = struct + let log_files = Hashtbl.create 11 + + let id_to_fname id = "node" ^ string_of_int id + + let start_node nodeid loc proc_name preds succs exn = + let node_fname = id_to_fname nodeid in + let modified = Io_infer.Html.modified_during_analysis ["nodes"; node_fname] in + let needs_initialization, (fd, fmt) = + if modified then + (false, Io_infer.Html.open_out ["nodes"; node_fname]) + else + (true, Io_infer.Html.create DB.Results_dir.Abs_source_dir ["nodes"; node_fname]) in + html_formatter := fmt; + Hashtbl.replace log_files (node_fname, !DB.current_source) fd; + if needs_initialization then + (F.fprintf fmt "

Cfg Node %a

" (Io_infer.Html.pp_line_link ~text: (Some (string_of_int nodeid)) [".."]) loc.Sil.line; + F.fprintf fmt "PROC: %a LINE:%a\n" (Io_infer.Html.pp_proc_link [".."] proc_name) (Escape.escape_xml (Procname.to_string proc_name)) (Io_infer.Html.pp_line_link [".."]) loc.Sil.line; + F.fprintf fmt "
PREDS:@\n"; + list_iter (fun node -> Io_infer.Html.pp_node_link [".."] "" (list_map Cfg.Node.get_id (Cfg.Node.get_preds node)) (list_map Cfg.Node.get_id (Cfg.Node.get_succs node)) (list_map Cfg.Node.get_id (Cfg.Node.get_exn node)) (is_visited node) false fmt (Cfg.Node.get_id node)) preds; + F.fprintf fmt "
SUCCS: @\n"; + list_iter (fun node -> Io_infer.Html.pp_node_link [".."] "" (list_map Cfg.Node.get_id (Cfg.Node.get_preds node)) (list_map Cfg.Node.get_id (Cfg.Node.get_succs node)) (list_map Cfg.Node.get_id (Cfg.Node.get_exn node)) (is_visited node) false fmt (Cfg.Node.get_id node)) succs; + F.fprintf fmt "
EXN: @\n"; + list_iter (fun node -> Io_infer.Html.pp_node_link [".."] "" (list_map Cfg.Node.get_id (Cfg.Node.get_preds node)) (list_map Cfg.Node.get_id (Cfg.Node.get_succs node)) (list_map Cfg.Node.get_id (Cfg.Node.get_exn node)) (is_visited node) false fmt (Cfg.Node.get_id node)) exn; + F.fprintf fmt "
@\n"; + F.pp_print_flush fmt (); + true + ) + else false + + let finish_node nodeid = + let fname = id_to_fname nodeid in + let fd = Hashtbl.find log_files (fname, !DB.current_source) in + Unix.close fd; + html_formatter := F.std_formatter +end +(* =============== END of module Log_nodes =============== *) + +(** printing functions *) +let force_delayed_print fmt = + let pe_default = if !Config.write_html then pe_html Black else pe_text in + function + | (L.PTatom, a) -> + let (a: Sil.atom) = Obj.obj a in + Sil.pp_atom pe_default fmt a + | (L.PTdecrease_indent, n) -> + let (n: int) = Obj.obj n in + for i = 1 to n do F.fprintf fmt "@]" done + | (L.PTexp, e) -> + let (e: Sil.exp) = Obj.obj e in + Sil.pp_exp pe_default fmt e + | (L.PTexp_list, el) -> + let (el: Sil.exp list) = Obj.obj el in + Sil.pp_exp_list pe_default fmt el + | (L.PThpred, hpred) -> + let (hpred: Sil.hpred) = Obj.obj hpred in + Sil.pp_hpred pe_default fmt hpred + | (L.PTincrease_indent, n) -> + let (n: int) = Obj.obj n in + let s = ref "" in + for i = 1 to n do s := " " ^ !s done; + F.fprintf fmt "%s@[" !s + | (L.PTinstr, i) -> + let (i: Sil.instr) = Obj.obj i in + if !Config.write_html then F.fprintf fmt "%a%a%a" Io_infer.Html.pp_start_color Green (Sil.pp_instr (pe_html Green)) i Io_infer.Html.pp_end_color () + else Sil.pp_instr pe_text fmt i + | (L.PTinstr_list, il) -> + let (il: Sil.instr list) = Obj.obj il in + if !Config.write_html then F.fprintf fmt "%a%a%a" Io_infer.Html.pp_start_color Green (Sil.pp_instr_list (pe_html Green)) il Io_infer.Html.pp_end_color () + else Sil.pp_instr_list pe_text fmt il + | (L.PTjprop_list, shallow_jpl) -> + let ((shallow: bool), (jpl: Prop.normal Specs.Jprop.t list)) = Obj.obj shallow_jpl in + Specs.Jprop.pp_list pe_default shallow fmt jpl + | (L.PTjprop_short, jp) -> + let (jp: Prop.normal Specs.Jprop.t) = Obj.obj jp in + Specs.Jprop.pp_short pe_default fmt jp + | (L.PTloc, loc) -> + let (loc: Sil.location) = Obj.obj loc in + Sil.pp_loc fmt loc + | (L.PTnode_instrs, b_n) -> + let (b: bool), (io: Sil.instr option), (n: Cfg.node) = Obj.obj b_n in + if !Config.write_html then F.fprintf fmt "%a%a%a" Io_infer.Html.pp_start_color Green (Cfg.Node.pp_instr (pe_html Green) io ~sub_instrs: b) n Io_infer.Html.pp_end_color () + else F.fprintf fmt "%a" (Cfg.Node.pp_instr pe_text io ~sub_instrs: b) n + | (L.PToff, off) -> + let (off: Sil.offset) = Obj.obj off in + Sil.pp_offset pe_default fmt off + | (L.PToff_list, offl) -> + let (offl: Sil.offset list) = Obj.obj offl in + Sil.pp_offset_list pe_default fmt offl + | (L.PTpathset, ps) -> + let (ps: Paths.PathSet.t) = Obj.obj ps in + F.fprintf fmt "%a@\n" (Paths.PathSet.pp pe_default) ps + | (L.PTpi, pi) -> + let (pi: Sil.atom list) = Obj.obj pi in + Prop.pp_pi pe_default fmt pi + | (L.PTpath, path) -> + let (path: Paths.Path.t) = Obj.obj path in + Paths.Path.pp fmt path + | (L.PTprop, p) -> + let (p: Prop.normal Prop.t) = Obj.obj p in + Prop.pp_prop pe_default fmt p + | (L.PTproplist, x) -> + let (p : Prop.normal Prop.t), (pl: Prop.normal Prop.t list) = Obj.obj x in + Propgraph.pp_proplist pe_default "PROP" (p, false) fmt pl + | (L.PTprop_list_with_typ, plist) -> + let (pl: Prop.normal Prop.t list) = Obj.obj plist in + F.fprintf fmt "%a" (Prop.pp_proplist_with_typ pe_default) pl + | (L.PTprop_with_typ, p) -> + let (p: Prop.normal Prop.t) = Obj.obj p in + Prop.pp_prop_with_typ pe_default fmt p + | (L.PTpvar, pvar) -> + let (pvar: Sil.pvar) = Obj.obj pvar in + Sil.pp_pvar pe_default fmt pvar + | (L.PTsexp, se) -> + let (se: Sil.strexp) = Obj.obj se in + Sil.pp_sexp pe_default fmt se + | (L.PTsexp_list, sel) -> + let (sel: Sil.strexp list) = Obj.obj sel in + Sil.pp_sexp_list pe_default fmt sel + | (L.PTsigma, sigma) -> + let (sigma: Sil.hpred list) = Obj.obj sigma in + Prop.pp_sigma pe_default fmt sigma + | (L.PTspec, spec) -> + let (spec: Prop.normal Specs.spec) = Obj.obj spec in + Specs.pp_spec (if !Config.write_html then pe_html Blue else pe_text) None fmt spec + | (L.PTstr, s) -> + let (s: string) = Obj.obj s in + F.fprintf fmt "%s" s + | (L.PTstr_color, s) -> + let (s: string), (c: color) = Obj.obj s in + if !Config.write_html then F.fprintf fmt "%a%s%a" Io_infer.Html.pp_start_color c s Io_infer.Html.pp_end_color () + else F.fprintf fmt "%s" s + | (L.PTstrln, s) -> + let (s: string) = Obj.obj s in + F.fprintf fmt "%s@\n" s + | (L.PTstrln_color, s) -> + let (s: string), (c: color) = Obj.obj s in + if !Config.write_html then F.fprintf fmt "%a%s%a@\n" Io_infer.Html.pp_start_color c s Io_infer.Html.pp_end_color () + else F.fprintf fmt "%s@\n" s + | (L.PTsub, sub) -> + let (sub: Sil.subst) = Obj.obj sub in + Prop.pp_sub pe_default fmt sub + | (L.PTtexp_full, te) -> + let (te: Sil.exp) = Obj.obj te in + Sil.pp_texp_full pe_default fmt te + | (L.PTtyp_full, t) -> + let (t: Sil.typ) = Obj.obj t in + Sil.pp_typ_full pe_default fmt t + | (L.PTtyp_list, tl) -> + let (tl: Sil.typ list) = Obj.obj tl in + (pp_seq (Sil.pp_typ pe_default)) fmt tl + | (L.PTerror, s) -> + let (s: string) = Obj.obj s in + if !Config.write_html then F.fprintf fmt "%aERROR: %s%a" Io_infer.Html.pp_start_color Red s Io_infer.Html.pp_end_color () + else F.fprintf fmt "ERROR: %s" s + | (L.PTwarning, s) -> + let (s: string) = Obj.obj s in + if !Config.write_html then F.fprintf fmt "%aWARNING: %s%a" Io_infer.Html.pp_start_color Orange s Io_infer.Html.pp_end_color () + else F.fprintf fmt "WARNING: %s" s + | (L.PTinfo, s) -> + let (s: string) = Obj.obj s in + if !Config.write_html then F.fprintf fmt "%aINFO: %s%a" Io_infer.Html.pp_start_color Blue s Io_infer.Html.pp_end_color () + else F.fprintf fmt "INFO: %s" s + +(** set printer hook as soon as this module is loaded *) +let () = L.printer_hook := force_delayed_print + +(** Execute the delayed print actions *) +let force_delayed_prints () = + Config.forcing_delayed_prints := true; + F.fprintf !html_formatter "@?"; (* flush html stream *) + list_iter (force_delayed_print !html_formatter) (list_rev (L.get_delayed_prints ())); + F.fprintf !html_formatter "@?"; + L.reset_delayed_prints (); + Config.forcing_delayed_prints := false + +(** Start a session, and create a new html fine for the node if it does not exist yet *) +let _start_session node (loc: Sil.location) proc_name session = + let node_id = Cfg.Node.get_id node in + (if Log_nodes.start_node node_id loc proc_name (Cfg.Node.get_preds node) (Cfg.Node.get_succs node) (Cfg.Node.get_exn node) + then F.fprintf !html_formatter "%a@[%a@]%a" Io_infer.Html.pp_start_color Green (Cfg.Node.pp_instr (pe_html Green) None ~sub_instrs: true) node Io_infer.Html.pp_end_color ()); + F.fprintf !html_formatter "%a%a" Io_infer.Html.pp_hline () (Io_infer.Html.pp_session_link ~with_name: true [".."]) (node_id, session, loc.Sil.line); + F.fprintf !html_formatter "%a" Io_infer.Html.pp_start_color Black + +let start_session node loc proc_name session = + if !Config.write_html then _start_session node loc proc_name session + +(** Finish a session, and perform delayed print actions if required *) +let finish_session node = + if !Config.test == false then force_delayed_prints () + else L.reset_delayed_prints (); + if !Config.write_html then begin + F.fprintf !html_formatter "%a" Io_infer.Html.pp_end_color (); + Log_nodes.finish_node (Cfg.Node.get_id node) + end + +(** Write log file for the proc *) +let _proc_write_log whole_seconds cfg pname = + match Cfg.Procdesc.find_from_name cfg pname with + | Some pdesc -> + let nodes = list_sort Cfg.Node.compare (Cfg.Procdesc.get_nodes pdesc) in + let linenum = (Cfg.Node.get_loc (list_hd nodes)).Sil.line in + let fd, fmt = + Io_infer.Html.create DB.Results_dir.Abs_source_dir [Procname.to_filename pname] in + F.fprintf fmt "

Procedure %a

@\n" + (Io_infer.Html.pp_line_link ~text: (Some (Escape.escape_xml (Procname.to_string pname))) []) + linenum; + list_iter + (fun n -> Io_infer.Html.pp_node_link [] + (Cfg.Node.get_description (pe_html Black) n) + (list_map Cfg.Node.get_id (Cfg.Node.get_preds n)) + (list_map Cfg.Node.get_id (Cfg.Node.get_succs n)) + (list_map Cfg.Node.get_id (Cfg.Node.get_exn n)) + (is_visited n) false fmt (Cfg.Node.get_id n)) + nodes; + (match Specs.get_summary pname with + | None -> () + | Some summary -> + Specs.pp_summary (pe_html Black) whole_seconds fmt summary; + Io_infer.Html.close (fd, fmt)) + | None -> () + +let proc_write_log whole_seconds cfg pname = + if !Config.write_html then _proc_write_log whole_seconds cfg pname + +(** Creare a hash table mapping line numbers to the set of errors occurring on that line *) +let create_errors_per_line err_log = + let err_per_line = Hashtbl.create 17 in + let add_err node_id_key loc ekind in_footprint err_name desc severity ltr pre_opt eclass = + let err_str = Localise.to_string err_name ^ " " ^ (pp_to_string Localise.pp_error_desc desc) in + (* if in_footprint then *) + try + let set = Hashtbl.find err_per_line loc.Sil.line in + Hashtbl.replace err_per_line loc.Sil.line (StringSet.add err_str set) + with Not_found -> + Hashtbl.add err_per_line loc.Sil.line (StringSet.singleton err_str) in + Errlog.iter add_err err_log; + err_per_line + +(** create err message for html file *) +let create_err_message err_string = + "\n
" ^ err_string ^ "
" + +(** Module to read specific lines from files. +The data from any file will stay in memory until the handle is collected by the gc *) +module LineReader : sig + type t + + (** create a line reader *) + val create : unit -> t + + (** get the line from a source file and line number *) + val from_file_linenum_original : t -> DB.source_file -> int -> string option + + (** get the line from a source file and line number looking for the copy of the file in the results dir *) + val from_file_linenum : t -> DB.source_file -> int -> string option + + (** get the line from a location looking for the copy of the file in the results dir *) + val from_loc : t -> Sil.location -> string option +end = struct + + (* map a file name to an array of string, one for each line in the file *) + type t = (DB.source_file, string array) Hashtbl.t + + let create () = + Hashtbl.create 1 + + let read_file fname = + let cin = open_in fname in + let lines = ref [] in + try + while true do + let line_raw = input_line cin in + let line = + let len = String.length line_raw in + if len > 0 && String.get line_raw (len -1) = '\013' then + String.sub line_raw 0 (len -1) + else line_raw in + lines := line :: !lines + done; + assert false (* execution never reaches here *) + with End_of_file -> + (close_in cin; + Array.of_list (list_rev !lines)) + + let file_data (hash: t) fname = + try + Some (Hashtbl.find hash fname) + with Not_found -> + try + let lines_arr = read_file (DB.source_file_to_string fname) in + Hashtbl.add hash fname lines_arr; + Some lines_arr + with exn when exn_not_timeout exn -> None + + let from_file_linenum_original hash fname linenum = + match file_data hash fname with + | None -> None + | Some lines_arr -> + if linenum > 0 && linenum <= Array.length lines_arr + then Some lines_arr.(linenum -1) + else None + + let from_file_linenum hash fname linenum = + let fname_in_resdir = DB.source_file_in_resdir fname in + let sourcefile_in_resdir = DB.abs_source_file_from_path (DB.filename_to_string fname_in_resdir) in + from_file_linenum_original hash sourcefile_in_resdir linenum + + let from_loc hash loc = + from_file_linenum hash loc.Sil.file loc.Sil.line +end + +(** Create filename.c.html with line numbers and links to nodes *) +let c_file_write_html proc_is_active linereader fname tenv cfg = + let proof_cover = ref Specs.Visitedset.empty in + let tbl = Hashtbl.create 11 in + let process_node n = + let lnum = (Cfg.Node.get_loc n).Sil.line in + let curr_nodes = + try Hashtbl.find tbl lnum + with Not_found -> [] in + Hashtbl.replace tbl lnum (n:: curr_nodes) in + let fname_encoding = DB.source_file_encoding fname in + let (fd, fmt) = Io_infer.Html.create DB.Results_dir.Abs_source_dir [".."; fname_encoding] in + let global_err_log = Errlog.empty () in + let do_proc proc_name proc_desc = (* add the err_log of this proc to [global_err_log] *) + let proc_loc = (Cfg.Procdesc.get_loc proc_desc) in + if proc_is_active proc_name && Cfg.Procdesc.is_defined proc_desc && (DB.source_file_equal proc_loc.Sil.file !DB.current_source) then + begin + list_iter process_node (Cfg.Procdesc.get_nodes proc_desc); + match Specs.get_summary proc_name with + | None -> () + | Some summary -> + list_iter + (fun sp -> proof_cover := Specs.Visitedset.union sp.Specs.visited !proof_cover) + (Specs.get_specs_from_payload summary); + Errlog.update global_err_log summary.Specs.stats.Specs.err_log + end in + Cfg.iter_proc_desc cfg do_proc; + let err_per_line = create_errors_per_line global_err_log in + try + (let s = "

File " ^ (DB.source_file_to_string !DB.current_source) ^ "

\n" ^ + "\n" in + F.fprintf fmt "%s" s); + let linenum = ref 0 in + while true do + incr linenum; + let line_html = match LineReader.from_file_linenum linereader !DB.current_source !linenum with + | Some line_raw -> Escape.escape_xml line_raw + | None -> raise End_of_file in + let nodes_at_linenum = + try Hashtbl.find tbl !linenum + with Not_found -> [] in + let errors_at_linenum = + try + let errset = Hashtbl.find err_per_line !linenum in + StringSet.elements errset + with Not_found -> [] in + let linenum_str = string_of_int !linenum in + let line_str = "LINE" ^ linenum_str in + let str = + "\n" + done + with End_of_file -> + (F.fprintf fmt "%s" "
" ^ linenum_str ^ "" ^ line_html in + F.fprintf fmt "%s" str; + list_iter (fun n -> + let isproof = Specs.Visitedset.mem (Cfg.Node.get_id n, []) !proof_cover in + Io_infer.Html.pp_node_link [fname_encoding] (Cfg.Node.get_description (pe_html Black) n) (list_map Cfg.Node.get_id (Cfg.Node.get_preds n)) (list_map Cfg.Node.get_id (Cfg.Node.get_succs n)) (list_map Cfg.Node.get_id (Cfg.Node.get_exn n)) (is_visited n) isproof fmt (Cfg.Node.get_id n)) nodes_at_linenum; + list_iter (fun n -> match Cfg.Node.get_kind n with + | Cfg.Node.Start_node proc_desc -> + let proc_name = Cfg.Procdesc.get_proc_name proc_desc in + let num_specs = list_length (Specs.get_specs proc_name) in + let label = (Escape.escape_xml (Procname.to_string proc_name)) ^ ": " ^ (string_of_int num_specs) ^ " specs" in + Io_infer.Html.pp_proc_link [fname_encoding] proc_name fmt label + | _ -> ()) nodes_at_linenum; + list_iter (fun err_string -> F.fprintf fmt "%s" (create_err_message err_string)) errors_at_linenum; + F.fprintf fmt "%s" "
\n"; + Errlog.pp_html [fname_encoding] fmt global_err_log; + Io_infer.Html.close (fd, fmt)) + +let c_files_write_html linereader exe_env = + let proc_is_active = Exe_env.proc_is_active exe_env in + if !Config.write_html then Exe_env.iter_files (c_file_write_html proc_is_active linereader) exe_env diff --git a/infer/src/backend/printer.mli b/infer/src/backend/printer.mli new file mode 100644 index 000000000..82121985d --- /dev/null +++ b/infer/src/backend/printer.mli @@ -0,0 +1,46 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Printers for the analysis results *) + +(** return true if the node was visited during footprint and during re-execution*) +val is_visited_phase : Cfg.Node.t -> bool * bool + +(** return true if the node was visited during analysis *) +val is_visited : Cfg.Node.t -> bool + +(** Execute the delayed print actions *) +val force_delayed_prints : unit -> unit + +(** Start a session, and create a new html fine for the node if it does not exist yet *) +val start_session : Cfg.node -> Sil.location -> Procname.t -> int -> unit + +(** Finish a session, and perform delayed print actions if required *) +val finish_session : Cfg.node -> unit + +(** Write log file for the proc, the boolean indicates whether to print whole seconds only *) +val proc_write_log : bool -> Cfg.cfg -> Procname.t -> unit + +(** Module to read specific lines from files. +The data from any file will stay in memory until the handle is collected by the gc *) +module LineReader : sig + type t + + (** create a line reader *) + val create : unit -> t + + (** get the line from a source file and line number *) + val from_file_linenum_original : t -> DB.source_file -> int -> string option + + (** get the line from a source file and line number looking for the copy of the file in the results dir *) + val from_file_linenum : t -> DB.source_file -> int -> string option + + (** get the line from a location looking for the copy of the file in the results dir *) + val from_loc : t -> Sil.location -> string option +end + +(** Create filename.c.html with line numbers and links to nodes for each file in the exe_env *) +val c_files_write_html : LineReader.t -> Exe_env.t -> unit diff --git a/infer/src/backend/procname.ml b/infer/src/backend/procname.ml new file mode 100644 index 000000000..277e1896b --- /dev/null +++ b/infer/src/backend/procname.ml @@ -0,0 +1,426 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Module for Procedure Names *) + +module L = Logging +module F = Format +open Utils +open Str + +type java_type = string option * string (* e.g. ("", "int") for primitive types or ("java.io", "PrintWriter") for objects *) + +(* java_signature extends base_signature with a classname and a package *) +type java_signature = { + classname: java_type; + returntype: java_type option; (* option because constructors have no return type *) + methodname: string; + parameters: java_type list +} + +type objc_signature = { + objc_class : string; + objc_method: string; +} + +type t = + | JAVA of java_signature + | C_CPP of string * (string option) (* it is a pair (plain, mangled optional) *) + | STATIC of string * (string option) (* it is a pair (plain name, filename optional) *) + | OBJC of objc_signature + | OBJC_BLOCK of string + +(* Defines the level of verbosity of some to_string functions *) +type detail_level = + | VERBOSE + | NON_VERBOSE + | SIMPLE + +let is_verbose v = + match v with + | VERBOSE -> true + | _ -> false + +type proc_name = t + +let mangled_compare so1 so2 = match so1, so2 with + | None, None -> 0 + | None, Some _ -> -1 + | Some _, None -> 1 + | Some s1, Some s2 -> string_compare s1 s2 + +(** A type is a pair (package, type_name) that is translated in a string package.type_name *) +let java_type_to_string p verbosity = + match p with + | (None, typ) -> typ + | (Some p, cls) -> + if is_verbose verbosity then p ^ "." ^ cls + else cls + +(** Given a list of types, it creates a unique string of types separated by commas *) +let rec java_param_list_to_string inputList verbosity = + match inputList with + | [] -> "" + | [head] -> java_type_to_string head verbosity + | head :: rest -> (java_type_to_string head verbosity) ^ "," ^ (java_param_list_to_string rest verbosity) + +(** It is the same as java_type_to_string, but Java return types are optional because of constructors without type *) +let java_return_type_to_string j verbosity = + match j.returntype with + | None -> "" + | Some typ -> + java_type_to_string typ verbosity + +let java_type_compare (p1, c1) (p2, c2) = + string_compare c1 c2 |> next mangled_compare p1 p2 + +let rec java_type_list_compare jt1 jt2 = + match jt1, jt2 with + | [], [] -> 0 + | [], _ -> -1 + | _, [] -> 1 + | (x1:: rest1), (x2:: rest2) -> + java_type_compare x1 x2 |> next java_type_list_compare rest1 rest2 + +let java_return_type_compare jr1 jr2 = + match jr1, jr2 with + | None, None -> 0 + | None, Some _ -> -1 + | Some _, None -> 1 + | Some jt1 , Some jt2 -> java_type_compare jt1 jt2 + +(** Compare java signatures. *) +let java_sig_compare js1 js2 = + string_compare js1.methodname js2.methodname + |> next java_type_list_compare js1.parameters js2.parameters + |> next java_type_compare js1.classname js2.classname + |> next java_return_type_compare js1.returntype js2.returntype + +(** Compare objc signatures. *) +let objc_sig_compare osig1 osig2 = + let n = string_compare osig1.objc_class osig2.objc_class in + if n <> 0 then n else string_compare osig1.objc_method osig2.objc_method + +(** Given a package.classname string, it looks for the latest dot and split the string in two (package, classname) *) +let split_classname package_classname = + string_split_character package_classname '.' + +let from_string (s: string) = C_CPP (s, None) + +let empty = C_CPP ("", None) + +let mangled_cpp (plain: string) (mangled: string) = C_CPP (plain, Some mangled) + +(** Create a static procedure name from a plain name and source file *) +let mangled_static (plain: string) (source_file: DB.source_file) = + let mangled = + if !Config.long_static_proc_names then Some (DB.source_file_encoding source_file) + else None in STATIC (plain, mangled) + +(** Creates a java procname, given classname, return type, method name and its parameters *) +let mangled_java class_name ret_type method_name params = + JAVA { + classname = class_name; + returntype = ret_type; + methodname = method_name; + parameters = params + } + +(** Create an objc procedure name from a class_name and method_name. *) +let mangled_objc objc_class objc_method = + OBJC { + objc_class = objc_class; + objc_method = objc_method; + } + +(** Create an objc procedure name from a class_name and method_name. *) +let mangled_objc_block name = + OBJC_BLOCK name + +let is_java = function + | JAVA _ -> true + | _ -> false + +let is_objc = function + | OBJC _ -> true + | _ -> false + +(** Replace package and classname of a java procname. *) +let java_replace_class p package_classname = + match p with + | JAVA j -> JAVA { j with classname = (split_classname package_classname) } + | _ -> assert false + +(** Replace the class name of an objc procedure name. *) +let objc_replace_class t objc_class = + match t with + | OBJC osig -> OBJC { osig with objc_class = objc_class } + | _ -> assert false + +(** Return the package.classname of a java procname. *) +let java_get_class = function + | JAVA j -> java_type_to_string j.classname VERBOSE + | _ -> assert false + +(** Return path components of a java class name *) +let java_get_class_components proc_name = + Str.split (Str.regexp (Str.quote ".")) (java_get_class proc_name) + +(** Return the class name of a java procedure name. *) +let java_get_simple_class proc_name = + list_hd (list_rev (java_get_class_components proc_name)) + +(** Return the method of a java procname. *) +let java_get_method = function + | JAVA j -> j.methodname + | _ -> assert false + +(** Replace the method of a java procname. *) +let java_replace_method p mname = match p with + | JAVA p -> JAVA { p with methodname = mname } + | _ -> assert false + +(** Replace the return type of a java procname. *) +let java_replace_return_type p ret_type = match p with + | JAVA p -> JAVA { p with returntype = Some ret_type } + | _ -> assert false + +(** Return the method of a objc procname. *) +let clang_get_method = function + | OBJC name -> name.objc_method + | C_CPP (name, _) -> name + | OBJC_BLOCK name -> name + | _ -> assert false + +(** Replace the method name of an existing java procname. *) +let java_replace_method p methodname = match p with + | JAVA j -> JAVA { j with methodname = methodname } + | _ -> assert false + +(** Return the return type of a java procname. *) +let java_get_return_type = function + | JAVA j -> java_return_type_to_string j VERBOSE + | _ -> assert false + +(** Return the parameters of a java procname. *) +let java_get_parameters = function + | JAVA j -> list_map (fun param -> java_type_to_string param VERBOSE) j.parameters + | _ -> assert false + +(** Prints a string of a java procname with the given level of verbosity *) +let java_to_string ?withclass: (wc = false) j verbosity = + match verbosity with + | VERBOSE | NON_VERBOSE -> + (* if verbose, then package.class.method(params): rtype, + else rtype package.class.method(params) + verbose is used for example to create unique filenames, non_verbose to create reports *) + let return_type = java_return_type_to_string j verbosity in + let params = java_param_list_to_string j.parameters verbosity in + let classname = java_type_to_string j.classname verbosity in + let separator = + match j.returntype, verbosity with + | (None, _) -> "" + | (Some _, VERBOSE) -> ":" + | _ -> " " in + let output = classname ^ "." ^ j.methodname ^ "(" ^ params ^ ")" in + if verbosity = VERBOSE then output ^ separator ^ return_type + else return_type ^ separator ^ output + | SIMPLE -> (* methodname(...) or without ... if there are no parameters *) + let cls_prefix = + if wc then + java_type_to_string j.classname verbosity ^ "." + else "" in + let params = + match j.parameters with + | [] -> "" + | _ -> "..." in + let methodname = if j.methodname = "" then java_get_simple_class (JAVA j) else j.methodname in + cls_prefix ^ methodname ^ "(" ^ params ^ ")" + +(** Check if the class name is for an anonymous inner class. *) +let is_anonymous_inner_class_name class_name = + match string_split_character class_name '$' with + | Some _, s -> + let is_int = + try ignore (int_of_string (String.trim s)); true with Failure _ -> false in + is_int + | None, _ -> false + +(** Check if the procedure belongs to an anonymous inner class. *) +let java_is_anonymous_inner_class = function + | JAVA j -> is_anonymous_inner_class_name (snd j.classname) + | _ -> false + +(** Check if the last parameter is a hidden inner class, and remove it if present. +This is used in private constructors, where a proxy constructor is generated +with an extra parameter and calls the normal constructor. *) +let java_remove_hidden_inner_class_parameter = function + | JAVA js -> + (match list_rev js.parameters with + | (so, s) :: par' -> + if is_anonymous_inner_class_name s + then Some (JAVA { js with parameters = list_rev par'}) + else None + | [] -> None) + | _ -> None + +(** Check if the procedure name is an anonymous inner class constructor. *) +let java_is_anonymous_inner_class_constructor = function + | JAVA js -> + let _, name = js.classname in + is_anonymous_inner_class_name name + | _ -> false + +(** Check if the procedure name is an acess method (e.g. access$100 used to +access private members from a nested class. *) +let java_is_access_method = function + | JAVA js -> + (match string_split_character js.methodname '$' with + | Some "access", s -> + let is_int = + try ignore (int_of_string s); true with Failure _ -> false in + is_int + | _ -> false) + | _ -> false + +(** Check if the proc name has the type of a java vararg. +Note: currently only checks that the last argument has type Object[]. *) +let java_is_vararg = function + | JAVA js -> + begin + match (list_rev js.parameters) with + | (_,"java.lang.Object[]") :: _ -> true + | _ -> false + end + | _ -> false + +(** [is_constructor pname] returns true if [pname] is a constructor *) +let is_constructor = function + | JAVA js -> js.methodname = "" + | OBJC name -> Utils.string_is_prefix "init" name.objc_method + (* TODO: Add cases for ObjC and C++ *) + | _ -> false + +(** [is_class_initializer pname] returns true if [pname] is a class initializer *) +let is_class_initializer = function + | JAVA js -> js.methodname = "" + | _ -> false + +(** [is_infer_undefined pn] returns true if [pn] is a special Infer undefined proc *) +let is_infer_undefined pn = match pn with + | JAVA j -> + let regexp = Str.regexp "com.facebook.infer.models.InferUndefined" in + Str.string_match regexp (java_get_class pn) 0 + | _ -> + (* TODO: add cases for obj-c, c, c++ *) + false + +(** to_string for C_CPP and STATIC types *) +let to_readable_string (c1, c2) verbose = + let plain = c1 in + if verbose then + match c2 with + | None -> plain + | Some s -> plain ^ "{" ^ s ^ "}" + else + plain + +let objc_to_string osig detail_level = + match detail_level with + | SIMPLE -> + osig.objc_method + | VERBOSE | NON_VERBOSE -> + osig.objc_class ^ "_" ^ osig.objc_method + +(** Very verbose representation of an existing Procname.t *) +let to_unique_id pn = + match pn with + | JAVA j -> java_to_string j VERBOSE + | C_CPP (c1, c2) -> to_readable_string (c1, c2) true + | STATIC (s1, s2) -> to_readable_string (s1, s2) true + | OBJC osig -> objc_to_string osig VERBOSE + | OBJC_BLOCK name -> name + +(** Convert a proc name to a string for the user to see *) +let to_string p = + match p with + | JAVA j -> (java_to_string j NON_VERBOSE) + | C_CPP (c1, c2) | STATIC (c1, c2) -> + to_readable_string (c1, c2) false + | OBJC osig -> objc_to_string osig NON_VERBOSE + | OBJC_BLOCK name -> name + +(** Convenient representation of a procname for external tools (e.g. eclipse plugin) *) +let to_simplified_string ?withclass: (wc = false) p = + match p with + | JAVA j -> (java_to_string ~withclass: wc j SIMPLE) + | C_CPP (c1, c2) | STATIC (c1, c2) -> + to_readable_string (c1, c2) false ^ "()" + | OBJC osig -> objc_to_string osig SIMPLE + | OBJC_BLOCK name -> "block" + +(** Convert a proc name to a filename *) +let to_filename (pn : proc_name) = + let cutoff_length = 100 in (** if longer than cutoff, cut it and append CRC *) + let name = to_unique_id pn in + if String.length name <= cutoff_length then name + else + let pname_first_100 = String.sub name 0 cutoff_length in + let crc_str = CRC.crc16 name in + pname_first_100 ^ crc_str + +(** Pretty print a proc name *) +let pp f pn = + F.fprintf f "%s" (to_string pn) + +(** Compare function for Procname.t types *) +(* These rules create an ordered set of procnames grouped with the following priority (lowest to highest): *) +(* JAVA, C_CPP, STATIC, OBJC *) +let compare pn1 pn2 = match pn1, pn2 with + | JAVA j1, JAVA j2 -> java_sig_compare j1 j2 + | JAVA _, _ -> -1 + | _, JAVA _ -> 1 + | C_CPP (c1, c2), C_CPP (c3, c4) -> (* Compare C_CPP types *) + let n = string_compare c1 c3 in + if n <> 0 then n else mangled_compare c2 c4 + | C_CPP _, _ -> -1 + | _, C_CPP _ -> 1 + | STATIC (c1, c2), STATIC (c3, c4) -> (* Compare STATIC types *) + let n = string_compare c1 c3 in + if n <> 0 then n else mangled_compare c2 c4 + | STATIC _, _ -> -1 + | _, STATIC _ -> 1 + | OBJC_BLOCK s1, OBJC_BLOCK s2 -> (* Compare OBJC_BLOCK types *) + string_compare s1 s2 + | OBJC_BLOCK _, _ -> -1 + | _, OBJC_BLOCK _ -> 1 + | OBJC osig1, OBJC osig2 -> objc_sig_compare osig1 osig2 + +let equal pn1 pn2 = + compare pn1 pn2 = 0 + +(** hash function for procname *) +let hash_pname = Hashtbl.hash + +module Hash = + Hashtbl.Make(struct + type t = proc_name + let equal = equal + let hash = hash_pname + end) + +module Map = Map.Make (struct + type t = proc_name + let compare = compare end) + +module Set = Set.Make(struct + type t = proc_name + let compare = compare + end) + +(** Pretty print a set of proc names *) +let pp_set fmt set = + Set.iter (fun pname -> F.fprintf fmt "%a " pp pname) set diff --git a/infer/src/backend/procname.mli b/infer/src/backend/procname.mli new file mode 100644 index 000000000..0e395c687 --- /dev/null +++ b/infer/src/backend/procname.mli @@ -0,0 +1,141 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Module for Procedure Names *) + +open Utils + +(** Type of procedure names *) +type t + +type java_type = string option * string + +(** Comparison for proc names *) +val compare : t -> t -> int + +(** Equality for proc names *) +val equal : t -> t -> bool + +(** Convert a string to a proc name *) +val from_string : string -> t + +(** Create a C++ procedure name from plain and mangled name *) +val mangled_cpp : string -> string -> t + +(** Create a static procedure name from a plain name and source file *) +val mangled_static : string -> DB.source_file -> t + +(** Create a Java procedure name from its class_name method_name args_type_name return_type_name *) +val mangled_java : java_type -> java_type option -> string -> java_type list -> t + +(** Create an objc procedure name from a class_name and method_name. *) +val mangled_objc : string -> string -> t + +(** Create an objc block name. *) +val mangled_objc_block : string -> t + +(** Return true if this is a Java procedure name *) +val is_java : t -> bool + +(** Return true if this is an Objective-C procedure name *) +val is_objc : t -> bool + +(** Replace package and classname of a java procname. *) +val java_replace_class : t -> string -> t + +(** Replace the method of a java procname. *) +val java_replace_method : t -> string -> t + +(** Replace the method of a java procname. *) +val java_replace_return_type : t -> java_type -> t + +(** Replace the class name of an Objective-C procedure name. *) +val objc_replace_class : t -> string -> t + +(** Return the class name of a java procedure name. *) +val java_get_class : t -> string + +(** Return the simple class name of a java procedure name. *) +val java_get_simple_class : t -> string + +(** Return the method name of a java procedure name. *) +val java_get_method : t -> string + +(** Return the method name of a objc procedure name. *) +val clang_get_method : t -> string + +(** Replace the method name of an existing java procname. *) +val java_replace_method : t -> string -> t + +(** Return the return type of a java procedure name. *) +val java_get_return_type : t -> string + +(** Return the parameters of a java procedure name. *) +val java_get_parameters : t -> string list + +(** Check if the last parameter is a hidden inner class, and remove it if present. +This is used in private constructors, where a proxy constructor is generated +with an extra parameter and calls the normal constructor. *) +val java_remove_hidden_inner_class_parameter : t -> t option + +(** Check if a class string is an anoynmous inner class name *) +val is_anonymous_inner_class_name : string -> bool + +(** [is_constructor pname] returns true if [pname] is a constructor *) +val is_constructor : t -> bool + +(** [is_class_initializer pname] returns true if [pname] is a class initializer *) +val is_class_initializer : t -> bool + +(** [is_infer_undefined pn] returns true if [pn] is a special Infer undefined proc *) +val is_infer_undefined : t -> bool + +(** Check if the procedure belongs to an anonymous inner class. *) +val java_is_anonymous_inner_class : t -> bool + +(** Check if the procedure name is an anonymous inner class constructor. *) +val java_is_anonymous_inner_class_constructor : t -> bool + +(** Check if the procedure name is an acess method (e.g. access$100 used to +access private members from a nested class. *) +val java_is_access_method : t -> bool + +(** Check if the proc name has the type of a java vararg. +Note: currently only checks that the last argument has type Object[]. *) +val java_is_vararg : t -> bool + +(** Convert a proc name to a string for the user to see *) +val to_string : t -> string + +(** Convert a proc name into a easy string for the user to see in an IDE *) +val to_simplified_string : ?withclass: bool -> t -> string + +(** Convert a proc name into a unique identifier *) +val to_unique_id : t -> string + +(** Convert a proc name to a filename *) +val to_filename : t -> string + +(** empty proc name *) +val empty : t + +(** Pretty print a proc name *) +val pp : Format.formatter -> t -> unit + +(** hash function for procname *) +val hash_pname : t -> int + +(** hash tables with proc names as keys *) +module Hash : Hashtbl.S with type key = t + +(** maps from proc names *) +module Map : Map.S with type key = t + +(** sets of proc names *) +module Set : Set.S with type elt = t + +(** Pretty print a set of proc names *) +val pp_set : Format.formatter -> Set.t -> unit diff --git a/infer/src/backend/prop.ml b/infer/src/backend/prop.ml new file mode 100644 index 000000000..cd95d3b49 --- /dev/null +++ b/infer/src/backend/prop.ml @@ -0,0 +1,2862 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Functions for Propositions (i.e., Symbolic Heaps) *) + +module L = Logging +module F = Format +open Utils + +(** type to describe different strategies for initializing fields of a structure. [No_init] does not +initialize any fields of the struct. [Fld_init] initializes the fields of the struct with fresh +variables (C) or default values (Java). *) +type struct_init_mode = + | No_init + | Fld_init + +let cil_exp_compare (e1: Sil.exp) (e2: Sil.exp) = Pervasives.compare e1 e2 + +let unSome = function + | Some x -> x + | _ -> assert false + +type normal = Normal (** kind for normal props, i.e. normalized *) +type exposed = Exposed (** kind for exposed props *) + +(** A proposition. The following invariants are mantained. [sub] is of +the form id1 = e1 ... idn = en where: the id's are distinct and do not +occur in the e's nor in [pi] or [sigma]; the id's are in sorted +order; the id's are not existentials; if idn = yn (for yn not +existential) then idn < yn in the order on ident's. [pi] is sorted +and normalized, and does not contain x = e. [sigma] is sorted and +normalized. *) +type 'a t = + { sigma: Sil.hpred list; + sub: Sil.subst; + pi: Sil.atom list; + foot_sigma : Sil.hpred list; + foot_pi: Sil.atom list } + +exception Cannot_star of ml_location + +(** Pure proposition. *) +type pure_prop = Sil.subst * Sil.atom list + +(** {2 Basic Functions for Propositions} *) + +(** {1 Functions for Comparison} *) + +(** Comparison between lists of equalities and disequalities. Lexicographical order. *) +let rec pi_compare pi1 pi2 = + if pi1 == pi2 then 0 + else match (pi1, pi2) with + | ([],[]) -> 0 + | ([], _:: _) -> - 1 + | (_:: _,[]) -> 1 + | (a1:: pi1', a2:: pi2') -> + let n = Sil.atom_compare a1 a2 in + if n <> 0 then n + else pi_compare pi1' pi2' + +let pi_equal pi1 pi2 = + pi_compare pi1 pi2 = 0 + +(** Comparsion between lists of heap predicates. Lexicographical order. *) +let rec sigma_compare sigma1 sigma2 = + if sigma1 == sigma2 then 0 + else match (sigma1, sigma2) with + | ([],[]) -> 0 + | ([], _:: _) -> - 1 + | (_:: _,[]) -> 1 + | (h1:: sigma1', h2:: sigma2') -> + let n = Sil.hpred_compare h1 h2 in + if n <> 0 then n + else sigma_compare sigma1' sigma2' + +let sigma_equal sigma1 sigma2 = + sigma_compare sigma1 sigma2 = 0 + +(** Comparison between propositions. Lexicographical order. *) +let prop_compare p1 p2 = + let n = sigma_compare p1.sigma p2.sigma in + if n <> 0 then n else + let n = Sil.sub_compare p1.sub p2.sub in + if n <> 0 then n else + let n = pi_compare p1.pi p2.pi in + if n <> 0 then n else + let n = sigma_compare p1.foot_sigma p2.foot_sigma in + if n <> 0 then n else pi_compare p1.foot_pi p2.foot_pi + +(** Check the equality of two propositions *) +let prop_equal p1 p2 = + (prop_compare p1 p2 = 0) + +(** {1 Functions for Pretty Printing} *) + +(** Pretty print a footprint. *) +let pp_footprint _pe f fp = + let pe = { _pe with pe_cmap_norm = _pe.pe_cmap_foot } in + let pp_pi f () = + if fp.foot_pi != [] then + F.fprintf f "%a ;@\n" (pp_semicolon_seq_oneline pe (Sil.pp_atom pe)) fp.foot_pi in + if fp.foot_pi != [] || fp.foot_sigma != [] then + F.fprintf f "@\n[footprint@\n @[%a%a@] ]" pp_pi () (pp_semicolon_seq pe (Sil.pp_hpred pe)) fp.foot_sigma + +let pp_lseg_kind f = function + | Sil.Lseg_NE -> F.fprintf f "ne" + | Sil.Lseg_PE -> F.fprintf f "" + +let pp_texp_simple pe = match pe.pe_opt with + | PP_SIM_DEFAULT -> Sil.pp_texp pe + | PP_SIM_WITH_TYP -> Sil.pp_texp_full pe + +(** Pretty print a pointsto representing a stack variable as an equality *) +let pp_hpred_stackvar pe0 env f hpred = + let pe, changed = Sil.color_pre_wrapper pe0 f hpred in + begin match hpred with + | Sil.Hpointsto (Sil.Lvar pvar, se, te) -> + let pe' = match se with + | Sil.Eexp (Sil.Var id, inst) when not (Sil.pvar_is_global pvar) -> + { pe with pe_obj_sub = None } (* dont use obj sub on the var defining it *) + | _ -> pe in + (match pe'.pe_kind with + | PP_TEXT | PP_HTML -> + F.fprintf f "%a = %a:%a" (Sil.pp_pvar_value pe') pvar (Sil.pp_sexp pe') se (pp_texp_simple pe') te + | PP_LATEX -> + F.fprintf f "%a{=}%a" (Sil.pp_pvar_value pe') pvar (Sil.pp_sexp pe') se) + | Sil.Hpointsto _ | Sil.Hlseg _ | Sil.Hdllseg _ -> assert false (* should not happen *) + end; + Sil.color_post_wrapper changed pe0 f + +(** Pretty print a substitution. *) +let pp_sub pe f sub = + let pi_sub = list_map (fun (id, e) -> Sil.Aeq(Sil.Var id, e)) (Sil.sub_to_list sub) in + (pp_semicolon_seq_oneline pe (Sil.pp_atom pe)) f pi_sub + +(** Dump a substitution. *) +let d_sub (sub: Sil.subst) = L.add_print_action (L.PTsub, Obj.repr sub) + +let pp_sub_entry pe0 f entry = + let pe, changed = Sil.color_pre_wrapper pe0 f entry in + let (x, e) = entry in + begin + match pe.pe_kind with + | PP_TEXT | PP_HTML -> + F.fprintf f "%a = %a" (Ident.pp pe) x (Sil.pp_exp pe) e + | PP_LATEX -> + F.fprintf f "%a{=}%a" (Ident.pp pe) x (Sil.pp_exp pe) e + end; + Sil.color_post_wrapper changed pe0 f + +(** Pretty print a substitution as a list of (ident,exp) pairs *) +let pp_subl pe = + if !Config.smt_output then pp_semicolon_seq pe (pp_sub_entry pe) + else pp_semicolon_seq_oneline pe (pp_sub_entry pe) + +(** Pretty print a pi. *) +let pp_pi pe = + if !Config.smt_output then pp_semicolon_seq pe (Sil.pp_atom pe) + else pp_semicolon_seq_oneline pe (Sil.pp_atom pe) + +(** Dump a pi. *) +let d_pi (pi: Sil.atom list) = L.add_print_action (L.PTpi, Obj.repr pi) + +(** Pretty print a sigma. *) +let pp_sigma pe = + pp_semicolon_seq pe (Sil.pp_hpred pe) + +(** Split sigma into stack and nonstack parts. +The boolean indicates whether the stack should only include local variales. *) +let sigma_get_stack_nonstack only_local_vars sigma = + let hpred_is_stack_var = function + | Sil.Hpointsto (Sil.Lvar pvar, _, _) -> not only_local_vars || Sil.pvar_is_local pvar + | _ -> false in + list_partition hpred_is_stack_var sigma + +(** Pretty print a sigma in simple mode. *) +let pp_sigma_simple pe env fmt sigma = + let sigma_stack, sigma_nonstack = sigma_get_stack_nonstack false sigma in + let pp_stack fmt _sg = + let sg = list_sort Sil.hpred_compare _sg in + if sg != [] then Format.fprintf fmt "%a" (pp_semicolon_seq pe (pp_hpred_stackvar pe env)) sg in + let pp_nl fmt doit = if doit then + (match pe.pe_kind with + | PP_TEXT | PP_HTML -> Format.fprintf fmt " ;@\n" + | PP_LATEX -> Format.fprintf fmt " ; \\\\@\n") in + let pp_nonstack fmt = pp_semicolon_seq pe (Sil.pp_hpred_env pe (Some env)) fmt in + if sigma_stack != [] || sigma_nonstack != [] then + Format.fprintf fmt "%a%a%a" pp_stack sigma_stack pp_nl (sigma_stack != [] && sigma_nonstack != []) pp_nonstack sigma_nonstack + +(** Dump a sigma. *) +let d_sigma (sigma: Sil.hpred list) = L.add_print_action (L.PTsigma, Obj.repr sigma) + +(** Dump a pi and a sigma *) +let d_pi_sigma pi sigma = + let d_separator () = if pi != [] && sigma != [] then L.d_strln " *" in + d_pi pi; d_separator (); d_sigma sigma + +(** Return the sub part of [prop]. *) +let get_sub (p: 'a t) : Sil.subst = p.sub + +(** Return the pi part of [prop]. *) +let get_pi (p: 'a t) : Sil.atom list = p.pi + +(** Return the pure part of [prop]. *) +let get_pure (p: 'a t) : Sil.atom list = + list_map (fun (id1, e2) -> Sil.Aeq (Sil.Var id1, e2)) (Sil.sub_to_list p.sub) @ p.pi + +(** Print existential quantification *) +let pp_evars pe f evars = + if evars != [] + then match pe.pe_kind with + | PP_TEXT | PP_HTML -> + F.fprintf f "exists [%a]. " (pp_comma_seq (Ident.pp pe)) evars + | PP_LATEX -> + F.fprintf f "\\exists %a. " (pp_comma_seq (Ident.pp pe)) evars + +(** Print an hpara in simple mode *) +let pp_hpara_simple _pe env n f pred = + let pe = pe_reset_obj_sub _pe in (* no free vars: disable object substitution *) + match pe.pe_kind with + | PP_TEXT | PP_HTML -> + F.fprintf f "P%d = %a%a" n (pp_evars pe) pred.Sil.evars (pp_semicolon_seq pe (Sil.pp_hpred_env pe (Some env))) pred.Sil.body + | PP_LATEX -> + F.fprintf f "P_{%d} = %a%a\\\\" n (pp_evars pe) pred.Sil.evars (pp_semicolon_seq pe (Sil.pp_hpred_env pe (Some env))) pred.Sil.body + +(** Print an hpara_dll in simple mode *) +let pp_hpara_dll_simple _pe env n f pred = + let pe = pe_reset_obj_sub _pe in (* no free vars: disable object substitution *) + match pe.pe_kind with + | PP_TEXT | PP_HTML -> + F.fprintf f "P%d = %a%a" n (pp_evars pe) pred.Sil.evars_dll (pp_semicolon_seq pe (Sil.pp_hpred_env pe (Some env))) pred.Sil.body_dll + | PP_LATEX -> + F.fprintf f "P_{%d} = %a%a" n (pp_evars pe) pred.Sil.evars_dll (pp_semicolon_seq pe (Sil.pp_hpred_env pe (Some env))) pred.Sil.body_dll + +(** Create an environment mapping (ident) expressions to the program variables containing them *) +let create_pvar_env (sigma: Sil.hpred list) : (Sil.exp -> Sil.exp) = + let env = ref [] in + let filter = function + | Sil.Hpointsto (Sil.Lvar pvar, Sil.Eexp (Sil.Var v, inst), _) -> + if not (Sil.pvar_is_global pvar) then env := (Sil.Var v, Sil.Lvar pvar) :: !env + | _ -> () in + list_iter filter sigma; + let find e = + try + snd (list_find (fun (e1, e2) -> Sil.exp_equal e1 e) !env) + with Not_found -> e in + find + +(** Update the object substitution given the stack variables in the prop *) +let prop_update_obj_sub pe prop = + if !Config.pp_simple + then pe_set_obj_sub pe (create_pvar_env prop.sigma) + else pe + +(** Pretty print a footprint in simple mode. *) +let pp_footprint_simple _pe env f fp = + let pe = { _pe with pe_cmap_norm = _pe.pe_cmap_foot } in + let pp_pure f pi = + if pi != [] then + F.fprintf f "%a *@\n" (pp_pi pe) pi in + if fp.foot_pi != [] || fp.foot_sigma != [] then + F.fprintf f "@\n[footprint@\n @[%a%a@] ]" + pp_pure fp.foot_pi + (pp_sigma_simple pe env) fp.foot_sigma + +(** Create a predicate environment for a prop *) +let prop_pred_env prop = + let env = Sil.Predicates.empty_env () in + list_iter (Sil.Predicates.process_hpred env) prop.sigma; + list_iter (Sil.Predicates.process_hpred env) prop.foot_sigma; + env + +(** Pretty print a proposition. *) +let pp_prop pe0 f prop = + let pe = prop_update_obj_sub pe0 prop in + let latex = pe.pe_kind == PP_LATEX in + let do_print f () = + let subl = Sil.sub_to_list (get_sub prop) in (* since prop diff is based on physical equality, we need to extract the sub verbatim *) + let pi = get_pi prop in + let pp_pure f () = + if subl != [] then F.fprintf f "%a ;@\n" (pp_subl pe) subl; + if pi != [] then F.fprintf f "%a ;@\n" (pp_pi pe) pi in + if !Config.pp_simple || latex then + begin + let env = prop_pred_env prop in + let iter_f n hpara = F.fprintf f "@,@[%a@]" (pp_hpara_simple pe env n) hpara in + let iter_f_dll n hpara_dll = F.fprintf f "@,@[%a@]" (pp_hpara_dll_simple pe env n) hpara_dll in + let pp_predicates fmt () = + if Sil.Predicates.is_empty env + then () + else if latex then + begin + F.fprintf f "@\n\\\\\\textsf{where }"; + Sil.Predicates.iter env iter_f iter_f_dll + end + else + begin + F.fprintf f "@,where"; + Sil.Predicates.iter env iter_f iter_f_dll + end in + F.fprintf f "%a%a%a%a" + pp_pure () (pp_sigma_simple pe env) prop.sigma + (pp_footprint_simple pe env) prop pp_predicates () + end + else + F.fprintf f "%a%a%a" pp_pure () (pp_sigma pe) prop.sigma (pp_footprint pe) prop in + if !Config.forcing_delayed_prints then (** print in html mode *) + F.fprintf f "%a%a%a" Io_infer.Html.pp_start_color Blue do_print () Io_infer.Html.pp_end_color () + else + do_print f () (** print in text mode *) + +let pp_prop_with_typ pe f p = pp_prop { pe with pe_opt = PP_SIM_WITH_TYP } f p + +(** Dump a proposition. *) +let d_prop (prop: 'a t) = L.add_print_action (L.PTprop, Obj.repr prop) + +(** Dump a proposition. *) +let d_prop_with_typ (prop: 'a t) = L.add_print_action (L.PTprop_with_typ, Obj.repr prop) + +(** Print a list of propositions, prepending each one with the given string *) +let pp_proplist_with_typ pe f plist = + let rec pp_seq_newline f = function + | [] -> () + | [x] -> F.fprintf f "@[%a@]" (pp_prop_with_typ pe) x + | x:: l -> F.fprintf f "@[%a@]@\n(||)@\n%a" (pp_prop_with_typ pe) x pp_seq_newline l in + F.fprintf f "@[%a@]" pp_seq_newline plist + +(** dump a proplist *) +let d_proplist_with_typ (pl: 'a t list) = + L.add_print_action (L.PTprop_list_with_typ, Obj.repr pl) + +(** {1 Functions for computing free non-program variables} *) + +let pi_fav_add fav pi = + list_iter (Sil.atom_fav_add fav) pi + +let pi_fav = + Sil.fav_imperative_to_functional pi_fav_add + +let sigma_fav_add fav sigma = + list_iter (Sil.hpred_fav_add fav) sigma + +let sigma_fav = + Sil.fav_imperative_to_functional sigma_fav_add + +let prop_footprint_fav_add fav prop = + sigma_fav_add fav prop.foot_sigma; + pi_fav_add fav prop.foot_pi + +(** Find fav of the footprint part of the prop *) +let prop_footprint_fav prop = + Sil.fav_imperative_to_functional prop_footprint_fav_add prop + +let prop_fav_add fav prop = + sigma_fav_add fav prop.sigma; + sigma_fav_add fav prop.foot_sigma; + Sil.sub_fav_add fav prop.sub; + pi_fav_add fav prop.pi; + pi_fav_add fav prop.foot_pi + +let prop_fav p = + Sil.fav_imperative_to_functional prop_fav_add p + +(** free vars of the prop, excluding the pure part *) +let prop_fav_nonpure_add fav prop = + sigma_fav_add fav prop.sigma; + sigma_fav_add fav prop.foot_sigma + +(** free vars, except pi and sub, of current and footprint parts *) +let prop_fav_nonpure = + Sil.fav_imperative_to_functional prop_fav_nonpure_add + +let rec hpred_fav_in_pvars_add fav = function + | Sil.Hpointsto (Sil.Lvar _, sexp, _) -> Sil.strexp_fav_add fav sexp + | Sil.Hpointsto _ | Sil.Hlseg _ | Sil.Hdllseg _ -> () + +let sigma_fav_in_pvars_add fav sigma = + list_iter (hpred_fav_in_pvars_add fav) sigma + +let sigma_fpv sigma = + list_flatten (list_map Sil.hpred_fpv sigma) + +let pi_fpv pi = + list_flatten (list_map Sil.atom_fpv pi) + +let prop_fpv prop = + (Sil.sub_fpv prop.sub) @ + (pi_fpv prop.pi) @ + (pi_fpv prop.foot_pi) @ + (sigma_fpv prop.foot_sigma) @ + (sigma_fpv prop.sigma) + +(** {1 Functions for computing free or bound non-program variables} *) + +let pi_av_add fav pi = + list_iter (Sil.atom_av_add fav) pi + +let sigma_av_add fav sigma = + list_iter (Sil.hpred_av_add fav) sigma + +let prop_av_add fav prop = + Sil.sub_av_add fav prop.sub; + pi_av_add fav prop.pi; + sigma_av_add fav prop.sigma; + pi_av_add fav prop.foot_pi; + sigma_av_add fav prop.foot_sigma + +let prop_av = + Sil.fav_imperative_to_functional prop_av_add + +(** {2 Functions for Subsitition} *) + +let pi_sub (subst: Sil.subst) pi = + let f = Sil.atom_sub subst in + list_map f pi + +let sigma_sub subst sigma = + let f = Sil.hpred_sub subst in + list_map f sigma + +(** {2 Functions for normalization} *) + +(** This function assumes that if (x,Sil.Var(y)) in sub, then compare x y = 1 *) +let sub_normalize sub = + let f (id, e) = (not (Ident.is_primed id)) && (not (Sil.ident_in_exp id e)) in + let sub' = Sil.sub_filter_pair f sub in + if Sil.sub_equal sub sub' then sub else sub' + +let (--) = Sil.Int.sub +let (++) = Sil.Int.add + +let iszero_int_float = function + | Sil.Cint i -> Sil.Int.iszero i + | Sil.Cfloat 0.0 -> true + | _ -> false + +let isone_int_float = function + | Sil.Cint i -> Sil.Int.isone i + | Sil.Cfloat 1.0 -> true + | _ -> false + +let isminusone_int_float = function + | Sil.Cint i -> Sil.Int.isminusone i + | Sil.Cfloat (-1.0) -> true + | _ -> false + +let sym_eval abs e = + let rec eval e = + (* L.d_str " ["; Sil.d_exp e; L.d_str"] "; *) + match e with + | Sil.Var _ -> + e + | Sil.Const (Sil.Ctuple el) -> + Sil.Const (Sil.Ctuple (list_map eval el)) + | Sil.Const _ -> + e + | Sil.Sizeof (Sil.Tarray (Sil.Tint ik, e), _) when Sil.ikind_is_char ik && !Sil.curr_language <> Sil.Java -> + eval e + | Sil.Sizeof _ -> + e + | Sil.Cast (_, e1) -> + eval e1 + | Sil.UnOp (Sil.LNot, e1, topt) -> + begin + match eval e1 with + | Sil.Const (Sil.Cint i) when Sil.Int.iszero i -> + Sil.exp_one + | Sil.Const (Sil.Cint _) -> + Sil.exp_zero + | Sil.UnOp(Sil.LNot, e1', _) -> + e1' + | e1' -> + if abs then Sil.exp_get_undefined false else Sil.UnOp(Sil.LNot, e1', topt) + end + | Sil.UnOp (Sil.Neg, e1, topt) -> + begin + match eval e1 with + | Sil.UnOp (Sil.Neg, e2', _) -> + e2' + | Sil.Const (Sil.Cint i) -> + Sil.exp_int (Sil.Int.neg i) + | Sil.Const (Sil.Cfloat v) -> + Sil.exp_float (-. v) + | Sil.Var id -> + Sil.UnOp (Sil.Neg, Sil.Var id, topt) + | e1' -> + if abs then Sil.exp_get_undefined false else Sil.UnOp (Sil.Neg, e1', topt) + end + | Sil.UnOp (Sil.BNot, e1, topt) -> + begin + match eval e1 with + | Sil.UnOp(Sil.BNot, e2', _) -> + e2' + | Sil.Const (Sil.Cint i) -> + Sil.exp_int (Sil.Int.lognot i) + | e1' -> + if abs then Sil.exp_get_undefined false else Sil.UnOp (Sil.BNot, e1', topt) + end + | Sil.BinOp (Sil.Le, e1, e2) -> + begin + match eval e1, eval e2 with + | Sil.Const (Sil.Cint n), Sil.Const (Sil.Cint m) -> + Sil.exp_bool (Sil.Int.leq n m) + | Sil.Const (Sil.Cfloat v), Sil.Const (Sil.Cfloat w) -> + Sil.exp_bool (v <= w) + | Sil.BinOp (Sil.PlusA, e3, Sil.Const (Sil.Cint n)), Sil.Const (Sil.Cint m) -> + Sil.BinOp (Sil.Le, e3, Sil.exp_int (m -- n)) + | e1', e2' -> + Sil.exp_le e1' e2' + end + | Sil.BinOp (Sil.Lt, e1, e2) -> + begin + match eval e1, eval e2 with + | Sil.Const (Sil.Cint n), Sil.Const (Sil.Cint m) -> + Sil.exp_bool (Sil.Int.lt n m) + | Sil.Const (Sil.Cfloat v), Sil.Const (Sil.Cfloat w) -> + Sil.exp_bool (v < w) + | Sil.Const (Sil.Cint n), Sil.BinOp (Sil.MinusA, f1, f2) -> + Sil.BinOp(Sil.Le, Sil.BinOp (Sil.MinusA, f2, f1), Sil.exp_int (Sil.Int.minus_one -- n)) + | Sil.BinOp(Sil.MinusA, f1 , f2), Sil.Const(Sil.Cint n) -> + Sil.exp_le (Sil.BinOp(Sil.MinusA, f1 , f2)) (Sil.exp_int (n -- Sil.Int.one)) + | Sil.BinOp (Sil.PlusA, e3, Sil.Const (Sil.Cint n)), Sil.Const (Sil.Cint m) -> + Sil.BinOp (Sil.Lt, e3, Sil.exp_int (m -- n)) + | e1', e2' -> + Sil.exp_lt e1' e2' + end + | Sil.BinOp (Sil.Ge, e1, e2) -> + eval (Sil.exp_le e2 e1) + | Sil.BinOp (Sil.Gt, e1, e2) -> + eval (Sil.exp_lt e2 e1) + | Sil.BinOp (Sil.Eq, e1, e2) -> + begin + match eval e1, eval e2 with + | Sil.Const (Sil.Cint n), Sil.Const (Sil.Cint m) -> + Sil.exp_bool (Sil.Int.eq n m) + | Sil.Const (Sil.Cfloat v), Sil.Const (Sil.Cfloat w) -> + Sil.exp_bool (v = w) + | e1', e2' -> + Sil.exp_eq e1' e2' + end + | Sil.BinOp (Sil.Ne, e1, e2) -> + begin + match eval e1, eval e2 with + | Sil.Const (Sil.Cint n), Sil.Const (Sil.Cint m) -> + Sil.exp_bool (Sil.Int.neq n m) + | Sil.Const (Sil.Cfloat v), Sil.Const (Sil.Cfloat w) -> + Sil.exp_bool (v <> w) + | e1', e2' -> + Sil.exp_ne e1' e2' + end + | Sil.BinOp (Sil.LAnd, e1, e2) -> + let e1' = eval e1 in + let e2' = eval e2 in + begin match e1', e2' with + | Sil.Const (Sil.Cint i), _ when Sil.Int.iszero i -> + e1' + | Sil.Const (Sil.Cint _), _ -> + e2' + | _, Sil.Const (Sil.Cint i) when Sil.Int.iszero i -> + e2' + | _, Sil.Const (Sil.Cint _) -> + e1' + | _ -> + Sil.BinOp (Sil.LAnd, e1', e2') + end + | Sil.BinOp (Sil.LOr, e1, e2) -> + let e1' = eval e1 in + let e2' = eval e2 in + begin + match e1', e2' with + | Sil.Const (Sil.Cint i), _ when Sil.Int.iszero i -> + e2' + | Sil.Const (Sil.Cint _), _ -> + e1' + | _, Sil.Const (Sil.Cint i) when Sil.Int.iszero i -> + e1' + | _, Sil.Const (Sil.Cint _) -> + e2' + | _ -> + Sil.BinOp (Sil.LOr, e1', e2') + end + | Sil.BinOp(Sil.PlusPI, Sil.Lindex (ep, e1), e2) -> (* array access with pointer arithmetic *) + let e' = Sil.BinOp (Sil.PlusA, e1, e2) in + eval (Sil.Lindex (ep, e')) + | Sil.BinOp (Sil.PlusPI, (Sil.BinOp (Sil.PlusPI, e11, e12)), e2) -> (* take care of pattern ((ptr + off1) + off2) *) + (* progress: convert inner +I to +A *) + let e2' = Sil.BinOp (Sil.PlusA, e12, e2) in + eval (Sil.BinOp (Sil.PlusPI, e11, e2')) + | Sil.BinOp (Sil.PlusA, (Sil.Sizeof (Sil.Tstruct (ftal, sftal, csu, name_opt, supers, def_mthds, iann), st) as e1), e2) -> (* pattern for extensible structs + given a struct declatead as struct s { ... t arr[n] ... }, allocation pattern malloc(sizeof(struct s) + k * siezof(t)) + turn it into struct s { ... t arr[n + k] ... } *) + let e1' = eval e1 in + let e2' = eval e2 in + (match list_rev ftal, e2' with + (fname, Sil.Tarray(typ, size), _):: ltfa, Sil.BinOp(Sil.Mult, num_elem, Sil.Sizeof (texp, st)) when ftal != [] && Sil.typ_equal typ texp -> + let size' = Sil.BinOp(Sil.PlusA, size, num_elem) in + let ltfa' = (fname, Sil.Tarray(typ, size'), Sil.item_annotation_empty) :: ltfa in + Sil.Sizeof(Sil.Tstruct (list_rev ltfa', sftal, csu, name_opt, supers, def_mthds, iann), st) + | _ -> Sil.BinOp(Sil.PlusA, e1', e2')) + | Sil.BinOp (Sil.PlusA as oplus, e1, e2) + | Sil.BinOp (Sil.PlusPI as oplus, e1, e2) -> + let e1' = eval e1 in + let e2' = eval e2 in + let isPlusA = oplus = Sil.PlusA in + let ominus = if isPlusA then Sil.MinusA else Sil.MinusPI in + let (+++) x y = match y with + | Sil.Const (Sil.Cint i) when Sil.Int.iszero i -> x + | _ -> Sil.BinOp (oplus, x, y) in + let (---) x y = match y with + | Sil.Const (Sil.Cint i) when Sil.Int.iszero i -> x + | _ -> Sil.BinOp (ominus, x, y) in + begin + match e1', e2' with + | Sil.Const c, _ when iszero_int_float c -> + e2' + | _, Sil.Const c when iszero_int_float c -> + e1' + | Sil.Const (Sil.Cint n), Sil.Const (Sil.Cint m) -> + Sil.exp_int (n ++ m) + | Sil.Const (Sil.Cfloat v), Sil.Const (Sil.Cfloat w) -> + Sil.exp_float (v +. w) + | Sil.UnOp(Sil.Neg, f1, _), f2 + | f2, Sil.UnOp(Sil.Neg, f1, _) -> + Sil.BinOp (ominus, f2, f1) + | Sil.BinOp (Sil.PlusA, e, Sil.Const (Sil.Cint n1)), Sil.Const (Sil.Cint n2) + | Sil.BinOp (Sil.PlusPI, e, Sil.Const (Sil.Cint n1)), Sil.Const (Sil.Cint n2) + | Sil.Const (Sil.Cint n2), Sil.BinOp (Sil.PlusA, e, Sil.Const (Sil.Cint n1)) + | Sil.Const (Sil.Cint n2), Sil.BinOp (Sil.PlusPI, e, Sil.Const (Sil.Cint n1)) -> + e +++ (Sil.exp_int (n1 ++ n2)) + | Sil.BinOp (Sil.MinusA, Sil.Const (Sil.Cint n1), e), Sil.Const (Sil.Cint n2) + | Sil.Const (Sil.Cint n2), Sil.BinOp (Sil.MinusA, Sil.Const (Sil.Cint n1), e) -> + Sil.exp_int (n1 ++ n2) --- e + | Sil.BinOp (Sil.MinusA, e1, e2), e3 -> (* (e1-e2)+e3 --> e1 + (e3-e2) *) + (* progress: brings + to the outside *) + eval (e1 +++ (e3 --- e2)) + | _, Sil.Const _ -> + e1' +++ e2' + | Sil.Const _, _ -> + if isPlusA then e2' +++ e1' else e1' +++ e2' + | Sil.Var _, Sil.Var _ -> + e1' +++ e2' + | _ -> + if abs && isPlusA then Sil.exp_get_undefined false else + if abs && not isPlusA then e1' +++ (Sil.exp_get_undefined false) + else e1' +++ e2' + end + | Sil.BinOp (Sil.MinusA as ominus, e1, e2) + | Sil.BinOp (Sil.MinusPI as ominus, e1, e2) -> + let e1' = eval e1 in + let e2' = eval e2 in + let isMinusA = ominus = Sil.MinusA in + let oplus = if isMinusA then Sil.PlusA else Sil.PlusPI in + let (+++) x y = Sil.BinOp (oplus, x, y) in + let (---) x y = Sil.BinOp (ominus, x, y) in + if Sil.exp_equal e1' e2' then Sil.exp_zero + else begin + match e1', e2' with + | Sil.Const c, _ when iszero_int_float c -> + eval (Sil.UnOp(Sil.Neg, e2', None)) + | _, Sil.Const c when iszero_int_float c -> + e1' + | Sil.Const (Sil.Cint n), Sil.Const (Sil.Cint m) -> + Sil.exp_int (n -- m) + | Sil.Const (Sil.Cfloat v), Sil.Const (Sil.Cfloat w) -> + Sil.exp_float (v -. w) + | _, Sil.UnOp (Sil.Neg, f2, _) -> + eval (e1 +++ f2) + | _ , Sil.Const(Sil.Cint n) -> + eval (e1' +++ (Sil.exp_int (Sil.Int.neg n))) + | Sil.Const _, _ -> + e1' --- e2' + | Sil.Var _, Sil.Var _ -> + e1' --- e2' + | _, _ -> + if abs then Sil.exp_get_undefined false else e1' --- e2' + end + | Sil.BinOp (Sil.MinusPP, e1, e2) -> + if abs then Sil.exp_get_undefined false + else Sil.BinOp (Sil.MinusPP, eval e1, eval e2) + | Sil.BinOp (Sil.Mult, esize, Sil.Sizeof (t, st)) | Sil.BinOp(Sil.Mult, Sil.Sizeof (t, st), esize) -> + begin + match eval esize, eval (Sil.Sizeof (t, st)) with + | Sil.Const (Sil.Cint i), e' when Sil.Int.isone i -> e' + | esize', e' -> Sil.BinOp(Sil.Mult, esize', e') + end + | Sil.BinOp (Sil.Mult, e1, e2) -> + let e1' = eval e1 in + let e2' = eval e2 in + begin + match e1', e2' with + | Sil.Const c, _ when iszero_int_float c -> + Sil.exp_zero + | Sil.Const c, _ when isone_int_float c -> + e2' + | Sil.Const c, _ when isminusone_int_float c -> + eval (Sil.UnOp (Sil.Neg, e2', None)) + | _, Sil.Const c when iszero_int_float c -> + Sil.exp_zero + | _, Sil.Const c when isone_int_float c -> + e1' + | _, Sil.Const c when isminusone_int_float c -> + eval (Sil.UnOp (Sil.Neg, e1', None)) + | Sil.Const (Sil.Cint n), Sil.Const (Sil.Cint m) -> + Sil.exp_int (Sil.Int.mul n m) + | Sil.Const (Sil.Cfloat v), Sil.Const (Sil.Cfloat w) -> + Sil.exp_float (v *. w) + | Sil.Var v, Sil.Var w -> + Sil.BinOp(Sil.Mult, e1', e2') + | _, _ -> + if abs then Sil.exp_get_undefined false else Sil.BinOp(Sil.Mult, e1', e2') + end + | Sil.BinOp (Sil.Div, e1, e2) -> + let e1' = eval e1 in + let e2' = eval e2 in + begin + match e1', e2' with + | _, Sil.Const c when iszero_int_float c -> + Sil.exp_get_undefined false + | Sil.Const c, _ when iszero_int_float c -> + e1' + | _, Sil.Const c when isone_int_float c -> + e1' + | Sil.Const (Sil.Cint n), Sil.Const (Sil.Cint m) -> + Sil.exp_int (Sil.Int.div n m) + | Sil.Const (Sil.Cfloat v), Sil.Const (Sil.Cfloat w) -> + Sil.exp_float (v /.w) + | Sil.Sizeof(Sil.Tarray(typ, size), _), Sil.Sizeof(_typ, _) (* pattern: sizeof(arr) / sizeof(arr[0]) = size of arr *) + when Sil.typ_equal _typ typ -> + size + | _ -> + if abs then Sil.exp_get_undefined false else Sil.BinOp (Sil.Div, e1', e2') + end + | Sil.BinOp (Sil.Mod, e1, e2) -> + let e1' = eval e1 in + let e2' = eval e2 in + begin + match e1', e2' with + | _, Sil.Const (Sil.Cint i) when Sil.Int.iszero i -> + Sil.exp_get_undefined false + | Sil.Const (Sil.Cint i), _ when Sil.Int.iszero i -> + e1' + | _, Sil.Const (Sil.Cint i) when Sil.Int.isone i -> + Sil.exp_zero + | Sil.Const (Sil.Cint n), Sil.Const (Sil.Cint m) -> + Sil.exp_int (Sil.Int.rem n m) + | _ -> + if abs then Sil.exp_get_undefined false else Sil.BinOp (Sil.Mod, e1', e2') + end + | Sil.BinOp (Sil.Shiftlt, e1, e2) -> + if abs then Sil.exp_get_undefined false else Sil.BinOp (Sil.Shiftlt, eval e1, eval e2) + | Sil.BinOp (Sil.Shiftrt, e1, e2) -> + if abs then Sil.exp_get_undefined false else Sil.BinOp (Sil.Shiftrt, eval e1, eval e2) + | Sil.BinOp (Sil.BAnd, e1, e2) -> + let e1' = eval e1 in + let e2' = eval e2 in + begin match e1', e2' with + | Sil.Const (Sil.Cint i), _ when Sil.Int.iszero i -> + e1' + | _, Sil.Const (Sil.Cint i) when Sil.Int.iszero i -> + e2' + | Sil.Const (Sil.Cint i1), Sil.Const(Sil.Cint i2) -> + Sil.exp_int (Sil.Int.logand i1 i2) + | _ -> + if abs then Sil.exp_get_undefined false else Sil.BinOp (Sil.BAnd, e1', e2') + end + | Sil.BinOp (Sil.BOr, e1, e2) -> + let e1' = eval e1 in + let e2' = eval e2 in + begin match e1', e2' with + | Sil.Const (Sil.Cint i), _ when Sil.Int.iszero i -> + e2' + | _, Sil.Const (Sil.Cint i) when Sil.Int.iszero i -> + e1' + | Sil.Const (Sil.Cint i1), Sil.Const(Sil.Cint i2) -> + Sil.exp_int (Sil.Int.logor i1 i2) + | _ -> + if abs then Sil.exp_get_undefined false else Sil.BinOp (Sil.BOr, e1', e2') + end + | Sil.BinOp (Sil.BXor, e1, e2) -> + let e1' = eval e1 in + let e2' = eval e2 in + begin match e1', e2' with + | Sil.Const (Sil.Cint i), _ when Sil.Int.iszero i -> + e2' + | _, Sil.Const (Sil.Cint i) when Sil.Int.iszero i -> + e1' + | Sil.Const (Sil.Cint i1), Sil.Const(Sil.Cint i2) -> + Sil.exp_int (Sil.Int.logxor i1 i2) + | _ -> + if abs then Sil.exp_get_undefined false else Sil.BinOp (Sil.BXor, e1', e2') + end + | Sil.BinOp (Sil.PtrFld, e1, e2) -> + let e1' = eval e1 in + let e2' = eval e2 in + begin + match e2' with + | Sil.Const (Sil.Cptr_to_fld (fn, typ)) -> + eval (Sil.Lfield(e1', fn, typ)) + | Sil.Const (Sil.Cint i) when Sil.Int.iszero i -> + Sil.exp_zero (* cause a NULL dereference *) + | _ -> Sil.BinOp (Sil.PtrFld, e1', e2') + end + | Sil.Lvar _ -> + e + | Sil.Lfield (e1, fld, typ) -> + let e1' = eval e1 in + Sil.Lfield (e1', fld, typ) + | Sil.Lindex(Sil.Lvar pv, e2) when false (* removed: it interferes with re-arrangement and error messages *) -> (* &x[n] --> &x + n *) + eval (Sil.BinOp (Sil.PlusPI, Sil.Lvar pv, e2)) + | Sil.Lindex (Sil.BinOp(Sil.PlusPI, ep, e1), e2) -> (* array access with pointer arithmetic *) + let e' = Sil.BinOp (Sil.PlusA, e1, e2) in + eval (Sil.Lindex (ep, e')) + | Sil.Lindex (e1, e2) -> + let e1' = eval e1 in + let e2' = eval e2 in + Sil.Lindex(e1', e2') in + let e' = eval e in + (* L.d_str "sym_eval "; Sil.d_exp e; L.d_str" --> "; Sil.d_exp e'; L.d_ln (); *) + e' + +let exp_normalize sub exp = + let exp' = Sil.exp_sub sub exp in + if !Config.abs_val >= 1 then sym_eval true exp' + else sym_eval false exp' + +let rec texp_normalize sub exp = match exp with + | Sil.Sizeof (typ, st) -> Sil.Sizeof (typ_normalize sub typ, st) + | _ -> exp_normalize sub exp + +and typ_normalize sub typ = match typ with + | Sil.Tvar _ + | Sil.Tint _ + | Sil.Tfloat _ + | Sil.Tvoid + | Sil.Tfun _ -> + typ + | Sil.Tptr (t', pk) -> + Sil.Tptr (typ_normalize sub t', pk) + | Sil.Tstruct (ftal, sftal, csu, nameo, supers, def_mthds, iann) -> + let fld_norm = list_map (fun (f, t, a) -> (f, typ_normalize sub t, a)) in + Sil.Tstruct (fld_norm ftal, fld_norm sftal, csu, nameo, supers, def_mthds, iann) + | Sil.Tarray (t, e) -> + Sil.Tarray (typ_normalize sub t, exp_normalize sub e) + | Sil.Tenum econsts -> + typ + +let run_with_abs_val_eq_zero f = + let abs_val_old = !Config.abs_val in + Config.abs_val := 0; + let res = f () in + Config.abs_val := abs_val_old; + res + +let exp_normalize_noabs sub exp = + run_with_abs_val_eq_zero + (fun () -> exp_normalize sub exp) + +(** Return [true] if the atom is an inequality *) +let atom_is_inequality = function + | Sil.Aeq (Sil.BinOp (Sil.Le, _, _), Sil.Const (Sil.Cint i)) when Sil.Int.isone i -> true + | Sil.Aeq (Sil.BinOp (Sil.Lt, _, _), Sil.Const (Sil.Cint i)) when Sil.Int.isone i -> true + | _ -> false + +(** If the atom is [e<=n] return [e,n] *) +let atom_exp_le_const = function + | Sil.Aeq(Sil.BinOp (Sil.Le, e1, Sil.Const (Sil.Cint n)), Sil.Const (Sil.Cint i)) when Sil.Int.isone i -> + Some (e1, n) + | _ -> None + +(** If the atom is [n + Some (n, e1) + | _ -> None + +(** Turn an inequality expression into an atom *) +let mk_inequality e = + match e with + | Sil.BinOp (Sil.Le, base, Sil.Const (Sil.Cint n)) -> + (* base <= n case *) + let nbase = exp_normalize_noabs Sil.sub_empty base in + (match nbase with + | Sil.BinOp(Sil.PlusA, base', Sil.Const (Sil.Cint n')) -> + let new_offset = Sil.exp_int (n -- n') in + let new_e = Sil.BinOp (Sil.Le, base', new_offset) in + Sil.Aeq (new_e, Sil.exp_one) + | Sil.BinOp(Sil.PlusA, Sil.Const (Sil.Cint n'), base') -> + let new_offset = Sil.exp_int (n -- n') in + let new_e = Sil.BinOp (Sil.Le, base', new_offset) in + Sil.Aeq (new_e, Sil.exp_one) + | Sil.BinOp(Sil.MinusA, base', Sil.Const (Sil.Cint n')) -> + let new_offset = Sil.exp_int (n ++ n') in + let new_e = Sil.BinOp (Sil.Le, base', new_offset) in + Sil.Aeq (new_e, Sil.exp_one) + | Sil.BinOp(Sil.MinusA, Sil.Const (Sil.Cint n'), base') -> + let new_offset = Sil.exp_int (n' -- n -- Sil.Int.one) in + let new_e = Sil.BinOp (Sil.Lt, new_offset, base') in + Sil.Aeq (new_e, Sil.exp_one) + | Sil.UnOp(Sil.Neg, new_base, _) -> + (* In this case, base = -new_base. Construct -n-1 < new_base. *) + let new_offset = Sil.exp_int (Sil.Int.zero -- n -- Sil.Int.one) in + let new_e = Sil.BinOp (Sil.Lt, new_offset, new_base) in + Sil.Aeq (new_e, Sil.exp_one) + | _ -> Sil.Aeq (e, Sil.exp_one)) + | Sil.BinOp (Sil.Lt, Sil.Const (Sil.Cint n), base) -> + (* n < base case *) + let nbase = exp_normalize_noabs Sil.sub_empty base in + (match nbase with + | Sil.BinOp(Sil.PlusA, base', Sil.Const (Sil.Cint n')) -> + let new_offset = Sil.exp_int (n -- n') in + let new_e = Sil.BinOp (Sil.Lt, new_offset, base') in + Sil.Aeq (new_e, Sil.exp_one) + | Sil.BinOp(Sil.PlusA, Sil.Const (Sil.Cint n'), base') -> + let new_offset = Sil.exp_int (n -- n') in + let new_e = Sil.BinOp (Sil.Lt, new_offset, base') in + Sil.Aeq (new_e, Sil.exp_one) + | Sil.BinOp(Sil.MinusA, base', Sil.Const (Sil.Cint n')) -> + let new_offset = Sil.exp_int (n ++ n') in + let new_e = Sil.BinOp (Sil.Lt, new_offset, base') in + Sil.Aeq (new_e, Sil.exp_one) + | Sil.BinOp(Sil.MinusA, Sil.Const (Sil.Cint n'), base') -> + let new_offset = Sil.exp_int (n' -- n -- Sil.Int.one) in + let new_e = Sil.BinOp (Sil.Le, base', new_offset) in + Sil.Aeq (new_e, Sil.exp_one) + | Sil.UnOp(Sil.Neg, new_base, _) -> + (* In this case, base = -new_base. Construct new_base <= -n-1 *) + let new_offset = Sil.exp_int (Sil.Int.zero -- n -- Sil.Int.one) in + let new_e = Sil.BinOp (Sil.Le, new_base, new_offset) in + Sil.Aeq (new_e, Sil.exp_one) + | _ -> Sil.Aeq (e, Sil.exp_one)) + | _ -> Sil.Aeq (e, Sil.exp_one) + +(** Normalize an inequality *) +let inequality_normalize a = + (** turn an expression into a triple (pos,neg,off) of positive and negative occurrences, and integer offset *) + (** representing inequality [sum(pos) - sum(neg) + off <= 0] *) + let rec exp_to_posnegoff e = match e with + | Sil.Const (Sil.Cint n) -> [],[], n + | Sil.BinOp(Sil.PlusA, e1, e2) | Sil.BinOp(Sil.PlusPI, e1, e2) -> + let pos1, neg1, n1 = exp_to_posnegoff e1 in + let pos2, neg2, n2 = exp_to_posnegoff e2 in + (pos1@pos2, neg1@neg2, n1 ++ n2) + | Sil.BinOp(Sil.MinusA, e1, e2) | Sil.BinOp(Sil.MinusPI, e1, e2) | Sil.BinOp(Sil.MinusPP, e1, e2) -> + let pos1, neg1, n1 = exp_to_posnegoff e1 in + let pos2, neg2, n2 = exp_to_posnegoff e2 in + (pos1@neg2, neg1@pos2, n1 -- n2) + | Sil.UnOp(Sil.Neg, e1, _) -> + let pos1, neg1, n1 = exp_to_posnegoff e1 in + (neg1, pos1, Sil.Int.zero -- n1) + | _ -> [e],[], Sil.Int.zero in + (** sort and filter out expressions appearing in both the positive and negative part *) + let normalize_posnegoff (pos, neg, off) = + let pos' = list_sort Sil.exp_compare pos in + let neg' = list_sort Sil.exp_compare neg in + let rec combine pacc nacc = function + | x:: ps, y:: ng -> + (match Sil.exp_compare x y with + | n when n < 0 -> combine (x:: pacc) nacc (ps, y :: ng) + | 0 -> combine pacc nacc (ps, ng) + | _ -> combine pacc (y:: nacc) (x :: ps, ng)) + | ps, ng -> (list_rev pacc) @ ps, (list_rev nacc) @ ng in + let pos'', neg'' = combine [] [] (pos', neg') in + (pos'', neg'', off) in + (** turn a non-empty list of expressions into a sum expression *) + let rec exp_list_to_sum = function + | [] -> assert false + | [e] -> e + | e:: el -> Sil.BinOp(Sil.PlusA, e, exp_list_to_sum el) in + let norm_from_exp e = + match normalize_posnegoff (exp_to_posnegoff e) with + | [],[], n -> Sil.BinOp(Sil.Le, Sil.exp_int n, Sil.exp_zero) + | [], neg, n -> Sil.BinOp(Sil.Lt, Sil.exp_int (n -- Sil.Int.one), exp_list_to_sum neg) + | pos, [], n -> Sil.BinOp(Sil.Le, exp_list_to_sum pos, Sil.exp_int (Sil.Int.zero -- n)) + | pos, neg, n -> + let lhs_e = Sil.BinOp(Sil.MinusA, exp_list_to_sum pos, exp_list_to_sum neg) in + Sil.BinOp(Sil.Le, lhs_e, Sil.exp_int (Sil.Int.zero -- n)) in + let ineq = match a with + | Sil.Aeq (ineq, Sil.Const (Sil.Cint i)) when Sil.Int.isone i -> + ineq + | _ -> assert false in + match ineq with + | Sil.BinOp(Sil.Le, e1, e2) -> + let e = Sil.BinOp(Sil.MinusA, e1, e2) in + mk_inequality (norm_from_exp e) + | Sil.BinOp(Sil.Lt, e1, e2) -> + let e = Sil.BinOp(Sil.MinusA, Sil.BinOp(Sil.MinusA, e1, e2), Sil.exp_minus_one) in + mk_inequality (norm_from_exp e) + | _ -> a + +let exp_reorder e1 e2 = if Sil.exp_compare e1 e2 <= 0 then (e1, e2) else (e2, e1) + +(** Normalize an atom. +We keep the convention that inequalities with constants +are only of the form [e <= n] and [n < e]. *) +let atom_normalize sub a0 = + let a = Sil.atom_sub sub a0 in + let rec normalize_eq eq = match eq with + | Sil.BinOp(Sil.PlusA, e1, Sil.Const (Sil.Cint n1)), Sil.Const (Sil.Cint n2) (* e1+n1==n2 ---> e1==n2-n1 *) + | Sil.BinOp(Sil.PlusPI, e1, Sil.Const (Sil.Cint n1)), Sil.Const (Sil.Cint n2) -> + (e1, Sil.exp_int (n2 -- n1)) + | Sil.BinOp(Sil.MinusA, e1, Sil.Const (Sil.Cint n1)), Sil.Const (Sil.Cint n2) (* e1-n1==n2 ---> e1==n1+n2 *) + | Sil.BinOp(Sil.MinusPI, e1, Sil.Const (Sil.Cint n1)), Sil.Const (Sil.Cint n2) -> + (e1, Sil.exp_int (n1 ++ n2)) + | Sil.BinOp(Sil.MinusA, Sil.Const (Sil.Cint n1), e1), Sil.Const (Sil.Cint n2) -> (* n1-e1 == n2 -> e1==n1-n2 *) + (e1, Sil.exp_int (n1 -- n2)) + | Sil.Lfield (e1', fld1, typ1), Sil.Lfield (e2', fld2, typ2) -> + if Sil.fld_equal fld1 fld2 + then normalize_eq (e1', e2') + else eq + | Sil.Lindex (e1', idx1), Sil.Lindex (e2', idx2) -> + if Sil.exp_equal idx1 idx2 then normalize_eq (e1', e2') + else if Sil.exp_equal e1' e2' then normalize_eq (idx1, idx2) + else eq + | _ -> eq in + let handle_unary_negation e1 e2 = + match e1, e2 with + | Sil.UnOp (Sil.LNot, e1', _), Sil.Const (Sil.Cint i) + | Sil.Const (Sil.Cint i), Sil.UnOp (Sil.LNot, e1', _) when Sil.Int.iszero i -> + (e1', Sil.exp_zero, true) + | _ -> (e1, e2, false) in + let handle_boolean_operation from_equality e1 e2 = + let ne1 = exp_normalize sub e1 in + let ne2 = exp_normalize sub e2 in + let ne1', ne2', op_negated = handle_unary_negation ne1 ne2 in + let (e1', e2') = normalize_eq (ne1', ne2') in + let (e1'', e2'') = exp_reorder e1' e2' in + let use_equality = + if op_negated then not from_equality else from_equality in + if use_equality then + Sil.Aeq (e1'', e2'') + else + Sil.Aneq (e1'', e2'') in + let a' = match a with + | Sil.Aeq (e1, e2) -> + handle_boolean_operation true e1 e2 + | Sil.Aneq (e1, e2) -> + handle_boolean_operation false e1 e2 in + if atom_is_inequality a' then inequality_normalize a' else a' + +(** Negate an atom *) +let atom_negate = function + | Sil.Aeq (Sil.BinOp (Sil.Le, e1, e2), Sil.Const (Sil.Cint i)) when Sil.Int.isone i -> + mk_inequality (Sil.exp_lt e2 e1) + | Sil.Aeq (Sil.BinOp (Sil.Lt, e1, e2), Sil.Const (Sil.Cint i)) when Sil.Int.isone i -> + mk_inequality (Sil.exp_le e2 e1) + | Sil.Aeq (e1, e2) -> Sil.Aneq (e1, e2) + | Sil.Aneq (e1, e2) -> Sil.Aeq (e1, e2) + +let rec remove_duplicates_from_sorted special_equal = function + | [] -> [] + | [x] -> [x] + | x:: y:: l -> + if (special_equal x y) + then remove_duplicates_from_sorted special_equal (y:: l) + else x:: (remove_duplicates_from_sorted special_equal (y:: l)) + +let rec strexp_normalize sub se = + match se with + | Sil.Eexp (e, inst) -> + Sil.Eexp (exp_normalize sub e, inst) + | Sil.Estruct (fld_cnts, inst) -> + begin + match fld_cnts with + | [] -> se + | _ -> + let fld_cnts' = + list_map (fun (fld, cnt) -> + fld, strexp_normalize sub cnt) fld_cnts in + let fld_cnts'' = list_sort Sil.fld_strexp_compare fld_cnts' in + Sil.Estruct (fld_cnts'', inst) + end + | Sil.Earray (size, idx_cnts, inst) -> + begin + let size' = exp_normalize_noabs sub size in + match idx_cnts with + | [] -> + if Sil.exp_equal size size' then se else Sil.Earray (size', idx_cnts, inst) + | _ -> + let idx_cnts' = + list_map (fun (idx, cnt) -> + let idx' = exp_normalize sub idx in + idx', strexp_normalize sub cnt) idx_cnts in + let idx_cnts'' = + list_sort Sil.exp_strexp_compare idx_cnts' in + Sil.Earray (size', idx_cnts'', inst) + end + +(** create a strexp of the given type, populating the structures if [expand_structs] is true *) +let rec create_strexp_of_type tenvo struct_init_mode typ inst = + let init_value () = + let create_fresh_var () = + let fresh_id = + (Ident.create_fresh (if !Config.footprint then Ident.kfootprint else Ident.kprimed)) in + Sil.Var fresh_id in + if !Sil.curr_language = Sil.Java && inst = Sil.Ialloc + then + match typ with + | Sil.Tfloat _ -> Sil.Const (Sil.Cfloat 0.0) + | _ -> Sil.exp_zero + else + create_fresh_var () in + match typ with + | Sil.Tint _ | Sil.Tfloat _ | Sil.Tvoid | Sil.Tfun _ | Sil.Tptr _ | Sil.Tenum _ -> + Sil.Eexp (init_value (), inst) + | Sil.Tstruct (ftal, sftal, _, _, _, _, _) -> + begin + match struct_init_mode with + | No_init -> Sil.Estruct ([], inst) + | Fld_init -> + let f (fld, t, a) = + if Sil.is_objc_ref_counter_field (fld, t, a) then + (fld, Sil.Eexp (Sil.exp_one, inst)) + else + (fld, create_strexp_of_type tenvo struct_init_mode t inst) in + Sil.Estruct (list_map f ftal, inst) + end + | Sil.Tarray (_, size) -> + Sil.Earray (size, [], inst) + | Sil.Tvar name -> + L.out "@[<2>ANALYSIS BUG@\n"; + L.out "type %a should be expanded to " (Sil.pp_typ_full pe_text) typ; + begin + match tenvo with + | None -> L.out "nothing@\n@." + | Some tenv -> + begin + match Sil.tenv_lookup tenv name with + | None -> L.out "nothing@\n@." + | Some typ' -> L.out "%a@\n@." (Sil.pp_typ_full pe_text) typ' + end; + end; + assert false + +(** Sil.Construct a pointsto. *) +let mk_ptsto lexp sexp te = + let nsexp = strexp_normalize Sil.sub_empty sexp in + Sil.Hpointsto(lexp, nsexp, te) + +(** Construct a points-to predicate for an expression using either the provided expression [name] as +base for fresh identifiers. If [expand_structs] is true, initialize the fields of structs with fresh variables. *) +let mk_ptsto_exp tenvo struct_init_mode (exp, te, expo) inst : Sil.hpred = + let default_strexp () = match te with + | Sil.Sizeof (typ, st) -> + create_strexp_of_type tenvo struct_init_mode typ inst + | Sil.Var id -> + Sil.Estruct ([], inst) + | te -> + L.err "trying to create ptsto with type: %a@\n@." (Sil.pp_texp_full pe_text) te; + assert false in + let strexp = match expo with + | Some e -> Sil.Eexp (e, inst) + | None -> default_strexp () in + mk_ptsto exp strexp te + +let replace_array_contents hpred esel = match hpred with + | Sil.Hpointsto (root, Sil.Earray (size, [], inst), te) -> + Sil.Hpointsto (root, Sil.Earray (size, esel, inst), te) + | _ -> assert false + +let rec hpred_normalize sub hpred = + let replace_hpred hpred' = + L.d_strln "found array with sizeof(..) size"; + L.d_str "converting original hpred: "; Sil.d_hpred hpred; L.d_ln (); + L.d_str "into the following: "; Sil.d_hpred hpred'; L.d_ln (); + hpred' in + match hpred with + | Sil.Hpointsto (root, cnt, te) -> + let normalized_root = exp_normalize sub root in + let normalized_cnt = strexp_normalize sub cnt in + let normalized_te = texp_normalize sub te in + begin match normalized_cnt, normalized_te with + | Sil.Earray (Sil.Sizeof (t, st1), [], inst), Sil.Sizeof (Sil.Tarray _, st2) -> + (* check for an empty array whose size expression is (Sizeof type), and turn the array into a strexp of the given type *) + let hpred' = mk_ptsto_exp None Fld_init (root, Sil.Sizeof (t, st1), None) inst in + replace_hpred hpred' + | Sil.Earray (Sil.BinOp(Sil.Mult, Sil.Sizeof (t, st1), x), esel, inst), Sil.Sizeof (Sil.Tarray _, st2) + | Sil.Earray (Sil.BinOp(Sil.Mult, x, Sil.Sizeof (t, st1)), esel, inst), Sil.Sizeof (Sil.Tarray _, st2) -> + (* check for an array whose size expression is n * (Sizeof type), and turn the array into a strexp of the given type *) + let hpred' = mk_ptsto_exp None Fld_init (root, Sil.Sizeof (Sil.Tarray(t, x), st1), None) inst in + replace_hpred (replace_array_contents hpred' esel) + | _ -> Sil.Hpointsto (normalized_root, normalized_cnt, normalized_te) + end + | Sil.Hlseg (k, para, e1, e2, elist) -> + let normalized_e1 = exp_normalize sub e1 in + let normalized_e2 = exp_normalize sub e2 in + let normalized_elist = list_map (exp_normalize sub) elist in + let normalized_para = hpara_normalize sub para in + Sil.Hlseg (k, normalized_para, normalized_e1, normalized_e2, normalized_elist) + | Sil.Hdllseg (k, para, e1, e2, e3, e4, elist) -> + let norm_e1 = exp_normalize sub e1 in + let norm_e2 = exp_normalize sub e2 in + let norm_e3 = exp_normalize sub e3 in + let norm_e4 = exp_normalize sub e4 in + let norm_elist = list_map (exp_normalize sub) elist in + let norm_para = hpara_dll_normalize sub para in + Sil.Hdllseg (k, norm_para, norm_e1, norm_e2, norm_e3, norm_e4, norm_elist) + +and hpara_normalize sub para = + let normalized_body = list_map (hpred_normalize Sil.sub_empty) (para.Sil.body) in + let sorted_body = list_sort Sil.hpred_compare normalized_body in + { para with Sil.body = sorted_body } + +and hpara_dll_normalize sub para = + let normalized_body = list_map (hpred_normalize Sil.sub_empty) (para.Sil.body_dll) in + let sorted_body = list_sort Sil.hpred_compare normalized_body in + { para with Sil.body_dll = sorted_body } + +let pi_tighten_ineq pi = + let ineq_list, nonineq_list = list_partition atom_is_inequality pi in + let diseq_list = + let get_disequality_info acc = function + | Sil.Aneq(Sil.Const (Sil.Cint n), e) | Sil.Aneq(e, Sil.Const (Sil.Cint n)) -> (e, n):: acc + | _ -> acc in + list_fold_left get_disequality_info [] nonineq_list in + let is_neq e n = + list_exists (fun (e', n') -> Sil.exp_equal e e' && Sil.Int.eq n n') diseq_list in + let le_list_tightened = + let get_le_inequality_info acc a = + match atom_exp_le_const a with + | Some (e, n) -> (e, n):: acc + | _ -> acc in + let rec le_tighten le_list_done = function + | [] -> list_rev le_list_done + | (e, n):: le_list_todo -> (* e <= n *) + if is_neq e n then le_tighten le_list_done ((e, n -- Sil.Int.one):: le_list_todo) + else le_tighten ((e, n):: le_list_done) (le_list_todo) in + let le_list = list_rev (list_fold_left get_le_inequality_info [] ineq_list) in + le_tighten [] le_list in + let lt_list_tightened = + let get_lt_inequality_info acc a = + match atom_const_lt_exp a with + | Some (n, e) -> (n, e):: acc + | _ -> acc in + let rec lt_tighten lt_list_done = function + | [] -> list_rev lt_list_done + | (n, e):: lt_list_todo -> (* n < e *) + let n_plus_one = n ++ Sil.Int.one in + if is_neq e n_plus_one then lt_tighten lt_list_done ((n ++ Sil.Int.one, e):: lt_list_todo) + else lt_tighten ((n, e):: lt_list_done) (lt_list_todo) in + let lt_list = list_rev (list_fold_left get_lt_inequality_info [] ineq_list) in + lt_tighten [] lt_list in + let ineq_list' = + let le_ineq_list = + list_map + (fun (e, n) -> mk_inequality (Sil.BinOp(Sil.Le, e, Sil.exp_int n))) + le_list_tightened in + let lt_ineq_list = + list_map + (fun (n, e) -> mk_inequality (Sil.BinOp(Sil.Lt, Sil.exp_int n, e))) + lt_list_tightened in + le_ineq_list @ lt_ineq_list in + let nonineq_list' = + list_filter + (function + | Sil.Aneq(Sil.Const (Sil.Cint n), e) | Sil.Aneq(e, Sil.Const (Sil.Cint n)) -> + (not (list_exists (fun (e', n') -> Sil.exp_equal e e' && Sil.Int.lt n' n) le_list_tightened)) && + (not (list_exists (fun (n', e') -> Sil.exp_equal e e' && Sil.Int.leq n n') lt_list_tightened)) + | _ -> true) + nonineq_list in + (ineq_list', nonineq_list') + +(** remove duplicate atoms and redundant inequalities from a sorted pi *) +let rec pi_sorted_remove_redundant = function + | (Sil.Aeq(Sil.BinOp (Sil.Le, e1, Sil.Const (Sil.Cint n1)), Sil.Const (Sil.Cint i1)) as a1) :: + Sil.Aeq(Sil.BinOp (Sil.Le, e2, Sil.Const (Sil.Cint n2)), Sil.Const (Sil.Cint i2)) :: rest + when Sil.Int.isone i1 && Sil.Int.isone i2 && Sil.exp_equal e1 e2 && Sil.Int.lt n1 n2 -> (* second inequality redundant *) + pi_sorted_remove_redundant (a1 :: rest) + | Sil.Aeq(Sil.BinOp (Sil.Lt, Sil.Const (Sil.Cint n1), e1), Sil.Const (Sil.Cint i1)) :: + (Sil.Aeq(Sil.BinOp (Sil.Lt, Sil.Const (Sil.Cint n2), e2), Sil.Const (Sil.Cint i2)) as a2) :: rest + when Sil.Int.isone i1 && Sil.Int.isone i2 && Sil.exp_equal e1 e2 && Sil.Int.lt n1 n2 -> (* first inequality redundant *) + pi_sorted_remove_redundant (a2 :: rest) + | a1:: a2:: rest -> + if Sil.atom_equal a1 a2 then pi_sorted_remove_redundant (a2 :: rest) + else a1 :: pi_sorted_remove_redundant (a2 :: rest) + | [a] -> [a] + | [] -> [] + +(** find the unsigned expressions in sigma (immediately inside a pointsto, for now) *) +let sigma_get_unsigned_exps sigma = + let uexps = ref [] in + let do_hpred = function + | Sil.Hpointsto(_, Sil.Eexp(e, _), Sil.Sizeof (Sil.Tint ik, _)) when Sil.ikind_is_unsigned ik -> + uexps := e :: !uexps + | _ -> () in + list_iter do_hpred sigma; + !uexps + +(** Normalization of pi. +The normalization filters out obviously - true disequalities, such as e <> e + 1. *) +let pi_normalize sub sigma pi0 = + let pi = list_map (atom_normalize sub) pi0 in + let ineq_list, nonineq_list = pi_tighten_ineq pi in + let syntactically_different = function + | Sil.BinOp(op1, e1, Sil.Const(c1)), Sil.BinOp(op2, e2, Sil.Const(c2)) + when Sil.exp_equal e1 e2 -> + Sil.binop_equal op1 op2 && Sil.binop_injective op1 && not (Sil.const_equal c1 c2) + | e1, Sil.BinOp(op2, e2, Sil.Const(c2)) + when Sil.exp_equal e1 e2 -> + Sil.binop_injective op2 && Sil.binop_is_zero_runit op2 && not (Sil.const_equal (Sil.Cint Sil.Int.zero) c2) + | Sil.BinOp(op1, e1, Sil.Const(c1)), e2 + when Sil.exp_equal e1 e2 -> + Sil.binop_injective op1 && Sil.binop_is_zero_runit op1 && not (Sil.const_equal (Sil.Cint Sil.Int.zero) c1) + | _ -> false in + let filter_useful_atom = + let unsigned_exps = lazy (sigma_get_unsigned_exps sigma) in + function + | Sil.Aneq ((Sil.Var _) as e, Sil.Const (Sil.Cint n)) when Sil.Int.isnegative n -> + not (list_exists (Sil.exp_equal e) (Lazy.force unsigned_exps)) + | Sil.Aneq(e1, e2) -> + not (syntactically_different (e1, e2)) + | Sil.Aeq(Sil.Const c1, Sil.Const c2) -> + not (Sil.const_equal c1 c2) + | a -> true in + let pi' = list_stable_sort Sil.atom_compare ((list_filter filter_useful_atom nonineq_list) @ ineq_list) in + let pi'' = pi_sorted_remove_redundant pi' in + if pi_equal pi0 pi'' then pi0 else pi'' + +let sigma_normalize sub sigma = + let sigma' = + list_stable_sort Sil.hpred_compare (list_map (hpred_normalize sub) sigma) in + if sigma_equal sigma sigma' then sigma else sigma' + +(** normalize the footprint part, and rename any primed vars in the footprint with fresh footprint vars *) +let footprint_normalize prop = + let nsigma = sigma_normalize Sil.sub_empty prop.foot_sigma in + let npi = pi_normalize Sil.sub_empty nsigma prop.foot_pi in + let fp_vars = + let fav = pi_fav npi in + sigma_fav_add fav nsigma; + fav in + (* TODO (t4893479): make this check less angelic *) + if Sil.fav_exists fp_vars Ident.is_normal && not !Config.angelic_execution then + begin + L.d_strln "footprint part contains normal variables"; + d_pi npi; L.d_ln (); + d_sigma nsigma; L.d_ln (); + assert false + end; + Sil.fav_filter_ident fp_vars Ident.is_primed; (* only keep primed vars *) + let npi', nsigma' = + if Sil.fav_is_empty fp_vars then npi, nsigma + else (* replace primed vars by fresh footprint vars *) + let ids_primed = Sil.fav_to_list fp_vars in + let ids_footprint = + list_map (fun id -> (id, Ident.create_fresh Ident.kfootprint)) ids_primed in + let ren_sub = Sil.sub_of_list (list_map (fun (id1, id2) -> (id1, Sil.Var id2)) ids_footprint) in + let nsigma' = sigma_normalize Sil.sub_empty (sigma_sub ren_sub nsigma) in + let npi' = pi_normalize Sil.sub_empty nsigma' (pi_sub ren_sub npi) in + (npi', nsigma') in + { prop with foot_pi = npi'; foot_sigma = nsigma' } + +let exp_normalize_prop prop exp = + run_with_abs_val_eq_zero + (fun () -> exp_normalize prop.sub exp) + +let lexp_normalize_prop p lexp = + let root = Sil.root_of_lexp lexp in + let offsets = Sil.exp_get_offsets lexp in + let nroot = exp_normalize_prop p root in + let noffsets = + list_map (fun n -> match n with + | Sil.Off_fld _ -> n + | Sil.Off_index e -> Sil.Off_index (exp_normalize_prop p e) + ) offsets in + Sil.exp_add_offsets nroot noffsets + +(** Collapse consecutive indices that should be added. For instance, +this function reduces x[1][1] to x[2]. The [typ] argument is used +to ensure the soundness of this collapsing. *) +let exp_collapse_consecutive_indices_prop p typ exp = + let typ_is_base = function + | Sil.Tint _ | Sil.Tfloat _ | Sil.Tstruct _ | Sil.Tvoid | Sil.Tfun _ -> true + | _ -> false in + let typ_is_one_step_from_base = + match typ with + | Sil.Tptr (t, _) | Sil.Tarray (t, _) -> typ_is_base t + | _ -> false in + let rec exp_remove e0 = + match e0 with + | Sil.Lindex(Sil.Lindex(base, e1), e2) -> + let e0' = Sil.Lindex(base, Sil.BinOp(Sil.PlusA, e1, e2)) in + exp_remove e0' + | _ -> e0 in + begin + if typ_is_one_step_from_base then exp_remove exp else exp + end + +let atom_normalize_prop prop atom = + run_with_abs_val_eq_zero + (fun () -> atom_normalize prop.sub atom) + +let strexp_normalize_prop prop strexp = + run_with_abs_val_eq_zero + (fun () -> strexp_normalize prop.sub strexp) + +let hpred_normalize_prop prop hpred = + run_with_abs_val_eq_zero + (fun () -> hpred_normalize prop.sub hpred) + +let sigma_normalize_prop prop sigma = + run_with_abs_val_eq_zero + (fun () -> sigma_normalize prop.sub sigma) + +let pi_normalize_prop prop pi = + run_with_abs_val_eq_zero + (fun () -> pi_normalize prop.sub prop.sigma pi) + +(** {2 Compaction} *) +(** Return a compact representation of the prop *) +let prop_compact sh prop = + let sigma' = list_map (Sil.hpred_compact sh) prop.sigma in + { prop with sigma = sigma'} + +(** {2 Function for replacing occurrences of expressions.} *) + +let replace_pi pi eprop = + { eprop with pi = pi } + +let replace_sigma sigma eprop = + { eprop with sigma = sigma } + +exception No_Footprint + +let unSome_footprint = function + | None -> raise No_Footprint + | Some fp -> fp + +let replace_sigma_footprint sigma (prop : 'a t) : exposed t = + { prop with foot_sigma = sigma } + +let replace_pi_footprint pi (prop : 'a t) : exposed t = + { prop with foot_pi = pi } + +let rec sigma_replace_exp epairs sigma = + let sigma' = list_map (Sil.hpred_replace_exp epairs) sigma in + sigma_normalize Sil.sub_empty sigma' + +let sigma_map prop f = + let sigma' = list_map f prop.sigma in + { prop with sigma = sigma' } + +(** {2 Query about Proposition} *) + +(** Check if the sigma part of the proposition is emp *) +let prop_is_emp p = match p.sigma with + | [] -> true + | _ -> false + +(** {2 Functions for changing and generating propositions} *) + +(** Replace the sub part of [prop]. *) +let prop_replace_sub sub p = + let nsub = sub_normalize sub in + { p with sub = nsub } + +(** Sil.Construct a disequality. *) +let mk_neq e1 e2 = + run_with_abs_val_eq_zero + (fun () -> + let ne1 = exp_normalize Sil.sub_empty e1 in + let ne2 = exp_normalize Sil.sub_empty e2 in + atom_normalize Sil.sub_empty (Sil.Aneq (ne1, ne2))) + +(** Sil.Construct an equality. *) +let mk_eq e1 e2 = + run_with_abs_val_eq_zero + (fun () -> + let ne1 = exp_normalize Sil.sub_empty e1 in + let ne2 = exp_normalize Sil.sub_empty e2 in + atom_normalize Sil.sub_empty (Sil.Aeq (ne1, ne2))) + +let unstructured_type = function + | Sil.Tstruct _ | Sil.Tarray _ -> false + | _ -> true + +(** Construct a points-to predicate for a single program variable. +If [expand_structs] is true, initialize the fields of structs with fresh variables. *) +let mk_ptsto_lvar tenv expand_structs inst ((pvar: Sil.pvar), texp, expo) : Sil.hpred = + mk_ptsto_exp tenv expand_structs (Sil.Lvar pvar, texp, expo) inst + +(** Sil.Construct a lseg predicate *) +let mk_lseg k para e_start e_end es_shared = + let npara = hpara_normalize Sil.sub_empty para in + Sil.Hlseg (k, npara, e_start, e_end, es_shared) + +(** Sil.Construct a dllseg predicate *) +let mk_dllseg k para exp_iF exp_oB exp_oF exp_iB exps_shared = + let npara = hpara_dll_normalize Sil.sub_empty para in + Sil.Hdllseg (k, npara, exp_iF, exp_oB , exp_oF, exp_iB, exps_shared) + +(** Sil.Construct a hpara *) +let mk_hpara root next svars evars body = + let para = { Sil.root = root; Sil.next = next; Sil.svars = svars; Sil.evars = evars; Sil.body = body } in + hpara_normalize Sil.sub_empty para + +(** Sil.Construct a dll_hpara *) +let mk_dll_hpara iF oB oF svars evars body = + let para = { Sil.cell = iF; Sil.blink = oB; Sil.flink = oF; Sil.svars_dll = svars; Sil.evars_dll = evars; Sil.body_dll = body } in + hpara_dll_normalize Sil.sub_empty para + +(** Proposition [true /\ emp]. *) +let prop_emp : normal t = { sub = Sil.sub_empty; pi =[]; sigma =[]; foot_pi =[]; foot_sigma =[] } + +(** Conjoin a heap predicate by separating conjunction. *) +let prop_hpred_star (p : 'a t) (h : Sil.hpred) : exposed t = + let sigma' = h:: p.sigma in + { p with sigma = sigma'} + +let prop_sigma_star (p : 'a t) (sigma : Sil.hpred list) : exposed t = + let sigma' = sigma @ p.sigma in + { p with sigma = sigma' } + +(** Eliminates all empty lsegs from sigma, and collect equalities +The empty lsegs include +(a) "lseg_pe para 0 e elist", +(b) "dllseg_pe para iF oB oF iB elist" with iF = 0 or iB = 0, +(c) "lseg_pe para e1 e2 elist" and the rest of sigma contains the "cell" e1, +(d) "dllseg_pe para iF oB oF iB elist" and the rest of sigma contains +cell iF or iB. *) + +module ExpSet = Set.Make(struct + type t = Sil.exp + let compare = Sil.exp_compare + end) + +let sigma_remove_emptylseg sigma = + let alloc_set = + let rec f_alloc set = function + | [] -> + set + | Sil.Hpointsto (e, _, _) :: sigma' | Sil.Hlseg (Sil.Lseg_NE, _, e, _, _) :: sigma' -> + f_alloc (ExpSet.add e set) sigma' + | Sil.Hdllseg (Sil.Lseg_NE, _, iF, _, _, iB, _) :: sigma' -> + f_alloc (ExpSet.add iF (ExpSet.add iB set)) sigma' + | _ :: sigma' -> + f_alloc set sigma' in + f_alloc ExpSet.empty sigma + in + let rec f eqs_zero sigma_passed = function + | [] -> + (list_rev eqs_zero, list_rev sigma_passed) + | Sil.Hpointsto _ as hpred :: sigma' -> + f eqs_zero (hpred :: sigma_passed) sigma' + | Sil.Hlseg (Sil.Lseg_PE, _, e1, e2, _) :: sigma' + when (Sil.exp_equal e1 Sil.exp_zero) || (ExpSet.mem e1 alloc_set) -> + f (Sil.Aeq(e1, e2) :: eqs_zero) sigma_passed sigma' + | Sil.Hlseg _ as hpred :: sigma' -> + f eqs_zero (hpred :: sigma_passed) sigma' + | Sil.Hdllseg (Sil.Lseg_PE, _, iF, oB, oF, iB, _) :: sigma' + when (Sil.exp_equal iF Sil.exp_zero) || (ExpSet.mem iF alloc_set) + || (Sil.exp_equal iB Sil.exp_zero) || (ExpSet.mem iB alloc_set) -> + f (Sil.Aeq(iF, oF):: Sil.Aeq(iB, oB):: eqs_zero) sigma_passed sigma' + | Sil.Hdllseg _ as hpred :: sigma' -> + f eqs_zero (hpred :: sigma_passed) sigma' + in + f [] [] sigma + +let sigma_intro_nonemptylseg e1 e2 sigma = + let rec f sigma_passed = function + | [] -> + list_rev sigma_passed + | Sil.Hpointsto _ as hpred :: sigma' -> + f (hpred :: sigma_passed) sigma' + | Sil.Hlseg (Sil.Lseg_PE, para, f1, f2, shared) :: sigma' + when (Sil.exp_equal e1 f1 && Sil.exp_equal e2 f2) + || (Sil.exp_equal e2 f1 && Sil.exp_equal e1 f2) -> + f (Sil.Hlseg (Sil.Lseg_NE, para, f1, f2, shared) :: sigma_passed) sigma' + | Sil.Hlseg _ as hpred :: sigma' -> + f (hpred :: sigma_passed) sigma' + | Sil.Hdllseg (Sil.Lseg_PE, para, iF, oB, oF, iB, shared) :: sigma' + when (Sil.exp_equal e1 iF && Sil.exp_equal e2 oF) + || (Sil.exp_equal e2 iF && Sil.exp_equal e1 oF) + || (Sil.exp_equal e1 iB && Sil.exp_equal e2 oB) + || (Sil.exp_equal e2 iB && Sil.exp_equal e1 oB) -> + f (Sil.Hdllseg (Sil.Lseg_NE, para, iF, oB, oF, iB, shared) :: sigma_passed) sigma' + | Sil.Hdllseg _ as hpred :: sigma' -> + f (hpred :: sigma_passed) sigma' + in + f [] sigma + +let normalize_and_strengthen_atom (p : normal t) (a : Sil.atom) : Sil.atom = + let a' = atom_normalize p.sub a in + match a' with + | Sil.Aeq (Sil.BinOp (Sil.Le, Sil.Var id, Sil.Const (Sil.Cint n)), Sil.Const (Sil.Cint i)) when Sil.Int.isone i -> + let lower = Sil.exp_int (n -- Sil.Int.one) in + let a_lower = Sil.Aeq (Sil.BinOp (Sil.Lt, lower, Sil.Var id), Sil.exp_one) in + if not (list_mem Sil.atom_equal a_lower p.pi) then a' + else Sil.Aeq (Sil.Var id, Sil.exp_int n) + | Sil.Aeq (Sil.BinOp (Sil.Lt, Sil.Const (Sil.Cint n), Sil.Var id), Sil.Const (Sil.Cint i)) when Sil.Int.isone i -> + let upper = Sil.exp_int (n ++ Sil.Int.one) in + let a_upper = Sil.Aeq (Sil.BinOp (Sil.Le, Sil.Var id, upper), Sil.exp_one) in + if not (list_mem Sil.atom_equal a_upper p.pi) then a' + else Sil.Aeq (Sil.Var id, upper) + | Sil.Aeq (Sil.BinOp (Sil.Ne, e1, e2), Sil.Const (Sil.Cint i)) when Sil.Int.isone i -> + Sil.Aneq (e1, e2) + | _ -> a' + +(** Conjoin a pure atomic predicate by normal conjunction. *) +let rec prop_atom_and ?(footprint = false) (p : normal t) (a : Sil.atom) : normal t = + let a' = normalize_and_strengthen_atom p a in + if list_mem Sil.atom_equal a' p.pi then p + else begin + let p' = + match a' with + | Sil.Aeq (Sil.Var i, e) when Sil.ident_in_exp i e -> p + | Sil.Aeq (Sil.Var i, e) -> + let sub_list = [(i, e)] in + let mysub = Sil.sub_of_list sub_list in + let p_sub = Sil.sub_filter (fun i' -> not (Ident.equal i i')) p.sub in + let sub' = Sil.sub_join mysub (Sil.sub_range_map (Sil.exp_sub mysub) p_sub) in + let (nsub', npi', nsigma') = + let nsigma' = sigma_normalize sub' p.sigma in + (sub_normalize sub', pi_normalize sub' nsigma' p.pi, nsigma') in + let (eqs_zero, nsigma'') = sigma_remove_emptylseg nsigma' in + let p' = { p with sub = nsub'; pi = npi'; sigma = nsigma''} in + list_fold_left (prop_atom_and ~footprint: footprint) p' eqs_zero + | Sil.Aeq (e1, e2) when (Sil.exp_compare e1 e2 = 0) -> + p + | Sil.Aneq (e1, e2) -> + let sigma' = sigma_intro_nonemptylseg e1 e2 p.sigma in + let pi' = pi_normalize p.sub sigma' (a':: p.pi) in + { p with pi = pi'; sigma = sigma'} + | _ -> + let pi' = pi_normalize p.sub p.sigma (a':: p.pi) in + { p with pi = pi'} in + if not footprint then p' + else begin + let fav_a' = Sil.atom_fav a' in + let fav_nofootprint_a' = + Sil.fav_copy_filter_ident fav_a' (fun id -> not (Ident.is_footprint id)) in + let predicate_warning = + not (Sil.fav_is_empty fav_nofootprint_a') in + let p'' = + if predicate_warning then footprint_normalize p' + else + match a' with + | Sil.Aeq (Sil.Var i, e) when not (Sil.ident_in_exp i e) -> + let mysub = Sil.sub_of_list [(i, e)] in + let foot_sigma' = sigma_normalize mysub p'.foot_sigma in + let foot_pi' = a' :: pi_normalize mysub foot_sigma' p'.foot_pi in + footprint_normalize { p' with foot_pi = foot_pi'; foot_sigma = foot_sigma' } + | _ -> + footprint_normalize { p' with foot_pi = a' :: p'.foot_pi } in + if predicate_warning then (L.d_warning "dropping non-footprint "; Sil.d_atom a'; L.d_ln ()); + p'' + end + end + +(** Conjoin [exp1]=[exp2] with a symbolic heap [prop]. *) +let conjoin_eq ?(footprint = false) exp1 exp2 prop = + prop_atom_and ~footprint: footprint prop (Sil.Aeq(exp1, exp2)) + +(** Conjoin [exp1!=exp2] with a symbolic heap [prop]. *) +let conjoin_neq ?(footprint = false) exp1 exp2 prop = + prop_atom_and ~footprint: footprint prop (Sil.Aneq (exp1, exp2)) + +(** Return the spatial part *) +let get_sigma (p: 'a t) : Sil.hpred list = p.sigma + +(** Return the pure part of the footprint *) +let get_pi_footprint p = + p.foot_pi + +(** Return the spatial part of the footprint *) +let get_sigma_footprint p = + p.foot_sigma + +(** Create a [prop] without any normalization *) +let from_pi_sigma pi sigma = + { prop_emp with + pi = pi; + sigma = sigma } + +(** Reset every inst in the prop using the given map *) +let prop_reset_inst inst_map prop = + let sigma' = list_map (Sil.hpred_instmap inst_map) (get_sigma prop) in + let sigma_fp' = list_map (Sil.hpred_instmap inst_map) (get_sigma_footprint prop) in + replace_sigma_footprint sigma_fp' (replace_sigma sigma' prop) + +(** {2 Attributes} *) + +(** Return the exp and attribute marked in the atom if any, and return None otherwise *) +let atom_get_exp_attribute = function + | Sil.Aneq (Sil.Const (Sil.Cattribute att), e) + | Sil.Aneq (e, Sil.Const (Sil.Cattribute att)) -> Some (e, att) + | _ -> None + +(** Check whether an atom is used to mark an attribute *) +let atom_is_attribute a = + atom_get_exp_attribute a <> None + +(** Get the attribute associated to the expression, if any *) +let get_exp_attributes prop exp = + let nexp = exp_normalize_prop prop exp in + let atom_get_attr attributes atom = + match atom with + | Sil.Aneq (e, Sil.Const (Sil.Cattribute att)) + | Sil.Aneq (Sil.Const (Sil.Cattribute att), e) when Sil.exp_equal e nexp -> att:: attributes + | _ -> attributes in + list_fold_left atom_get_attr [] prop.pi + +let attributes_in_same_category attr1 attr2 = + let cat1 = Sil.attribute_to_category attr1 in + let cat2 = Sil.attribute_to_category attr2 in + Sil.attribute_category_equal cat1 cat2 + +let get_attribute prop exp category = + let atts = get_exp_attributes prop exp in + try Some (list_find + (fun att -> + Sil.attribute_category_equal + (Sil.attribute_to_category att) category) + atts) + with Not_found -> None + +let get_resource_undef_attribute prop exp = + get_attribute prop exp Sil.ACresource + +let get_taint_attribute prop exp = + get_attribute prop exp Sil.ACtaint + +let get_autorelease_attribute prop exp = + get_attribute prop exp Sil.ACautorelease + +let get_objc_null_attribute prop exp = + get_attribute prop exp Sil.ACobjc_null + +let get_variadic_function_argument_attribute prop exp = + get_attribute prop exp Sil.ACvariadic_function_argument + +let get_div0_attribute prop exp = + get_attribute prop exp Sil.ACdiv0 + +(** Get all the attributes of the prop *) +let get_all_attributes prop = + let res = ref [] in + let do_atom a = match atom_get_exp_attribute a with + | Some (e, att) -> res := (e, att) :: !res + | None -> () in + list_iter do_atom prop.pi; + list_rev !res + +(** Set an attribute associated to the expression *) +let set_exp_attribute prop exp att = + let exp_att = Sil.Const (Sil.Cattribute att) in + conjoin_neq exp exp_att prop + +(** Replace an attribute associated to the expression *) +let add_or_replace_exp_attribute check_attribute_change prop exp att = + let nexp = exp_normalize_prop prop exp in + let found = ref false in + let atom_map a = match a with + | Sil.Aneq (e, Sil.Const (Sil.Cattribute att_old)) + | Sil.Aneq (Sil.Const (Sil.Cattribute att_old), e) -> + if Sil.exp_equal nexp e && (attributes_in_same_category att_old att) then + begin + found := true; + check_attribute_change att_old att; + + let e1, e2 = exp_reorder e (Sil.Const (Sil.Cattribute att)) in + Sil.Aneq (e1, e2) + end + else a + | _ -> a in + let pi' = list_map atom_map (get_pi prop) in + if !found then replace_pi pi' prop + else set_exp_attribute prop nexp att + +(** Remove an attribute from all the atoms in the heap *) +let remove_attribute att prop = + let atom_remove atom pi = match atom with + | Sil.Aneq (e, Sil.Const (Sil.Cattribute att_old)) + | Sil.Aneq (Sil.Const (Sil.Cattribute att_old), e) -> + if Sil.attribute_equal att_old att then + pi + else atom:: pi + | _ -> atom:: pi in + let pi' = list_fold_right atom_remove (get_pi prop) [] in + replace_pi pi' prop + +let remove_attribute_from_exp att prop exp = + let nexp = exp_normalize_prop prop exp in + let atom_remove atom pi = match atom with + | Sil.Aneq (e, Sil.Const (Sil.Cattribute att_old)) + | Sil.Aneq (Sil.Const (Sil.Cattribute att_old), e) -> + if Sil.attribute_equal att_old att && Sil.exp_equal nexp e then + pi + else atom:: pi + | _ -> atom:: pi in + let pi' = list_fold_right atom_remove (get_pi prop) [] in + replace_pi pi' prop + +(* Replace an attribute OBJC_NULL($n1) with OBJC_NULL(var) when var = $n1, and also sets $n1 = 0 *) +let replace_objc_null prop lhs_exp rhs_exp = + match get_objc_null_attribute prop rhs_exp, rhs_exp with + | Some att, Sil.Var var -> + let prop = remove_attribute_from_exp att prop rhs_exp in + let prop = conjoin_eq rhs_exp Sil.exp_zero prop in + add_or_replace_exp_attribute (fun a1 a2 -> ()) prop lhs_exp att + | _ -> prop + +(** Get all the attributes of the prop *) +let get_atoms_with_attribute att prop = + let atom_remove atom autoreleased_atoms = match atom with + | Sil.Aneq (e, Sil.Const (Sil.Cattribute att_old)) + | Sil.Aneq (Sil.Const (Sil.Cattribute att_old), e) -> + if Sil.attribute_equal att_old att then + e:: autoreleased_atoms + else autoreleased_atoms + | _ -> autoreleased_atoms in + list_fold_right atom_remove (get_pi prop) [] + +(** Apply f to every resource attribute in the prop *) +let attribute_map_resource prop f = + let pi = get_pi prop in + let attribute_map e = function + | Sil.Aresource ra -> + Sil.Aresource (f e ra) + | att -> att in + let atom_map a = match a with + | Sil.Aneq (e, Sil.Const (Sil.Cattribute att)) + | Sil.Aneq (Sil.Const (Sil.Cattribute att), e) -> + let att' = attribute_map e att in + let e1, e2 = exp_reorder e (Sil.Const (Sil.Cattribute att')) in + Sil.Aneq (e1, e2) + | _ -> a in + let pi' = list_map atom_map pi in + replace_pi pi' prop + +(** if [atom] represents an attribute [att], add the attribure to [prop] *) +let replace_atom_attribute check_attribute_change prop atom = + match atom_get_exp_attribute atom with + | None -> prop + | Some (exp, att) -> add_or_replace_exp_attribute check_attribute_change prop exp att + +(** type for arithmetic problems *) +type arith_problem = + | Div0 of Sil.exp (* division by zero *) + | UminusUnsigned of Sil.exp * Sil.typ (* unary minus of unsigned type applied to the given expression *) + +(** Look for an arithmetic problem in [exp] *) +let find_arithmetic_problem proc_node_session prop exp = + let exps_divided = ref [] in + let uminus_unsigned = ref [] in + let res = ref prop in + let check_zero e = + match exp_normalize_prop prop e with + | Sil.Const c when iszero_int_float c -> true + | _ -> + let check_attr_change att_old att_new = () in + res := add_or_replace_exp_attribute check_attr_change !res e (Sil.Adiv0 proc_node_session); + false in + let rec walk = function + | Sil.Var _ -> () + | Sil.UnOp (Sil.Neg, e, Some ((Sil.Tint (Sil.IUChar | Sil.IUInt | Sil.IUShort | Sil.IULong | Sil.IULongLong) as typ))) -> + uminus_unsigned := (e, typ) :: !uminus_unsigned + | Sil.UnOp(_, e, _) -> walk e + | Sil.BinOp(op, e1, e2) -> + if op = Sil.Div || op = Sil.Mod then exps_divided := e2 :: !exps_divided; + walk e1; walk e2 + | Sil.Const _ -> () + | Sil.Cast (_, e) -> walk e + | Sil.Lvar _ -> () + | Sil.Lfield (e, _, _) -> walk e + | Sil.Lindex (e1, e2) -> walk e1; walk e2 + | Sil.Sizeof _ -> () in + walk exp; + try Some (Div0 (list_find check_zero !exps_divided)), !res + with Not_found -> + (match !uminus_unsigned with + | (e, t):: _ -> Some (UminusUnsigned (e, t)), !res + | _ -> None, !res) + +(** Deallocate the stack variables in [pvars], and replace them by normal variables. +Return the list of stack variables whose address was still present after deallocation. *) +let deallocate_stack_vars p pvars = + let filter = function + | Sil.Hpointsto (Sil.Lvar v, _, _) -> + list_exists (Sil.pvar_equal v) pvars + | _ -> false in + let sigma_stack, sigma_other = list_partition filter p.sigma in + let fresh_address_vars = ref [] in (* fresh vars substituted for the address of stack vars *) + let stack_vars_address_in_post = ref [] in (* stack vars whose address is still present *) + let exp_replace = list_map (function + | Sil.Hpointsto (Sil.Lvar v, _, _) -> + let freshv = Ident.create_fresh Ident.kprimed in + fresh_address_vars := (v, freshv) :: !fresh_address_vars; + (Sil.Lvar v, Sil.Var freshv) + | _ -> assert false) sigma_stack in + let pi1 = list_map (fun (id, e) -> Sil.Aeq (Sil.Var id, e)) (Sil.sub_to_list p.sub) in + let pi = list_map (Sil.atom_replace_exp exp_replace) (p.pi @ pi1) in + let p' = { p with sub = Sil.sub_empty; pi = []; sigma = sigma_replace_exp exp_replace sigma_other } in + let p'' = + let res = ref p' in + let p'_fav = prop_fav p' in + let do_var (v, freshv) = + if Sil.fav_mem p'_fav freshv then (* the address of a de-allocated stack var in in the post *) + begin + stack_vars_address_in_post := v :: !stack_vars_address_in_post; + let check_attribute_change att_old att_new = () in + res := add_or_replace_exp_attribute check_attribute_change !res (Sil.Var freshv) (Sil.Adangling Sil.DAaddr_stack_var) + end in + list_iter do_var !fresh_address_vars; + !res in + !stack_vars_address_in_post, list_fold_left prop_atom_and p'' pi + + +(** {1 Functions for transforming footprints into propositions.} *) + +(** The ones used for abstraction add/remove local stacks in order to +stop the firing of some abstraction rules. The other usual +transforation functions do not use this hack. *) + +(** Extract the footprint and return it as a prop *) +let extract_footprint p = + { prop_emp with pi = p.foot_pi; sigma = p.foot_sigma } + +(** Extract the (footprint,current) pair *) +let extract_spec p = + let pre = extract_footprint p in + let post = { p with foot_pi = []; foot_sigma = [] } in + (pre, post) + +(** [prop_set_fooprint p p_foot] sets proposition [p_foot] as footprint of [p]. *) +let prop_set_footprint p p_foot = + let pi = (list_map (fun (i, e) -> Sil.Aeq(Sil.Var i, e)) (Sil.sub_to_list p_foot.sub)) @ p_foot.pi in + { p with foot_pi = pi; foot_sigma = p_foot.sigma } + +(** {2 Functions for renaming primed variables by "canonical names"} *) + +module ExpStack : sig + val init : Sil.exp list -> unit + val final : unit -> unit + val is_empty : unit -> bool + val push : Sil.exp -> unit + val pop : unit -> Sil.exp +end = struct + let stack = Stack.create () + let init es = + Stack.clear stack; + list_iter (fun e -> Stack.push e stack) (list_rev es) + let final () = Stack.clear stack + let is_empty () = Stack.is_empty stack + let push e = Stack.push e stack + let pop () = Stack.pop stack +end + +let sigma_get_start_lexps_sort sigma = + let exp_compare_neg e1 e2 = - (Sil.exp_compare e1 e2) in + let filter e = Sil.fav_for_all (Sil.exp_fav e) Ident.is_normal in + let lexps = Sil.hpred_list_get_lexps filter sigma in + list_sort exp_compare_neg lexps + +let sigma_dfs_sort sigma = + + let init () = + let start_lexps = sigma_get_start_lexps_sort sigma in + ExpStack.init start_lexps in + + let final () = ExpStack.final () in + + let rec handle_strexp = function + | Sil.Eexp (e, inst) -> ExpStack.push e + | Sil.Estruct (fld_se_list, inst) -> + list_iter (fun (_, se) -> handle_strexp se) fld_se_list + | Sil.Earray (_, idx_se_list, inst) -> + list_iter (fun (_, se) -> handle_strexp se) idx_se_list in + + let rec handle_e visited seen e = function + | [] -> (visited, list_rev seen) + | hpred :: cur -> + begin + match hpred with + | Sil.Hpointsto (e', se, _) when Sil.exp_equal e e' -> + handle_strexp se; + (hpred:: visited, list_rev_append cur seen) + | Sil.Hlseg (_, _, root, next, shared) when Sil.exp_equal e root -> + list_iter ExpStack.push (next:: shared); + (hpred:: visited, list_rev_append cur seen) + | Sil.Hdllseg (_, _, iF, oB, oF, iB, shared) + when Sil.exp_equal e iF || Sil.exp_equal e iB -> + list_iter ExpStack.push (oB:: oF:: shared); + (hpred:: visited, list_rev_append cur seen) + | _ -> + handle_e visited (hpred:: seen) e cur + end in + + let rec handle_sigma visited = function + | [] -> list_rev visited + | cur -> + if ExpStack.is_empty () then + let cur' = sigma_normalize Sil.sub_empty cur in + list_rev_append cur' visited + else + let e = ExpStack.pop () in + let (visited', cur') = handle_e visited [] e cur in + handle_sigma visited' cur' in + + init (); + let sigma' = handle_sigma [] sigma in + final (); + sigma' + +let prop_dfs_sort p = + let sigma = get_sigma p in + let sigma' = sigma_dfs_sort sigma in + let sigma_fp = get_sigma_footprint p in + let sigma_fp' = sigma_dfs_sort sigma_fp in + let p' = { p with sigma = sigma'; foot_sigma = sigma_fp'} in + (* L.err "@[<2>P SORTED:@\n%a@\n@." pp_prop p'; *) + p' + +let prop_fav_add_dfs fav prop = + prop_fav_add fav (prop_dfs_sort prop) + +let rec strexp_get_array_indices acc = function + | Sil.Eexp _ -> acc + | Sil.Estruct (fsel, inst) -> + let se_list = list_map snd fsel in + list_fold_left strexp_get_array_indices acc se_list + | Sil.Earray (size, isel, _) -> + let acc_new = list_fold_left (fun acc' (idx, _) -> idx:: acc') acc isel in + let se_list = list_map snd isel in + list_fold_left strexp_get_array_indices acc_new se_list + +let hpred_get_array_indices acc = function + | Sil.Hpointsto (_, se, _) -> strexp_get_array_indices acc se + | Sil.Hlseg _ | Sil.Hdllseg _ -> acc + +let sigma_get_array_indices sigma = + let indices = list_fold_left hpred_get_array_indices [] sigma in + list_rev indices + +let compute_reindexing fav_add get_id_offset list = + let rec select list_passed list_seen = function + | [] -> list_passed + | x :: list_rest -> + let id_offset_opt = get_id_offset x in + let list_passed_new = match id_offset_opt with + | None -> list_passed + | Some (id, _) -> + let fav = Sil.fav_new () in + list_iter (fav_add fav) list_seen; + list_iter (fav_add fav) list_passed; + if (Sil.fav_exists fav (Ident.equal id)) + then list_passed + else (x:: list_passed) in + let list_seen_new = x:: list_seen in + select list_passed_new list_seen_new list_rest in + let list_passed = select [] [] list in + let transform x = + let id, offset = match get_id_offset x with None -> assert false | Some io -> io in + let base_new = Sil.Var (Ident.create_fresh Ident.kprimed) in + let offset_new = Sil.exp_int (Sil.Int.neg offset) in + let exp_new = Sil.BinOp(Sil.PlusA, base_new, offset_new) in + (id, exp_new) in + let reindexing = list_map transform list_passed in + Sil.sub_of_list reindexing + +let compute_reindexing_from_indices indices = + let get_id_offset = function + | Sil.BinOp (Sil.PlusA, Sil.Var id, Sil.Const(Sil.Cint offset)) -> + if Ident.is_primed id then Some (id, offset) else None + | _ -> None in + let fav_add = Sil.exp_fav_add in + compute_reindexing fav_add get_id_offset indices + +let apply_reindexing subst prop = + let nsigma = sigma_normalize subst prop.sigma in + let npi = pi_normalize subst nsigma prop.pi in + let nsub, atoms = + let dom_subst = list_map fst (Sil.sub_to_list subst) in + let in_dom_subst id = list_exists (Ident.equal id) dom_subst in + let sub' = Sil.sub_filter (fun id -> not (in_dom_subst id)) prop.sub in + let contains_substituted_id e = Sil.fav_exists (Sil.exp_fav e) in_dom_subst in + let sub_eqs, sub_keep = Sil.sub_range_partition contains_substituted_id sub' in + let eqs = Sil.sub_to_list sub_eqs in + let atoms = list_map (fun (id, e) -> Sil.Aeq (Sil.Var id, exp_normalize subst e)) eqs in + (sub_keep, atoms) in + let p' = { prop with sub = nsub; pi = npi; sigma = nsigma } in + list_fold_left prop_atom_and p' atoms + +let prop_rename_array_indices prop = + if !Config.footprint then prop + else begin + let indices = sigma_get_array_indices prop.sigma in + let not_same_base_lt_offsets e1 e2 = + match e1, e2 with + | Sil.BinOp(Sil.PlusA, e1', Sil.Const (Sil.Cint n1')), + Sil.BinOp(Sil.PlusA, e2', Sil.Const (Sil.Cint n2')) -> + not (Sil.exp_equal e1' e2' && Sil.Int.lt n1' n2') + | _ -> true in + let rec select_minimal_indices indices_seen = function + | [] -> list_rev indices_seen + | index:: indices_rest -> + let indices_seen' = list_filter (not_same_base_lt_offsets index) indices_seen in + let indices_seen_new = index:: indices_seen' in + let indices_rest_new = list_filter (not_same_base_lt_offsets index) indices_rest in + select_minimal_indices indices_seen_new indices_rest_new in + let minimal_indices = select_minimal_indices [] indices in + let subst = compute_reindexing_from_indices minimal_indices in + apply_reindexing subst prop + end + +let rec pp_ren pe f = function + | [] -> () + | [(i, x)] -> F.fprintf f "%a->%a" (Ident.pp pe) i (Ident.pp pe) x + | (i, x):: ren -> F.fprintf f "%a->%a, %a" (Ident.pp pe) i (Ident.pp pe) x (pp_ren pe) ren + +let compute_renaming fav = + let ids = Sil.fav_to_list fav in + let ids_primed, ids_nonprimed = list_partition Ident.is_primed ids in + let ids_footprint = list_filter Ident.is_footprint ids_nonprimed in + + let id_base_primed = Ident.create Ident.kprimed 0 in + let id_base_footprint = Ident.create Ident.kfootprint 0 in + + let rec f id_base index ren_subst = function + | [] -> ren_subst + | id:: ids -> + let new_id = Ident.set_stamp id_base index in + if Ident.equal id new_id then + f id_base (index + 1) ren_subst ids + else + f id_base (index + 1) ((id, new_id):: ren_subst) ids in + + let ren_primed = f id_base_primed 0 [] ids_primed in + let ren_footprint = f id_base_footprint 0 [] ids_footprint in + + ren_primed @ ren_footprint + +let rec idlist_assoc id = function + | [] -> raise Not_found + | (i, x):: l -> if Ident.equal i id then x else idlist_assoc id l + +let ident_captured_ren ren id = + try (idlist_assoc id ren) + with Not_found -> id +(* If not defined in ren, id should be mapped to itself *) + +let rec exp_captured_ren ren = function + | Sil.Var id -> Sil.Var (ident_captured_ren ren id) + | Sil.Const (Sil.Cexn e) -> Sil.Const (Sil.Cexn (exp_captured_ren ren e)) + | Sil.Const _ as e -> e + | Sil.Sizeof (t, st) -> Sil.Sizeof (typ_captured_ren ren t, st) + | Sil.Cast (t, e) -> Sil.Cast (t, exp_captured_ren ren e) + | Sil.UnOp (op, e, topt) -> + let topt' = match topt with + | Some t -> Some (typ_captured_ren ren t) + | None -> None in + Sil.UnOp (op, exp_captured_ren ren e, topt') + | Sil.BinOp (op, e1, e2) -> + let e1' = exp_captured_ren ren e1 in + let e2' = exp_captured_ren ren e2 in + Sil.BinOp (op, e1', e2') + | Sil.Lvar id -> Sil.Lvar id + | Sil.Lfield (e, fld, typ) -> Sil.Lfield (exp_captured_ren ren e, fld, typ_captured_ren ren typ) + | Sil.Lindex (e1, e2) -> + let e1' = exp_captured_ren ren e1 in + let e2' = exp_captured_ren ren e2 in + Sil.Lindex(e1', e2') + +(* Apply a renaming function to a type *) +and typ_captured_ren ren typ = match typ with + | Sil.Tvar _ + | Sil.Tint _ + | Sil.Tfloat _ + | Sil.Tvoid + | Sil.Tstruct _ + | Sil.Tfun _ -> + typ + | Sil.Tptr (t', pk) -> + Sil.Tptr (typ_captured_ren ren t', pk) + | Sil.Tarray (t, e) -> + Sil.Tarray (typ_captured_ren ren t, exp_captured_ren ren e) + | Sil.Tenum econsts -> + typ + +let atom_captured_ren ren = function + | Sil.Aeq (e1, e2) -> + Sil.Aeq (exp_captured_ren ren e1, exp_captured_ren ren e2) + | Sil.Aneq (e1, e2) -> + Sil.Aneq (exp_captured_ren ren e1, exp_captured_ren ren e2) + +let rec strexp_captured_ren ren = function + | Sil.Eexp (e, inst) -> + Sil.Eexp (exp_captured_ren ren e, inst) + | Sil.Estruct (fld_se_list, inst) -> + let f (fld, se) = (fld, strexp_captured_ren ren se) in + Sil.Estruct (list_map f fld_se_list, inst) + | Sil.Earray (size, idx_se_list, inst) -> + let f (idx, se) = + let idx' = exp_captured_ren ren idx in + (idx', strexp_captured_ren ren se) in + let size' = exp_captured_ren ren size in + Sil.Earray (size', list_map f idx_se_list, inst) + +and hpred_captured_ren ren = function + | Sil.Hpointsto (base, se, te) -> + let base' = exp_captured_ren ren base in + let se' = strexp_captured_ren ren se in + let te' = exp_captured_ren ren te in + Sil.Hpointsto (base', se', te') + | Sil.Hlseg (k, para, e1, e2, elist) -> + let para' = hpara_ren para in + let e1' = exp_captured_ren ren e1 in + let e2' = exp_captured_ren ren e2 in + let elist' = list_map (exp_captured_ren ren) elist in + Sil.Hlseg (k, para', e1', e2', elist') + | Sil.Hdllseg (k, para, e1, e2, e3, e4, elist) -> + let para' = hpara_dll_ren para in + let e1' = exp_captured_ren ren e1 in + let e2' = exp_captured_ren ren e2 in + let e3' = exp_captured_ren ren e3 in + let e4' = exp_captured_ren ren e4 in + let elist' = list_map (exp_captured_ren ren) elist in + Sil.Hdllseg (k, para', e1', e2', e3', e4', elist') + +and hpara_ren para = + let av = Sil.hpara_shallow_av para in + let ren = compute_renaming av in + let root' = ident_captured_ren ren para.Sil.root in + let next' = ident_captured_ren ren para.Sil.next in + let svars' = list_map (ident_captured_ren ren) para.Sil.svars in + let evars' = list_map (ident_captured_ren ren) para.Sil.evars in + let body' = list_map (hpred_captured_ren ren) para.Sil.body in + { Sil.root = root'; Sil.next = next'; Sil.svars = svars'; Sil.evars = evars'; Sil.body = body'} + +and hpara_dll_ren para = + let av = Sil.hpara_dll_shallow_av para in + let ren = compute_renaming av in + let iF = ident_captured_ren ren para.Sil.cell in + let oF = ident_captured_ren ren para.Sil.flink in + let oB = ident_captured_ren ren para.Sil.blink in + let svars' = list_map (ident_captured_ren ren) para.Sil.svars_dll in + let evars' = list_map (ident_captured_ren ren) para.Sil.evars_dll in + let body' = list_map (hpred_captured_ren ren) para.Sil.body_dll in + { Sil.cell = iF; Sil.flink = oF; Sil.blink = oB; Sil.svars_dll = svars'; Sil.evars_dll = evars'; Sil.body_dll = body'} + +let pi_captured_ren ren pi = + list_map (atom_captured_ren ren) pi + +let sigma_captured_ren ren sigma = + list_map (hpred_captured_ren ren) sigma + +let sub_captured_ren ren sub = + Sil.sub_map (ident_captured_ren ren) (exp_captured_ren ren) sub + +(** Canonicalize the names of primed variables and footprint vars. *) +let prop_rename_primed_footprint_vars p = + let p = prop_rename_array_indices p in + let bound_vars = + let filter id = Ident.is_footprint id || Ident.is_primed id in + let p_dfs = prop_dfs_sort p in + let fvars_in_p = prop_fav p_dfs in + Sil.fav_filter_ident fvars_in_p filter; + fvars_in_p in + let ren = compute_renaming bound_vars in + let sub' = sub_captured_ren ren p.sub in + let pi' = pi_captured_ren ren p.pi in + let sigma' = sigma_captured_ren ren p.sigma in + let foot_pi' = pi_captured_ren ren p.foot_pi in + let foot_sigma' = sigma_captured_ren ren p.foot_sigma in + + let sub_for_normalize = Sil.sub_empty in + (* It is fine to use the empty substituion during normalization + because the renaming maintains that a substitution is normalized *) + let nsub' = sub_normalize sub' in + let nsigma' = sigma_normalize sub_for_normalize sigma' in + let npi' = pi_normalize sub_for_normalize nsigma' pi' in + let p' = footprint_normalize { sub = nsub'; pi = npi'; sigma = nsigma'; foot_pi = foot_pi'; foot_sigma = foot_sigma'} in + (* + L.out "@[<2>BEFORE RENAMING:@\n"; + L.out "%a@\n@." pp_prop p; + L.out "@[<2>AFTER RENAMING:@\n"; + L.out "%a@\n@." pp_prop p'; + L.out "@[<2>RENAMING:@\n"; + L.out "%a@\n@." pp_ren ren; + *) + p' + +(** {2 Functionss for changing and generating propositions} *) + +let mem_idlist i l = + list_exists (fun id -> Ident.equal i id) l + +let id_exp_compare (id1, e1) (id2, e2) = + let n = Sil.exp_compare e1 e2 in + if n <> 0 then n + else Ident.compare id1 id2 + +let expose (p : normal t) : exposed t = Obj.magic p + +(** normalize a prop *) +let normalize (eprop : 'a t) : normal t = + let p0 = { prop_emp with sigma = sigma_normalize Sil.sub_empty eprop.sigma } in + let nprop = list_fold_left prop_atom_and p0 (get_pure eprop) in + footprint_normalize { nprop with foot_pi = eprop.foot_pi; foot_sigma = eprop.foot_sigma } + +(** Apply subsitution to prop. *) +let prop_sub subst (prop: 'a t) : exposed t = + let pi = pi_sub subst (prop.pi @ list_map (fun (x, e) -> Sil.Aeq (Sil.Var x, e)) (Sil.sub_to_list prop.sub)) in + let sigma = sigma_sub subst prop.sigma in + let fp_pi = pi_sub subst prop.foot_pi in + let fp_sigma = sigma_sub subst prop.foot_sigma in + { prop_emp with pi = pi; sigma = sigma; foot_pi = fp_pi; foot_sigma = fp_sigma } + +(** Apply renaming substitution to a proposition. *) +let prop_ren_sub (ren_sub: Sil.subst) (prop: normal t) : normal t = + normalize (prop_sub ren_sub prop) + +(** Existentially quantify the [ids] in [prop]. +[ids] should not contain any primed variables. *) +let exist_quantify fav prop = + let ids = Sil.fav_to_list fav in + if list_exists Ident.is_primed ids then assert false; (* sanity check *) + if ids == [] then prop else + let gen_fresh_id_sub id = (id, Sil.Var (Ident.create_fresh Ident.kprimed)) in + let ren_sub = Sil.sub_of_list (list_map gen_fresh_id_sub ids) in + let prop' = + let sub = Sil.sub_filter (fun i -> not (mem_idlist i ids)) prop.sub in (** throw away x=E if x becomes _x *) + if Sil.sub_equal sub prop.sub then prop + else { prop with sub = sub } in + (* + L.out "@[<2>.... Existential Quantification ....\n"; + L.out "SUB:%a\n" pp_sub prop'.sub; + L.out "PI:%a\n" pp_pi prop'.pi; + L.out "PROP:%a\n@." pp_prop prop'; + *) + prop_ren_sub ren_sub prop' + +(** Apply the substitution [fe] to all the expressions in the prop. *) +let prop_expmap (fe: Sil.exp -> Sil.exp) prop = + let f (e, sil_opt) = (fe e, sil_opt) in + let pi = list_map (Sil.atom_expmap fe) prop.pi in + let sigma = list_map (Sil.hpred_expmap f) prop.sigma in + let foot_pi = list_map (Sil.atom_expmap fe) prop.foot_pi in + let foot_sigma = list_map (Sil.hpred_expmap f) prop.foot_sigma in + { prop with pi = pi; sigma = sigma; foot_pi = foot_pi; foot_sigma = foot_sigma } + +(** convert identifiers in fav to kind [k] *) +let vars_make_unprimed fav prop = + let ids = Sil.fav_to_list fav in + let ren_sub = Sil.sub_of_list (list_map (fun i -> (i, Sil.Var (Ident.create_fresh Ident.knormal))) ids) in + prop_ren_sub ren_sub prop + +(** convert the normal vars to primed vars. *) +let prop_normal_vars_to_primed_vars p = + let fav = prop_fav p in + Sil.fav_filter_ident fav Ident.is_normal; + exist_quantify fav p + +(** Rename all primed variables fresh *) +let prop_rename_primed_fresh (p : normal t) : normal t = + let ids_primed = + let fav = prop_fav p in + let ids = Sil.fav_to_list fav in + list_filter Ident.is_primed ids in + let ren_sub = + let f i = (i, Sil.Var (Ident.create_fresh Ident.kprimed)) in + Sil.sub_of_list (list_map f ids_primed) in + prop_ren_sub ren_sub p + +(** convert the primed vars to normal vars. *) +let prop_primed_vars_to_normal_vars (p : normal t) : normal t = + let fav = prop_fav p in + Sil.fav_filter_ident fav Ident.is_primed; + vars_make_unprimed fav p + +let from_pi pi = { prop_emp with pi = pi } + +let from_sigma sigma = { prop_emp with sigma = sigma } + +let replace_sub sub eprop = + { eprop with sub = sub } + +(** Rename free variables in a prop replacing them with existentially quantified vars *) +let prop_rename_fav_with_existentials (p : normal t) : normal t = + let fav = Sil.fav_new () in + prop_fav_add fav p; + let ids = Sil.fav_to_list fav in + let ids' = list_map (fun i -> (i, Ident.create_fresh Ident.kprimed)) ids in + let ren_sub = Sil.sub_of_list (list_map (fun (i, i') -> (i, Sil.Var i')) ids') in + let p' = prop_sub ren_sub p in + (*L.d_strln "Prop after renaming:"; d_prop p'; L.d_strln "";*) + normalize p' + +(** {2 Prop iterators} *) + +(** Iterator state over sigma. *) +type 'a prop_iter = + { pit_sub : Sil.subst; (** substitution for equalities *) + pit_pi : Sil.atom list; (** pure part *) + pit_newpi : (bool * Sil.atom) list; (** newly added atoms. *) + (** The first records !Config.footprint. *) + pit_old : Sil.hpred list; (** sigma already visited *) + pit_curr : Sil.hpred; (** current element *) + pit_state : 'a; (** state of current element *) + pit_new : Sil.hpred list; (** sigma not yet visited *) + pit_foot_pi : Sil.atom list; (** pure part of the footprint *) + pit_foot_sigma : Sil.hpred list; (** sigma part of the footprint *) + } + +let prop_iter_create prop = + match prop.sigma with + | hpred:: sigma' -> Some + { pit_sub = prop.sub; + pit_pi = prop.pi; + pit_newpi = []; + pit_old = []; + pit_curr = hpred; + pit_state = (); + pit_new = sigma'; + pit_foot_pi = prop.foot_pi; + pit_foot_sigma = prop.foot_sigma } + | _ -> None + +(** Return the prop associated to the iterator. *) +let prop_iter_to_prop iter = + let sigma = list_rev_append iter.pit_old (iter.pit_curr:: iter.pit_new) in + let prop = + normalize + { sub = iter.pit_sub; pi = iter.pit_pi; sigma = sigma; foot_pi = iter.pit_foot_pi; foot_sigma = iter.pit_foot_sigma } in + list_fold_left + (fun p (footprint, atom) -> prop_atom_and ~footprint: footprint p atom) + prop iter.pit_newpi + +(** Add an atom to the pi part of prop iter. The +first parameter records whether it is done +during footprint or during re - execution. *) +let prop_iter_add_atom footprint iter atom = + { iter with pit_newpi = (footprint, atom):: iter.pit_newpi } + +(** Remove the current element of the iterator, and return the prop +associated to the resulting iterator *) +let prop_iter_remove_curr_then_to_prop iter = + let sigma = list_rev_append iter.pit_old iter.pit_new in + let normalized_sigma = sigma_normalize iter.pit_sub sigma in + { sub = iter.pit_sub; + pi = iter.pit_pi; + sigma = normalized_sigma; + foot_pi = iter.pit_foot_pi; + foot_sigma = iter.pit_foot_sigma } + +(** Return the current hpred and state. *) +let prop_iter_current iter = + let curr = hpred_normalize iter.pit_sub iter.pit_curr in + let prop = { prop_emp with sigma = [curr] } in + let prop' = + list_fold_left + (fun p (footprint, atom) -> prop_atom_and ~footprint: footprint p atom) + prop iter.pit_newpi in + match prop'.sigma with + | [curr'] -> (curr', iter.pit_state) + | _ -> assert false + +(** Update the current element of the iterator. *) +let prop_iter_update_current iter hpred = + { iter with pit_curr = hpred } + +(** Update the current element of the iterator by a nonempty list of elements. *) +let prop_iter_update_current_by_list iter = function + | [] -> assert false (* the list should be nonempty *) + | hpred:: hpred_list -> + let pit_new' = hpred_list@iter.pit_new in + { iter with pit_curr = hpred; pit_state = (); pit_new = pit_new'} + +let prop_iter_next iter = + match iter.pit_new with + | [] -> None + | hpred':: new' -> Some + { iter with + pit_old = iter.pit_curr:: iter.pit_old; + pit_curr = hpred'; + pit_state = (); + pit_new = new'} + +let prop_iter_remove_curr_then_next iter = + match iter.pit_new with + | [] -> None + | hpred':: new' -> Some + { iter with + pit_old = iter.pit_old; + pit_curr = hpred'; + pit_state = (); + pit_new = new'} + +(** Insert before the current element of the iterator. *) +let prop_iter_prev_then_insert iter hpred = + { iter with + pit_new = iter.pit_curr:: iter.pit_new; + pit_curr = hpred } + +(** Scan sigma to find an [hpred] satisfying the filter function. *) +let rec prop_iter_find iter filter = + match filter iter.pit_curr with + | Some st -> Some { iter with pit_state = st } + | None -> + (match prop_iter_next iter with + | None -> None + | Some iter' -> prop_iter_find iter' filter) + +(** Set the state of the iterator *) +let prop_iter_set_state iter state = + { iter with pit_state = state } + +let prop_iter_make_id_primed id iter = + let pid = Ident.create_fresh Ident.kprimed in + let sub_id = Sil.sub_of_list [(id, Sil.Var pid)] in + + let normalize (id, e) = + let eq' = Sil.Aeq(Sil.exp_sub sub_id (Sil.Var id), Sil.exp_sub sub_id e) in + atom_normalize Sil.sub_empty eq' in + + let rec split pairs_unpid pairs_pid = function + | [] -> (list_rev pairs_unpid, list_rev pairs_pid) + | eq:: eqs_cur -> + begin + match eq with + | Sil.Aeq (Sil.Var id1, e1) when Sil.ident_in_exp id1 e1 -> + L.out "@[<2>#### ERROR: an assumption of the analyzer broken ####@\n"; + L.out "Broken Assumption: id notin e for all (id,e) in sub@\n"; + L.out "(id,e) : (%a,%a)@\n" (Ident.pp pe_text) id1 (Sil.pp_exp pe_text) e1; + L.out "PROP : %a@\n@." (pp_prop pe_text) (prop_iter_to_prop iter); + assert false + | Sil.Aeq (Sil.Var id1, e1) when Ident.equal pid id1 -> + split pairs_unpid ((id1, e1):: pairs_pid) eqs_cur + | Sil.Aeq (Sil.Var id1, e1) -> + split ((id1, e1):: pairs_unpid) pairs_pid eqs_cur + | _ -> + assert false + end in + + let rec get_eqs acc = function + | [] | [_] -> + list_rev acc + | (_, e1) :: (((_, e2) :: pairs') as pairs) -> + get_eqs (Sil.Aeq(e1, e2):: acc) pairs in + + let sub_new, sub_use, eqs_add = + let eqs = list_map normalize (Sil.sub_to_list iter.pit_sub) in + let pairs_unpid, pairs_pid = split [] [] eqs in + match pairs_pid with + | [] -> + let sub_unpid = Sil.sub_of_list pairs_unpid in + let pairs = (id, Sil.Var pid) :: pairs_unpid in + sub_unpid, Sil.sub_of_list pairs, [] + | (id1, e1):: _ -> + let sub_id1 = Sil.sub_of_list [(id1, e1)] in + let pairs_unpid' = + list_map (fun (id', e') -> (id', Sil.exp_sub sub_id1 e')) pairs_unpid in + let sub_unpid = Sil.sub_of_list pairs_unpid' in + let pairs = (id, e1) :: pairs_unpid' in + sub_unpid, Sil.sub_of_list pairs, get_eqs [] pairs_pid in + let nsub_new = sub_normalize sub_new in + + { iter with + pit_sub = nsub_new; + pit_pi = pi_sub sub_use (iter.pit_pi @ eqs_add); + pit_old = sigma_sub sub_use iter.pit_old; + pit_curr = Sil.hpred_sub sub_use iter.pit_curr; + pit_new = sigma_sub sub_use iter.pit_new } + +let prop_iter_footprint_fav_add fav iter = + sigma_fav_add fav iter.pit_foot_sigma; + pi_fav_add fav iter.pit_foot_pi + +(** Find fav of the footprint part of the iterator *) +let prop_iter_footprint_fav iter = + Sil.fav_imperative_to_functional prop_iter_footprint_fav_add iter + +let prop_iter_fav_add fav iter = + Sil.sub_fav_add fav iter.pit_sub; + pi_fav_add fav iter.pit_pi; + pi_fav_add fav (list_map snd iter.pit_newpi); + sigma_fav_add fav iter.pit_old; + sigma_fav_add fav iter.pit_new; + Sil.hpred_fav_add fav iter.pit_curr; + prop_iter_footprint_fav_add fav iter + +(** Find fav of the iterator *) +let prop_iter_fav iter = + Sil.fav_imperative_to_functional prop_iter_fav_add iter + +(** Free vars of the iterator except the current hpred (and footprint). *) +let prop_iter_noncurr_fav_add fav iter = + sigma_fav_add fav iter.pit_old; + sigma_fav_add fav iter.pit_new; + Sil.sub_fav_add fav iter.pit_sub; + pi_fav_add fav iter.pit_pi + +(** Extract the sigma part of the footprint *) +let prop_iter_get_footprint_sigma iter = + iter.pit_foot_sigma + +(** Replace the sigma part of the footprint *) +let prop_iter_replace_footprint_sigma iter sigma = + { iter with pit_foot_sigma = sigma } + +let prop_iter_noncurr_fav iter = + Sil.fav_imperative_to_functional prop_iter_noncurr_fav_add iter + +let rec strexp_gc_fields (fav: Sil.fav) se = + match se with + | Sil.Eexp _ -> + Some se + | Sil.Estruct (fsel, inst) -> + let fselo = list_map (fun (f, se) -> (f, strexp_gc_fields fav se)) fsel in + let fsel' = + let fselo' = list_filter (function | (_, Some _) -> true | _ -> false) fselo in + list_map (function (f, seo) -> (f, unSome seo)) fselo' in + if Sil.fld_strexp_list_compare fsel fsel' = 0 then Some se + else Some (Sil.Estruct (fsel', inst)) + | Sil.Earray _ -> + Some se + +let hpred_gc_fields (fav: Sil.fav) hpred = match hpred with + | Sil.Hpointsto (e, se, te) -> + Sil.exp_fav_add fav e; + Sil.exp_fav_add fav te; + (match strexp_gc_fields fav se with + | None -> hpred + | Some se' -> + if Sil.strexp_compare se se' = 0 then hpred + else Sil.Hpointsto (e, se', te)) + | Sil.Hlseg _ | Sil.Hdllseg _ -> + hpred + +let rec prop_iter_map f iter = + let hpred_curr = f iter in + let iter' = { iter with pit_curr = hpred_curr } in + match prop_iter_next iter' with + | None -> iter' + | Some iter'' -> prop_iter_map f iter'' + +(** Collect garbage fields. *) +let prop_iter_gc_fields iter = + let f iter' = + let fav = prop_iter_noncurr_fav iter' in + hpred_gc_fields fav iter'.pit_curr in + prop_iter_map f iter + +let prop_case_split prop = + let pi_sigma_list = Sil.sigma_to_sigma_ne prop.sigma in + let f props_acc (pi, sigma) = + let sigma' = sigma_normalize_prop prop sigma in + let prop' = { prop with sigma = sigma' } in + (list_fold_left prop_atom_and prop' pi):: props_acc in + list_fold_left f [] pi_sigma_list + +(** Raise an exception if the prop is not normalized *) +let check_prop_normalized prop = + let sigma' = sigma_normalize_prop prop prop.sigma in + if sigma_equal prop.sigma sigma' == false then assert false + +let prop_expand prop = + (* + let _ = check_prop_normalized prop in + *) + prop_case_split prop + +let mk_nondet il1 il2 loc = + Sil.Stackop (Sil.Push, loc) :: (* save initial state *) + il1 @ (* compute result1 *) + [Sil.Stackop (Sil.Swap, loc)] @ (* save result1 and restore initial state *) + il2 @ (* compute result2 *) + [Sil.Stackop (Sil.Pop, loc)] (* combine result1 and result2 *) + +(** translate a logical and/or operation taking care of the non-strict semantics for side effects *) +let trans_land_lor op ((idl1, stml1), e1) ((idl2, stml2), e2) loc = + let no_side_effects stml = + stml = [] in + if no_side_effects stml2 then + ((idl1@idl2, stml1@stml2), Sil.BinOp(op, e1, e2)) + else + begin + let id = Ident.create_fresh Ident.knormal in + let prune_instr1, prune_res1, prune_instr2, prune_res2 = + let cond1, cond2, res = match op with + | Sil.LAnd -> e1, Sil.UnOp(Sil.LNot, e1, None), Sil.Int.zero + | Sil.LOr -> Sil.UnOp(Sil.LNot, e1, None), e1, Sil.Int.one + | _ -> assert false in + let cond_res1 = Sil.BinOp(Sil.Eq, Sil.Var id, e2) in + let cond_res2 = Sil.BinOp(Sil.Eq, Sil.Var id, Sil.exp_int res) in + let mk_prune cond = Sil.Prune (cond, loc, true, Sil.Ik_land_lor) (* don't report always true/false *) in + mk_prune cond1, mk_prune cond_res1, mk_prune cond2, mk_prune cond_res2 in + let instrs2 = mk_nondet (prune_instr1 :: stml2 @ [prune_res1]) ([prune_instr2; prune_res2]) loc in + ((id:: idl1@idl2, stml1@instrs2), Sil.Var id) + end + +(** Input of this mehtod is an exp in a prop. Output is a formal variable or path from a +formal variable that is equal to the expression, or the OBJC_NULL attribute of the expression. *) +let find_equal_formal_path e prop = + let rec find_in_sigma e seen_hpreds = + list_fold_right ( + fun hpred res -> + if list_mem Sil.hpred_equal hpred seen_hpreds then None + else + let seen_hpreds = hpred :: seen_hpreds in + match res with + | Some _ -> res + | None -> + match hpred with + | Sil.Hpointsto (Sil.Lvar pvar1, Sil.Eexp (exp2, Sil.Iformal(_, _) ), _) + when Sil.exp_equal exp2 e && (Sil.pvar_is_local pvar1 || Sil.pvar_is_seed pvar1) -> + Some (Sil.Lvar pvar1) + | Sil.Hpointsto (exp1, Sil.Estruct (fields, _), _) -> + list_fold_right (fun (field, strexp) res -> + match res with + | Some _ -> res + | None -> + match strexp with + | Sil.Eexp (exp2, _) when Sil.exp_equal exp2 e -> + (match find_in_sigma exp1 seen_hpreds with + | Some exp' -> Some (Sil.Lfield (exp', field, Sil.Tvoid)) + | None -> None) + | _ -> None) fields None + | _ -> None) (get_sigma prop) None in + match find_in_sigma e [] with + | Some res -> Some res + | None -> match get_objc_null_attribute prop e with + | Some (Sil.Aobjc_null exp) -> Some exp + | _ -> None + +(** translate an if-then-else expression *) +let trans_if_then_else ((idl1, stml1), e1) ((idl2, stml2), e2) ((idl3, stml3), e3) loc = + match sym_eval false e1 with + | Sil.Const (Sil.Cint i) when Sil.Int.iszero i -> (idl1@idl3, stml1@stml3), e3 + | Sil.Const (Sil.Cint _) -> (idl1@idl2, stml1@stml2), e2 + | _ -> + let e1not = Sil.UnOp(Sil.LNot, e1, None) in + let id = Ident.create_fresh Ident.knormal in + let mk_prune_res e = + let mk_prune cond = Sil.Prune (cond, loc, true, Sil.Ik_land_lor) (* don't report always true/false *) in + mk_prune (Sil.BinOp(Sil.Eq, Sil.Var id, e)) in + let prune1 = Sil.Prune (e1, loc, true, Sil.Ik_bexp) in + let prune1not = Sil.Prune (e1not, loc, false, Sil.Ik_bexp) in + let stml' = mk_nondet (prune1 :: stml2 @ [mk_prune_res e2]) (prune1not :: stml3 @ [mk_prune_res e3]) loc in + (id:: idl1@idl2@idl3, stml1@stml'), Sil.Var id + +(*** START of module Metrics ***) +module Metrics : sig + val prop_size : 'a t -> int + val prop_chain_size : 'a t -> int +end = struct + let ptsto_weight = 1 + and lseg_weight = 3 + and pi_weight = 1 + + let rec hpara_size hpara = sigma_size hpara.Sil.body + + and hpara_dll_size hpara_dll = sigma_size hpara_dll.Sil.body_dll + + and hpred_size = function + | Sil.Hpointsto _ -> ptsto_weight + | Sil.Hlseg (_, hpara, _, _, _) -> lseg_weight * hpara_size hpara + | Sil.Hdllseg (_, hpara_dll, _, _, _, _, _) -> lseg_weight * hpara_dll_size hpara_dll + + and sigma_size sigma = + let size = ref 0 in + list_iter (fun hpred -> size := hpred_size hpred + !size) sigma; !size + + let pi_size pi = pi_weight * list_length pi + + module ExpMap = + Map.Make (struct + type t = Sil.exp + let compare = Sil.exp_compare end) + + (** Approximate the size of the longest chain by counting the max + number of |-> with the same type and whose lhs is primed or + footprint *) + let sigma_chain_size sigma = + let tbl = ref ExpMap.empty in + let add t = + try + let count = ExpMap.find t !tbl in + tbl := ExpMap.add t (count + 1) !tbl + with + | Not_found -> + tbl := ExpMap.add t 1 !tbl in + let process_hpred = function + | Sil.Hpointsto (e, _, te) -> + (match e with + | Sil.Var id when Ident.is_primed id || Ident.is_footprint id -> add te + | _ -> ()) + | Sil.Hlseg _ | Sil.Hdllseg _ -> () in + list_iter process_hpred sigma; + let size = ref 0 in + ExpMap.iter (fun t n -> size := max n !size) !tbl; + !size + + (** Compute a size value for the prop, which indicates its + complexity *) + let prop_size p = + let size_current = sigma_size p.sigma in + let size_footprint = sigma_size p.foot_sigma in + max size_current size_footprint + + (** Approximate the size of the longest chain by counting the max + number of |-> with the same type and whose lhs is primed or + footprint *) + let prop_chain_size p = + let fp_size = pi_size p.foot_pi + sigma_size p.foot_sigma in + pi_size p.pi + sigma_size p.sigma + fp_size +end +(*** END of module Metrics ***) + +module CategorizePreconditions = struct + type pre_category = + | NoPres (* no preconditions *) + | Empty (* the preconditions impose no restrictions *) + | OnlyAllocation (* the preconditions only demand that some pointers are allocated *) + | DataConstraints (* the preconditions impose constraints on the values of variables and/or memory *) + + (** categorize a list of preconditions *) + let categorize preconditions = + let lhs_is_lvar = function + | Sil.Lvar _ -> true + | _ -> false in + let lhs_is_var_lvar = function + | Sil.Var _ -> true + | Sil.Lvar _ -> true + | _ -> false in + let rhs_is_var = function + | Sil.Eexp (Sil.Var _, _) -> true + | _ -> false in + let rec rhs_only_vars = function + | Sil.Eexp (Sil.Var _, _) -> true + | Sil.Estruct (fsel, _) -> + list_for_all (fun (_, se) -> rhs_only_vars se) fsel + | Sil.Earray _ -> true + | _ -> false in + let hpred_is_var = function (* stack variable with no constraints *) + | Sil.Hpointsto (e, se, _) -> + lhs_is_lvar e && rhs_is_var se + | _ -> false in + let hpred_only_allocation = function (* only constraint is allocation *) + | Sil.Hpointsto (e, se, _) -> + lhs_is_var_lvar e && rhs_only_vars se + | _ -> false in + let check_pre hpred_filter pre = + let check_pi pi = + pi = [] in + let check_sigma sigma = + list_for_all hpred_filter sigma in + check_pi (get_pi pre) && check_sigma (get_sigma pre) in + let pres_no_constraints = list_filter (check_pre hpred_is_var) preconditions in + let pres_only_allocation = list_filter (check_pre hpred_only_allocation) preconditions in + match preconditions, pres_no_constraints, pres_only_allocation with + | [], _, _ -> + NoPres + | _:: _, _:: _, _ -> + Empty + | _:: _, [], _:: _ -> + OnlyAllocation + | _:: _, [], [] -> + DataConstraints +end diff --git a/infer/src/backend/prop.mli b/infer/src/backend/prop.mli new file mode 100644 index 000000000..0d84692de --- /dev/null +++ b/infer/src/backend/prop.mli @@ -0,0 +1,478 @@ +(* +* Copyright (c) 2009-2013 Monoidics ltd. +* Copyright (c) 2013- Facebook. +* All rights reserved. +*) + +(** Functions for Propositions (i.e., Symbolic Heaps) *) + +open Utils +open Sil + +type normal (** kind for normal props, i.e. normalized *) +type exposed (** kind for exposed props *) + +(** Proposition. *) +type 'a t (** the kind 'a should range over [normal] and [exposed] *) + +(** type to describe different strategies for initializing fields of a structure. [No_init] does not +initialize any fields of the struct. [Fld_init] initializes the fields of the struct with fresh +variables (C) or default values (Java). *) +type struct_init_mode = + | No_init + | Fld_init + +exception Cannot_star of ml_location + +(** {2 Basic Functions for propositions} *) + +(** Compare propositions *) +val prop_compare : 'a t -> 'a t -> int + +(** Check the equality of two sigma's *) +val sigma_equal : Sil.hpred list -> Sil.hpred list -> bool + +(** Check the equality of two propositions *) +val prop_equal : 'a t -> 'a t -> bool + +(** Pretty print a substitution. *) +val pp_sub : printenv -> Format.formatter -> subst -> unit + +(** Dump a substitution. *) +val d_sub : subst -> unit + +(** Pretty print a pi. *) +val pp_pi : printenv -> Format.formatter -> Sil.atom list -> unit + +(** Dump a pi. *) +val d_pi : Sil.atom list -> unit + +(** Pretty print a sigma. *) +val pp_sigma : printenv -> Format.formatter -> Sil.hpred list -> unit + +(** Dump a sigma. *) +val d_sigma : Sil.hpred list -> unit + +(** Dump a pi and a sigma *) +val d_pi_sigma: Sil.atom list -> Sil.hpred list -> unit + +(** Split sigma into stack and nonstack parts. +The boolean indicates whether the stack should only include local variales. *) +val sigma_get_stack_nonstack : bool -> Sil.hpred list -> Sil.hpred list * Sil.hpred list + +(** Update the object substitution given the stack variables in the prop *) +val prop_update_obj_sub : printenv -> 'a t -> printenv + +(** Pretty print a proposition. *) +val pp_prop : printenv -> Format.formatter -> 'a t -> unit + +(** Pretty print a proposition with type information *) +val pp_prop_with_typ : printenv -> Format.formatter -> normal t -> unit + +(** Create a predicate environment for a prop *) +val prop_pred_env : 'a t -> Sil.Predicates.env + +(** Dump a proposition. *) +val d_prop : 'a t -> unit + +(** Dump a proposition with type information *) +val d_prop_with_typ : 'a t -> unit + +(** Pretty print a list propositions with type information *) +val pp_proplist_with_typ : printenv -> Format.formatter -> normal t list -> unit + +val d_proplist_with_typ : 'a t list -> unit + +(** Compute free non-program variables of pi *) +val pi_fav : atom list -> fav + +val pi_fav_add : fav -> atom list -> unit + +(** Compute free non-program variables of sigma *) +val sigma_fav_add : fav -> hpred list -> unit + +val sigma_fav : hpred list -> fav + +(** returns free non-program variables that are used to express +the contents of stack variables *) +val sigma_fav_in_pvars_add : fav -> hpred list -> unit + +(** Compute free non-program variables of prop *) +val prop_fav_add : fav -> 'a t -> unit + +(** Compute free non-program variables of prop, visited in depth first order *) +val prop_fav_add_dfs : fav -> 'a t -> unit + +val prop_fav: normal t -> fav + +(** free vars, except pi and sub, of current and footprint parts *) +val prop_fav_nonpure : normal t -> fav + +(** Find fav of the footprint part of the prop *) +val prop_footprint_fav : 'a t -> fav + +(** Compute all the free program variables in the prop *) +val prop_fpv: 'a t -> Sil.pvar list + +(** Apply substitution for pi *) +val pi_sub : subst -> atom list -> atom list + +(** Apply subsitution for sigma *) +val sigma_sub : subst -> hpred list -> hpred list + +(** Apply subsitution to prop. Result is not normalized. *) +val prop_sub : subst -> 'a t -> exposed t + +(** Apply the substitution to all the expressions in the prop. *) +val prop_expmap : (Sil.exp -> Sil.exp) -> 'a t -> exposed t + +(** Relaces all expressions in the [hpred list] using the first argument. +Assume that the first parameter defines a partial function. +No expressions inside hpara are replaced. *) +val sigma_replace_exp : (exp * exp) list -> hpred list -> hpred list + +val sigma_map : 'a t -> (hpred -> hpred) -> 'a t + +(** {2 Normalization} *) + +(** Turn an inequality expression into an atom *) +val mk_inequality : Sil.exp -> Sil.atom + +(** Return [true] if the atom is an inequality *) +val atom_is_inequality : Sil.atom -> bool + +(** If the atom is [e<=n] return [e,n] *) +val atom_exp_le_const : Sil.atom -> (Sil.exp * Sil.Int.t) option + +(** If the atom is [n (Sil.Int.t * Sil.exp) option + +(** Negate an atom *) +val atom_negate : Sil.atom -> Sil.atom + +(** type for arithmetic problems *) +type arith_problem = + | Div0 of Sil.exp (* division by zero *) + | UminusUnsigned of Sil.exp * Sil.typ (* unary minus of unsigned type applied to the given expression *) + +(** Look for an arithmetic problem in [exp] *) +val find_arithmetic_problem : path_pos -> normal t -> Sil.exp -> arith_problem option * normal t + +(** Normalize [exp] using the pure part of [prop]. Later, we should +change this such that the normalization exposes offsets of [exp] +as much as possible. *) +val exp_normalize_prop : 'a t -> Sil.exp -> Sil.exp + +(** Normalize the expression without abstracting complex subexpressions *) +val exp_normalize_noabs : Sil.subst -> Sil.exp -> Sil.exp + +(** Collapse consecutive indices that should be added. For instance, +this function reduces x[1][1] to x[2]. The [typ] argument is used +to ensure the soundness of this collapsing. *) +val exp_collapse_consecutive_indices_prop : 'a t -> Sil.typ -> Sil.exp -> Sil.exp + +(** Normalize [exp] used for the address of a heap cell. +This normalization does not combine two offsets inside [exp]. *) +val lexp_normalize_prop : 'a t -> exp -> exp + +val atom_normalize_prop : 'a t -> atom -> atom + +val strexp_normalize_prop : 'a t -> strexp -> strexp + +val hpred_normalize_prop : 'a t -> hpred -> hpred + +val sigma_normalize_prop : 'a t -> hpred list -> hpred list + +val pi_normalize_prop : 'a t -> atom list -> atom list + +(** normalize a prop *) +val normalize : exposed t -> normal t + +(** expose a prop, no-op used to instantiate the sub-type relation *) +val expose : normal t -> exposed t + +(** {2 Compaction} *) +(** Return a compact representation of the prop *) +val prop_compact : sharing_env -> normal t -> normal t + +(** {2 Queries about propositions} *) + +(** Check if the sigma part of the proposition is emp *) +val prop_is_emp : 'a t -> bool + +(** {2 Functions for changing and generating propositions} *) + +(** Construct a disequality. *) +val mk_neq : exp -> exp -> atom + +(** Construct an equality. *) +val mk_eq : exp -> exp -> atom + +(** create a strexp of the given type, populating the structures if [expand_structs] is true *) +val create_strexp_of_type: Sil.tenv option -> struct_init_mode -> Sil.typ -> Sil.inst -> Sil.strexp + +(** Construct a pointsto. *) +val mk_ptsto : exp -> strexp -> exp -> hpred + +(** Construct a points-to predicate for an expression using either the provided expression [name] as +base for fresh identifiers. *) +val mk_ptsto_exp : Sil.tenv option -> struct_init_mode -> exp * exp * exp option -> Sil.inst -> hpred + +(** Construct a points-to predicate for a single program variable. +If [expand_structs] is true, initialize the fields of structs with fresh variables. *) +val mk_ptsto_lvar : Sil.tenv option -> struct_init_mode -> Sil.inst -> pvar * exp * exp option -> hpred + +(** Construct a lseg predicate *) +val mk_lseg : lseg_kind -> hpara -> exp -> exp -> exp list -> hpred + +(** Construct a dllseg predicate *) +val mk_dllseg : lseg_kind -> hpara_dll -> exp -> exp -> exp -> exp -> exp list -> hpred + +(** Construct a hpara *) +val mk_hpara : Ident.t -> Ident.t -> Ident.t list -> Ident.t list -> hpred list -> hpara + +(** Construct a dll_hpara *) +val mk_dll_hpara : Ident.t -> Ident.t -> Ident.t -> Ident.t list -> Ident.t list -> hpred list -> hpara_dll + +(** Proposition [true /\ emp]. *) +val prop_emp : normal t + +(** Reset every inst in the prop using the given map *) +val prop_reset_inst : (Sil.inst -> Sil.inst) -> 'a t -> exposed t + +(** Conjoin a heap predicate by separating conjunction. *) +val prop_hpred_star : 'a t -> hpred -> exposed t + +(** Conjoin a list of heap predicates by separating conjunction *) +val prop_sigma_star : 'a t -> hpred list -> exposed t + +(** Conjoin a pure atomic predicate by normal conjunction. *) +val prop_atom_and : ?footprint: bool -> normal t -> atom -> normal t + +(** Conjoin [exp1]=[exp2] with a symbolic heap [prop]. *) +val conjoin_eq : ?footprint: bool -> exp -> exp -> normal t -> normal t + +(** Conjoin [exp1]!=[exp2] with a symbolic heap [prop]. *) +val conjoin_neq : ?footprint: bool -> exp -> exp -> normal t -> normal t + +(** Check whether an atom is used to mark an attribute *) +val atom_is_attribute : atom -> bool + +(** Apply f to every resource attribute in the prop *) +val attribute_map_resource : normal t -> (Sil.exp -> Sil.res_action -> Sil.res_action) -> normal t + +(** Return the exp and attribute marked in the atom if any, and return None otherwise *) +val atom_get_exp_attribute : atom -> (Sil.exp * Sil.attribute) option + +(** Get the attributes associated to the expression, if any *) +val get_exp_attributes : 'a t -> exp -> attribute list + +(** Get the resource attribute associated to the expression, if any, or undefined *) +val get_resource_undef_attribute : 'a t -> exp -> attribute option + +(** Get the taint attribute associated to the expression, if any *) +val get_taint_attribute : 'a t -> exp -> attribute option + +(** Get the autorelease attribute associated to the expression, if any *) +val get_autorelease_attribute : 'a t -> exp -> attribute option + +(** Get the div0 attribute associated to the expression, if any *) +val get_div0_attribute : 'a t -> exp -> attribute option + +(** Get the objc null attribute associated to the expression, if any *) +val get_objc_null_attribute : 'a t -> exp -> attribute option + +(** Get the variadic function argument attribute associated to the expression, if any *) +val get_variadic_function_argument_attribute : 'a t -> exp -> attribute option + +(** Get all the attributes of the prop *) +val get_all_attributes : 'a t -> (exp * attribute) list + +(** Replace an attribute associated to the expression *) +val add_or_replace_exp_attribute : (Sil.attribute -> Sil.attribute -> unit) -> normal t -> exp -> attribute -> normal t + +(** Remove an attribute from all the atoms in the heap *) +val remove_attribute : Sil.attribute -> 'a t -> normal t + +(** [replace_objc_null lhs rhs]. If rhs has the objc_null attribute, replace the attribute and set the lhs = 0 *) +val replace_objc_null : normal t -> exp -> exp -> normal t + +(** Remove an attribute from an exp in the heap *) +val remove_attribute_from_exp : Sil.attribute -> 'a t -> exp -> normal t + +(** Retireve all the atoms in the heap that contain a specific attribute *) +val get_atoms_with_attribute : Sil.attribute -> 'a t -> Sil.exp list + +(** if [atom] represents an attribute [att], add the attribure to [prop] *) +val replace_atom_attribute : (Sil.attribute -> Sil.attribute -> unit) -> normal t -> atom -> normal t + +(** Return the sub part of [prop]. *) +val get_sub : 'a t -> subst + +(** Return the pi part of [prop]. *) +val get_pi : 'a t -> atom list + +(** Return the pure part of [prop]. *) +val get_pure : 'a t -> atom list + +(** Return the sigma part of [prop] *) +val get_sigma : 'a t -> hpred list + +(** Return the pi part of the footprint of [prop] *) +val get_pi_footprint : 'a t -> atom list + +(** Return the sigma part of the footprint of [prop] *) +val get_sigma_footprint : 'a t -> hpred list + +(** Deallocate the stack variables in [pvars], and replace them by normal variables. +Return the list of stack variables whose address was still present after deallocation. *) +val deallocate_stack_vars : normal t -> pvar list -> Sil.pvar list * normal t + +(** Canonicalize the names of primed variables. *) +val prop_rename_primed_footprint_vars : normal t -> normal t + +(** Extract the footprint and return it as a prop *) +val extract_footprint : 'a t -> exposed t + +(** Extract the (footprint,current) pair *) +val extract_spec : normal t -> (normal t * normal t) + +(** [prop_set_fooprint p p_foot] sets proposition [p_foot] as footprint of [p]. *) +val prop_set_footprint : 'a t -> 'b t -> exposed t + +(** Expand PE listsegs if the flag is on. *) +val prop_expand : normal t -> normal t list + +(** translate a logical and/or operation taking care of the non-strict semantics for side effects *) +val trans_land_lor : Sil.binop -> (Ident.t list * Sil.instr list) * Sil.exp -> (Ident.t list * Sil.instr list) * Sil.exp -> Sil.location -> (Ident.t list * Sil.instr list) * Sil.exp + +(** translate an if-then-else expression *) +val trans_if_then_else : (Ident.t list * Sil.instr list) * Sil.exp -> (Ident.t list * Sil.instr list) * Sil.exp -> (Ident.t list * Sil.instr list) * Sil.exp -> Sil.location -> (Ident.t list * Sil.instr list) * Sil.exp + +(** {2 Functions for existentially quantifying and unquantifying variables} *) + +(** Existentially quantify the [ids] in [prop]. *) +val exist_quantify : fav -> normal t -> normal t + +(** convert the footprint vars to primed vars. *) +val prop_normal_vars_to_primed_vars : normal t -> normal t + +(** convert the primed vars to normal vars. *) +val prop_primed_vars_to_normal_vars : normal t -> normal t + +(** Rename all primed variables. *) +val prop_rename_primed_fresh : normal t -> normal t + +(** Build an exposed prop from pi *) +val from_pi : Sil.atom list -> exposed t + +(** Build an exposed prop from sigma *) +val from_sigma : Sil.hpred list -> exposed t + +(** Build an exposed prop from pi and sigma *) +val from_pi_sigma : atom list -> hpred list -> exposed t + +(** Replace the substitution part of a prop *) +val replace_sub : Sil.subst -> 'a t -> exposed t + +(** Replace the pi part of a prop *) +val replace_pi : Sil.atom list -> 'a t -> exposed t + +(** Replace the sigma part of a prop *) +val replace_sigma : Sil.hpred list -> 'a t -> exposed t + +(** Replace the sigma part of the footprint of a prop *) +val replace_sigma_footprint : Sil.hpred list -> 'a t -> exposed t + +(** Replace the pi part of the footprint of a prop *) +val replace_pi_footprint : Sil.atom list -> 'a t -> exposed t + +(** Rename free variables in a prop replacing them with existentially quantified vars *) +val prop_rename_fav_with_existentials : normal t -> normal t + +(** {2 Prop iterators} *) + +(** Iterator over the sigma part. Each iterator has a current [hpred]. *) +type 'a prop_iter + +(** Create an iterator, return None if sigma part is empty. *) +val prop_iter_create : normal t -> unit prop_iter option + +(** Return the prop associated to the iterator. *) +val prop_iter_to_prop : 'a prop_iter -> normal t + +(** Add an atom to the pi part of prop iter. The +first parameter records whether it is done +during footprint or during re - execution. *) +val prop_iter_add_atom : bool -> 'a prop_iter -> atom -> 'a prop_iter + +(** Remove the current element from the iterator, and return the prop +associated to the resulting iterator. *) +val prop_iter_remove_curr_then_to_prop : 'a prop_iter -> normal t + +(** Return the current hpred and state. *) +val prop_iter_current : 'a prop_iter -> (hpred * 'a) + +(** Return the next iterator. *) +val prop_iter_next : 'a prop_iter -> unit prop_iter option + +(** Remove the current hpred and return the next iterator. *) +val prop_iter_remove_curr_then_next : 'a prop_iter -> unit prop_iter option + +(** Update the current element of the iterator. *) +val prop_iter_update_current : 'a prop_iter -> hpred -> 'a prop_iter + +(** Insert before the current element of the iterator. *) +val prop_iter_prev_then_insert : 'a prop_iter -> hpred -> 'a prop_iter + +(** Find fav of the footprint part of the iterator *) +val prop_iter_footprint_fav : 'a prop_iter -> fav + +(** Find fav of the iterator *) +val prop_iter_fav : 'a prop_iter -> fav + +(** Extract the sigma part of the footprint *) +val prop_iter_get_footprint_sigma : 'a prop_iter -> hpred list + +(** Replace the sigma part of the footprint *) +val prop_iter_replace_footprint_sigma : 'a prop_iter -> hpred list -> 'a prop_iter + +(** Scan sigma to find an [hpred] satisfying the filter function. *) +val prop_iter_find : unit prop_iter -> (hpred -> 'a option) -> 'a prop_iter option + +(** Update the current element of the iterator by a nonempty list of elements. *) +val prop_iter_update_current_by_list : 'a prop_iter -> hpred list -> unit prop_iter + +(** Set the state of an iterator *) +val prop_iter_set_state : 'a prop_iter -> 'b -> 'b prop_iter + +(** Rename [ident] in [iter] by a fresh primed identifier *) +val prop_iter_make_id_primed : Ident.t -> 'a prop_iter -> 'a prop_iter + +(** Collect garbage fields. *) +val prop_iter_gc_fields : unit prop_iter -> unit prop_iter + +val find_equal_formal_path : exp -> 'a t -> Sil.exp option + +(** {2 Internal modules} *) + +module Metrics : sig +(** Compute a size value for the prop, which indicates its complexity *) + val prop_size : 'a t -> int + + (** Approximate the size of the longest chain by counting the max + number of |-> with the same type and whose lhs is primed or + footprint *) + val prop_chain_size : 'a t -> int +end + +module CategorizePreconditions : sig + type pre_category = + | NoPres (* no preconditions *) + | Empty (* the preconditions impose no restrictions *) + | OnlyAllocation (* the preconditions only demand that some pointers are allocated *) + | DataConstraints (* the preconditions impose constraints on the values of variables and/or memory *) + + (** categorize a list of preconditions *) + val categorize : 'a t list -> pre_category +end diff --git a/infer/src/backend/propgraph.ml b/infer/src/backend/propgraph.ml new file mode 100644 index 000000000..ac08fc4b9 --- /dev/null +++ b/infer/src/backend/propgraph.ml @@ -0,0 +1,231 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Propositions seen as graphs *) + +module F = Format +module L = Logging +open Utils + +type t = Prop.normal Prop.t + +type node = Sil.exp + +type sub_entry = Ident.t * Sil.exp + +type edge = Ehpred of Sil.hpred | Eatom of Sil.atom | Esub_entry of sub_entry + +let from_prop p = p + +(** Return [true] if root node *) +let rec is_root = function + | Sil.Var id -> Ident.is_normal id + | Sil.Const _ | Sil.Lvar _ -> true + | Sil.Cast (_, e) -> is_root e + | Sil.UnOp _ | Sil.BinOp _ | Sil.Lfield _ | Sil.Lindex _ | Sil.Sizeof _ -> false + +(** Return [true] if the nodes are connected. Used to compute reachability. *) +let nodes_connected g n1 n2 = + Sil.exp_equal n1 n2 (* Implemented as equality for now, later it might contain offset by a constant *) + +(** Return [true] if the edge is an hpred, and [false] if it is an atom *) +let edge_is_hpred = function + | Ehpred _ -> true + | Eatom _ -> false + | Esub_entry _ -> false + +(** Return the source of the edge *) +let edge_get_source = function + | Ehpred (Sil.Hpointsto(e, _, _)) -> e + | Ehpred (Sil.Hlseg(_, _, e, _, _)) -> e + | Ehpred (Sil.Hdllseg(_, _, e1, _, _, e2, _)) -> e1 (* :: e2 only one direction supported for now *) + | Eatom (Sil.Aeq (e1, _)) -> e1 + | Eatom (Sil.Aneq (e1, _)) -> e1 + | Esub_entry (x, e) -> Sil.Var x + +(** Return the successor nodes of the edge *) +let edge_get_succs = function + | Ehpred (Sil.Hpointsto(_, se, _)) -> Sil.strexp_get_target_exps se + | Ehpred (Sil.Hlseg(_, _, _, e, el)) -> e:: el + | Ehpred (Sil.Hdllseg(_, _, _, oB, oF, iB, elist)) -> oB:: oF:: iB:: elist (* only one direction supported for now *) + | Eatom (Sil.Aeq (_, e2)) -> [e2] + | Eatom (Sil.Aneq (_, e2)) -> [e2] + | Esub_entry (s, e) -> [e] + +let get_sigma footprint_part g = + if footprint_part then Prop.get_sigma_footprint g else Prop.get_sigma g +let get_pi footprint_part g = + if footprint_part then Prop.get_pi_footprint g else Prop.get_pi g +let get_subl footprint_part g = + if footprint_part then [] else Sil.sub_to_list (Prop.get_sub g) + +(** [edge_from_source g n footprint_part is_hpred] finds and edge with the given source [n] in prop [g]. +[footprint_part] indicates whether to search the edge in the footprint part, and [is_pred] whether it is an hpred edge. *) +let edge_from_source g n footprint_part is_hpred = + let edges = + if is_hpred + then list_map (fun hpred -> Ehpred hpred ) (get_sigma footprint_part g) + else list_map (fun a -> Eatom a) (get_pi footprint_part g) @ list_map (fun entry -> Esub_entry entry) (get_subl footprint_part g) in + match list_filter (fun hpred -> Sil.exp_equal n (edge_get_source hpred)) edges with + | [] -> None + | edge:: _ -> Some edge + +(** [get_succs g n footprint_part is_hpred] returns the successor nodes of [n] in [g]. +[footprint_part] indicates whether to search the successors in the footprint part, and [is_pred] whether to follow hpred edges. *) +let get_succs g n footprint_part is_hpred = + match edge_from_source g n footprint_part is_hpred with + | None -> [] + | Some e -> edge_get_succs e + +(** [get_edges footprint_part g] returns the list of edges in [g], in the footprint part if [fotprint_part] is true *) +let get_edges footprint_part g = + let hpreds = get_sigma footprint_part g in + let atoms = get_pi footprint_part g in + let subst_entries = get_subl footprint_part g in + list_map (fun hpred -> Ehpred hpred) hpreds @ list_map (fun a -> Eatom a) atoms @ list_map (fun entry -> Esub_entry entry) subst_entries + +let edge_equal e1 e2 = match e1, e2 with + | Ehpred hp1, Ehpred hp2 -> Sil.hpred_equal hp1 hp2 + | Eatom a1, Eatom a2 -> Sil.atom_equal a1 a2 + | Esub_entry (x1, e1), Esub_entry (x2, e2) -> Ident.equal x1 x2 && Sil.exp_equal e1 e2 + | _ -> false + +(** [contains_edge footprint_part g e] returns true if the graph [g] contains edge [e], +searching the footprint part if [footprint_part] is true. *) +let contains_edge (footprint_part: bool) (g: t) (e: edge) = + try ignore (list_find (fun e' -> edge_equal e e') (get_edges footprint_part g)); true + with Not_found -> false + +(** [iter_edges footprint_part f g] iterates function [f] on the edges in [g] in the same order as returned by [get_edges]; +if [footprint_part] is true the edges are taken from the footprint part. *) +let iter_edges footprint_part f g = + list_iter f (get_edges footprint_part g) (* For now simple iterator; later might use a specific traversal *) + +(** Graph annotated with the differences w.r.t. a previous graph *) +type diff = + { diff_newgraph : t; (** the new graph *) + diff_changed_norm : Obj.t list; (** objects changed in the normal part *) + diff_cmap_norm : colormap; (** colormap for the normal part *) + diff_changed_foot : Obj.t list; (** objects changed in the footprint part *) + diff_cmap_foot : colormap (** colormap for the footprint part *) } + +(** Compute the subobjects in [e2] which are different from those in [e1] *) +let compute_exp_diff (e1: Sil.exp) (e2: Sil.exp) : Obj.t list = + if Sil.exp_equal e1 e2 then [] else [Obj.repr e2] + + +(** Compute the subobjects in [se2] which are different from those in [se1] *) +let rec compute_sexp_diff (se1: Sil.strexp) (se2: Sil.strexp) : Obj.t list = match se1, se2 with + | Sil.Eexp (e1, inst1), Sil.Eexp (e2, inst2) -> if Sil.exp_equal e1 e2 then [] else [Obj.repr se2] + | Sil.Estruct (fsel1, _), Sil.Estruct (fsel2, _) -> + compute_fsel_diff fsel1 fsel2 + | Sil.Earray (e1, esel1, _), Sil.Earray (e2, esel2, _) -> + compute_exp_diff e1 e2 @ compute_esel_diff esel1 esel2 + | _ -> [Obj.repr se2] + +and compute_fsel_diff fsel1 fsel2 : Obj.t list = match fsel1, fsel2 with + | ((f1, se1):: fsel1'), (((f2, se2) as x):: fsel2') -> + (match Sil.fld_compare f1 f2 with + | n when n < 0 -> compute_fsel_diff fsel1' fsel2 + | 0 -> compute_sexp_diff se1 se2 @ compute_fsel_diff fsel1' fsel2' + | _ -> (Obj.repr x) :: compute_fsel_diff fsel1 fsel2') + | _, [] -> [] + | [], x:: fsel2' -> + (Obj.repr x) :: compute_fsel_diff [] fsel2' + +and compute_esel_diff esel1 esel2 : Obj.t list = match esel1, esel2 with + | ((e1, se1):: esel1'), (((e2, se2) as x):: esel2') -> + (match Sil.exp_compare e1 e2 with + | n when n < 0 -> compute_esel_diff esel1' esel2 + | 0 -> compute_sexp_diff se1 se2 @ compute_esel_diff esel1' esel2' + | _ -> (Obj.repr x) :: compute_esel_diff esel1 esel2') + | _, [] -> [] + | [], x:: esel2' -> + (Obj.repr x) :: compute_esel_diff [] esel2' + +(** Compute the subobjects in [newedge] which are different from those in [oldedge] *) +let compute_edge_diff (oldedge: edge) (newedge: edge) : Obj.t list = match oldedge, newedge with + | Ehpred (Sil.Hpointsto(_, se1, e1)), Ehpred (Sil.Hpointsto(_, se2, e2)) -> + compute_sexp_diff se1 se2 @ compute_exp_diff e1 e2 + | Eatom (Sil.Aeq (_, e1)), Eatom (Sil.Aeq (_, e2)) -> + compute_exp_diff e1 e2 + | Eatom (Sil.Aneq (_, e1)), Eatom (Sil.Aneq (_, e2)) -> + compute_exp_diff e1 e2 + | Esub_entry (_, e1), Esub_entry (_, e2) -> + compute_exp_diff e1 e2 + | _ -> [Obj.repr newedge] + +(** [compute_diff oldgraph newgraph] returns the list of edges which are only in [newgraph] *) +let compute_diff default_color oldgraph newgraph : diff = + let compute_changed footprint_part = + let newedges = get_edges footprint_part newgraph in + let changed = ref [] in + let build_changed edge = + if not (contains_edge footprint_part oldgraph edge) then begin + match edge_from_source oldgraph (edge_get_source edge) footprint_part (edge_is_hpred edge) with + | None -> + let changed_obj = match edge with + | Ehpred hpred -> Obj.repr hpred + | Eatom a -> Obj.repr a + | Esub_entry entry -> Obj.repr entry in + changed := changed_obj :: !changed + | Some oldedge -> changed := compute_edge_diff oldedge edge @ !changed + end in + list_iter build_changed newedges; + let colormap (o: Obj.t) = + if list_exists (fun x -> x == o) !changed then Red + else default_color in + !changed, colormap in + let changed_norm, colormap_norm = compute_changed false in + let changed_foot, colormap_foot = compute_changed true in + { diff_newgraph = newgraph; + diff_changed_norm = changed_norm; + diff_cmap_norm = colormap_norm; + diff_changed_foot = changed_foot; + diff_cmap_foot = colormap_foot } + +(** [diff_get_colormap footprint_part diff] returns the colormap of a computed diff, +selecting the footprint colormap if [footprint_part] is true. *) +let diff_get_colormap footprint_part diff = + if footprint_part then diff.diff_cmap_foot else diff.diff_cmap_norm + +(** Print a list of propositions, prepending each one with the given string. +If !Config.pring_using_diff is true, print the diff w.r.t. the given prop, +extracting its local stack vars if the boolean is true. *) +let pp_proplist pe0 s (base_prop, extract_stack) f plist = + let num = list_length plist in + let base_stack = fst (Prop.sigma_get_stack_nonstack true (Prop.get_sigma base_prop)) in + let add_base_stack prop = + if extract_stack then Prop.replace_sigma (base_stack @ Prop.get_sigma prop) prop + else Prop.expose prop in + let update_pe_diff (prop: Prop.normal Prop.t) : printenv = + if !Config.print_using_diff then + let diff = compute_diff Blue (from_prop base_prop) (from_prop prop) in + let cmap_norm = diff_get_colormap false diff in + let cmap_foot = diff_get_colormap true diff in + { pe0 with pe_cmap_norm = cmap_norm; pe_cmap_foot = cmap_foot } + else pe0 in + let rec pp_seq_newline n f = function + | [] -> () + | [_x] -> + let pe = update_pe_diff _x in + let x = add_base_stack _x in + (match pe.pe_kind with + | PP_TEXT -> F.fprintf f "%s %d of %d:@\n%a" s n num (Prop.pp_prop pe) x + | PP_HTML -> F.fprintf f "%s %d of %d:@\n%a@\n" s n num (Prop.pp_prop pe) x + | PP_LATEX -> F.fprintf f "@[%a@]@\n" (Prop.pp_prop pe) x) + | _x:: l -> + let pe = update_pe_diff _x in + let x = add_base_stack _x in + (match pe.pe_kind with + | PP_TEXT -> F.fprintf f "%s %d of %d:@\n%a@\n%a" s n num (Prop.pp_prop pe) x (pp_seq_newline (n + 1)) l + | PP_HTML -> F.fprintf f "%s %d of %d:@\n%a@\n%a" s n num (Prop.pp_prop pe) x (pp_seq_newline (n + 1)) l + | PP_LATEX -> F.fprintf f "@[%a@]\\\\@\n\\bigvee\\\\@\n%a" (Prop.pp_prop pe) x (pp_seq_newline (n + 1)) l) + in pp_seq_newline 1 f plist + +(** dump a propset *) +let d_proplist (p: 'a Prop.t) (pl: 'b Prop.t list) = + L.add_print_action (L.PTproplist, Obj.repr (p, pl)) diff --git a/infer/src/backend/propgraph.mli b/infer/src/backend/propgraph.mli new file mode 100644 index 000000000..483bec6f6 --- /dev/null +++ b/infer/src/backend/propgraph.mli @@ -0,0 +1,67 @@ +(* +* Copyright (c) 2009-2013 Monoidics ltd. +* Copyright (c) 2013- Facebook. +* All rights reserved. +*) + +(** Propositions seen as graphs *) + +open Utils + +type t (** prop considered as a graph *) + +type node (** node of the graph *) + +type edge (** multi-edge: one source and many destinations *) + +(** create a graph from a prop *) +val from_prop : Prop.normal Prop.t -> t + +(** Return [true] if root node *) +val is_root : node -> bool + +(** Return [true] if the nodes are connected. Used to compute reachability. *) +val nodes_connected : t -> node -> node -> bool + +(** Return the source of the edge *) +val edge_get_source : edge -> node + +(** Return the successor nodes of the edge *) +val edge_get_succs : edge -> node list + +(** [edge_from_source g n footprint_part is_hpred] finds and edge with the given source [n] in prop [g]. +[footprint_part] indicates whether to search the edge in the footprint part, and [is_pred] whether it is an hpred edge. *) +val edge_from_source : t -> node -> bool -> bool -> edge option + +(** [get_succs g n footprint_part is_hpred] returns the successor nodes of [n] in [g]. +[footprint_part] indicates whether to search the successors in the footprint part, and [is_pred] whether to follow hpred edges. *) +val get_succs : t -> node -> bool -> bool -> node list + +(** [get_edges footprint_part g] returns the list of edges in [g], in the footprint part if [fotprint_part] is true *) +val get_edges : bool -> t -> edge list + +(** [contains_edge footprint_part g e] returns true if the graph [g] contains edge [e], +searching the footprint part if [footprint_part] is true. *) +val contains_edge : bool -> t -> edge -> bool + +(** [iter_edges footprint_part f g] iterates function [f] on the edges in [g] in the same order as returned by [get_edges]; +if [footprint_part] is true the edges are taken from the footprint part. *) +val iter_edges : bool -> (edge -> unit) -> t -> unit + +(** Graph annotated with the differences w.r.t. a previous graph *) +type diff + +(** [compute_diff default_color oldgraph newgraph] returns the list of edges which are only in [newgraph] *) +val compute_diff : color -> t -> t -> diff + +(** [diff_get_colormap footprint_part diff] returns the colormap of a computed diff, +selecting the footprint colormap if [footprint_part] is true. *) +val diff_get_colormap : bool -> diff -> Utils.colormap + +(** Print a list of propositions, prepending each one with the given string, +If !Config.pring_using_diff is true, print the diff w.r.t. the given prop, +extracting its local stack vars if the boolean is true. *) +val pp_proplist : printenv -> string -> (Prop.normal Prop.t * bool) -> Format.formatter -> Prop.normal Prop.t list -> unit + +(** dump a prop list coming form the given initial prop *) +val d_proplist : 'a Prop.t -> 'b Prop.t list -> unit diff --git a/infer/src/backend/propset.ml b/infer/src/backend/propset.ml new file mode 100644 index 000000000..f57ccee89 --- /dev/null +++ b/infer/src/backend/propset.ml @@ -0,0 +1,102 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Functions for Propositions (i.e., Symbolic Heaps) *) + +module L = Logging +module F = Format +open Utils + +(** {2 Sets of Propositions} *) + +module PropSet = + Set.Make(struct + type t = Prop.normal Prop.t + let compare = Prop.prop_compare + end) + +let compare = PropSet.compare + +(** Sets of propositions. +The invariant is maintaned that Prop.prop_rename_primed_footprint_vars is called on any prop added to the set. *) +type t = PropSet.t + +let add p pset = + let ps = Prop.prop_expand p in + list_fold_left (fun pset' p' -> PropSet.add (Prop.prop_rename_primed_footprint_vars p') pset') pset ps + +(** Singleton set. *) +let singleton p = + add p PropSet.empty + +(** Set union. *) +let union = PropSet.union + +(** Set membership *) +let mem p = + PropSet.mem p + +(** Set intersection *) +let inter = PropSet.inter + +(** Set difference. *) +let diff = + PropSet.diff + +let empty = PropSet.empty + +(** Set emptiness check. *) +let is_empty = PropSet.is_empty + +(** Size of the set *) +let size = PropSet.cardinal + +let filter = PropSet.filter + +let from_proplist plist = + list_fold_left (fun pset p -> add p pset) empty plist + +let to_proplist pset = + PropSet.elements pset + +(** Apply function to all the elements of [propset], removing those where it returns [None]. *) +let map_option f pset = + let plisto = list_map f (to_proplist pset) in + let plisto = list_filter (function | Some _ -> true | None -> false) plisto in + let plist = list_map (function Some p -> p | None -> assert false) plisto in + from_proplist plist + +(** Apply function to all the elements of [propset]. *) +let map f pset = + from_proplist (list_map f (to_proplist pset)) + +(** [fold f pset a] computes [f (... (f (f a p1) p2) ...) pn] +where [p1 ... pN] are the elements of pset, in increasing order. *) +let fold f a pset = + let l = to_proplist pset in + list_fold_left f a l + +(** [iter f pset] computes (f p1;f p2;..;f pN) +where [p1 ... pN] are the elements of pset, in increasing order. *) +let iter = + PropSet.iter + +let subseteq = + PropSet.subset + +let partition = + PropSet.partition + +(** {2 Pretty print} *) + +(** Pretty print a set of propositions, obtained from the given prop. *) +let pp pe prop f pset = + let plist = to_proplist pset in + (Propgraph.pp_proplist pe "PROP" (prop, false)) f plist + +let d p ps = + let plist = to_proplist ps in + Propgraph.d_proplist p plist diff --git a/infer/src/backend/propset.mli b/infer/src/backend/propset.mli new file mode 100644 index 000000000..da4fb9ba5 --- /dev/null +++ b/infer/src/backend/propset.mli @@ -0,0 +1,78 @@ +(* +* Copyright (c) 2009-2013 Monoidics ltd. +* Copyright (c) 2013- Facebook. +* All rights reserved. +*) + +(** Functions for Sets of Propositions with and without sharing *) + +open Utils + +(** {2 Sets of Propositions} *) + +(** Sets of propositions. +The invariant is maintaned that Prop.prop_rename_primed_footprint_vars is called on any prop added to the set. *) +type t + +(** Compare propsets *) +val compare : t -> t -> int + +(** Singleton set. *) +val singleton : Prop.normal Prop.t -> t + +(** Set membership. *) +val mem : Prop.normal Prop.t -> t -> bool + +(** Set union. *) +val union : t -> t -> t + +(** Set intersection *) +val inter : t -> t -> t + +(** Add [prop] to propset. *) +val add : Prop.normal Prop.t -> t -> t + +(** Set difference. *) +val diff : t -> t -> t + +(** The empty set of propositions. *) +val empty : t + +(** Size of the set *) +val size : t -> int + +val from_proplist : Prop.normal Prop.t list -> t + +val to_proplist : t -> Prop.normal Prop.t list + +(** Apply function to all the elements of the propset. *) +val map : (Prop.normal Prop.t -> Prop.normal Prop.t) -> t -> t + +(** Apply function to all the elements of the propset, removing those where it returns [None]. *) +val map_option : (Prop.normal Prop.t -> Prop.normal Prop.t option) -> t -> t + +(** [fold f pset a] computes [(f pN ... (f p2 (f p1 a))...)], +where [p1 ... pN] are the elements of pset, in increasing +order. *) +val fold : ('a -> Prop.normal Prop.t -> 'a) -> 'a -> t -> 'a + +(** [iter f pset] computes (f p1;f p2;..;f pN) +where [p1 ... pN] are the elements of pset, in increasing order. *) +val iter : (Prop.normal Prop.t -> unit) -> t -> unit + +val partition : (Prop.normal Prop.t -> bool) -> t -> t * t + +val subseteq : t -> t -> bool + +(** Set emptiness check. *) +val is_empty : t -> bool + +val filter : (Prop.normal Prop.t -> bool) -> t -> t + +(** {2 Pretty print} *) + +(** Pretty print a set of propositions, obtained from the given prop. *) +val pp : printenv -> Prop.normal Prop.t -> Format.formatter -> t -> unit + +(** dump a propset coming form the given initial prop *) +val d : Prop.normal Prop.t -> t -> unit diff --git a/infer/src/backend/prover.ml b/infer/src/backend/prover.ml new file mode 100644 index 000000000..d3c17b709 --- /dev/null +++ b/infer/src/backend/prover.ml @@ -0,0 +1,2124 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Functions for Propositions (i.e., Symbolic Heaps) *) + +module L = Logging +module F = Format +open Utils + +let decrease_indent_when_exception thunk = + try (thunk ()) + with exn when exn_not_timeout exn -> (L.d_decrease_indent 1; raise exn) + +let compute_max_from_nonempty_int_list l = + list_hd (list_rev (list_sort Sil.Int.compare_value l)) + +let compute_min_from_nonempty_int_list l = + list_hd (list_sort Sil.Int.compare_value l) + +let exp_pair_compare (e1, e2) (f1, f2) = + let c1 = Sil.exp_compare e1 f1 in + if c1 <> 0 then c1 else Sil.exp_compare e2 f2 + +let rec list_rev_acc acc = function + | [] -> acc + | x:: l -> list_rev_acc (x:: acc) l + +let rec remove_redundancy have_same_key acc = function + | [] -> list_rev acc + | [x] -> list_rev (x:: acc) + | x:: ((y:: l') as l) -> + if have_same_key x y then remove_redundancy have_same_key acc (x:: l') + else remove_redundancy have_same_key (x:: acc) l + +(** {2 Ordinary Theorem Proving} *) + +let (++) = Sil.Int.add +let (--) = Sil.Int.sub + +(** Reasoning about constraints of the form x-y <= n *) + +module DiffConstr : sig + + type t + val to_leq : t -> Sil.exp * Sil.exp + val to_lt : t -> Sil.exp * Sil.exp + val to_triple : t -> Sil.exp * Sil.exp * Sil.Int.t + val from_leq : t list -> Sil.exp * Sil.exp -> t list + val from_lt : t list -> Sil.exp * Sil.exp -> t list + val saturate : t list -> bool * t list + +end = struct + + type t = Sil.exp * Sil.exp * Sil.Int.t + + let compare (e1, e2, n) (f1, f2, m) = + let c1 = exp_pair_compare (e1, e2) (f1, f2) in + if c1 <> 0 then c1 else Sil.Int.compare_value n m + let equal entry1 entry2 = compare entry1 entry2 = 0 + + let to_leq (e1, e2, n) = + Sil.BinOp(Sil.MinusA, e1, e2), Sil.exp_int n + let to_lt (e1, e2, n) = + Sil.exp_int (Sil.Int.zero -- n -- Sil.Int.one), Sil.BinOp(Sil.MinusA, e2, e1) + let to_triple entry = entry + + let from_leq acc (e1, e2) = + match e1, e2 with + | Sil.BinOp(Sil.MinusA, (Sil.Var id11 as e11), (Sil.Var id12 as e12)), Sil.Const (Sil.Cint n) + when not (Ident.equal id11 id12) -> + (match Sil.Int.to_signed n with + | None -> acc (* ignore: constraint algorithm only terminates on signed integers *) + | Some n' -> + (e11, e12, n') :: acc) + | _ -> acc + let from_lt acc (e1, e2) = + match e1, e2 with + | Sil.Const (Sil.Cint n), Sil.BinOp(Sil.MinusA, (Sil.Var id21 as e21), (Sil.Var id22 as e22)) + when not (Ident.equal id21 id22) -> + (match Sil.Int.to_signed n with + | None -> acc (* ignore: constraint algorithm only terminates on signed integers *) + | Some n' -> + let m = Sil.Int.zero -- n' -- Sil.Int.one in + (e22, e21, m) :: acc) + | _ -> acc + + let rec generate ((e1, e2, n) as constr) acc = function + | [] -> false, acc + | (f1, f2, m):: rest -> + let equal_e2_f1 = Sil.exp_equal e2 f1 in + let equal_e1_f2 = Sil.exp_equal e1 f2 in + if equal_e2_f1 && equal_e1_f2 && Sil.Int.lt (n ++ m) Sil.Int.zero then + true, [] (* constraints are inconsistent *) + else if equal_e2_f1 && equal_e1_f2 then + generate constr acc rest + else if equal_e2_f1 then + let constr_new = (e1, f2, n ++ m) in + generate constr (constr_new:: acc) rest + else if equal_e1_f2 then + let constr_new = (f1, e2, m ++ n) in + generate constr (constr_new:: acc) rest + else + generate constr acc rest + + let sort_then_remove_redundancy constraints = + let constraints_sorted = list_sort compare constraints in + let have_same_key (e1, e2, _) (f1, f2, _) = exp_pair_compare (e1, e2) (f1, f2) = 0 in + remove_redundancy have_same_key [] constraints_sorted + + let remove_redundancy constraints = + let constraints' = sort_then_remove_redundancy constraints in + list_filter (fun entry -> list_exists (equal entry) constraints') constraints + + let rec combine acc_todos acc_seen constraints_new constraints_old = + match constraints_new, constraints_old with + | [], [] -> list_rev acc_todos, list_rev acc_seen + | [], _ -> list_rev acc_todos, list_rev_acc constraints_old acc_seen + | _, [] -> list_rev_acc constraints_new acc_todos, list_rev_acc constraints_new acc_seen + | constr:: rest, constr':: rest' -> + let e1, e2, n = constr in + let f1, f2, m = constr' in + let c1 = exp_pair_compare (e1, e2) (f1, f2) in + if c1 = 0 && Sil.Int.lt n m then + combine acc_todos acc_seen constraints_new rest' + else if c1 = 0 then + combine acc_todos acc_seen rest constraints_old + else if c1 < 0 then + combine (constr:: acc_todos) (constr:: acc_seen) rest constraints_old + else + combine acc_todos (constr':: acc_seen) constraints_new rest' + + let rec _saturate seen todos = + (* seen is a superset of todos. "seen" is sorted and doesn't have redundancy. *) + match todos with + | [] -> false, seen + | constr:: rest -> + let inconsistent, constraints_new = generate constr [] seen in + if inconsistent then true, [] + else + let constraints_new' = sort_then_remove_redundancy constraints_new in + let todos_new, seen_new = combine [] [] constraints_new' seen in + (* Important to use queue here. Otherwise, might diverge *) + let rest_new = remove_redundancy (rest @ todos_new) in + let seen_new' = sort_then_remove_redundancy seen_new in + _saturate seen_new' rest_new + + let saturate constraints = + let constraints_cleaned = sort_then_remove_redundancy constraints in + _saturate constraints_cleaned constraints_cleaned +end + +(** Return true if the two types have sizes which can be compared *) +let type_size_comparable t1 t2 = match t1, t2 with + | Sil.Tint _, Sil.Tint _ -> true + | _ -> false + +(** Compare the size of comparable types *) +let type_size_compare t1 t2 = + let ik_compare ik1 ik2 = + let ik_size = function + | Sil.IChar | Sil.ISChar | Sil.IUChar | Sil.IBool -> 1 + | Sil.IShort | Sil.IUShort -> 2 + | Sil.IInt | Sil.IUInt -> 3 + | Sil.ILong | Sil.IULong -> 4 + | Sil.ILongLong | Sil.IULongLong -> 5 + | Sil.I128 | Sil.IU128 -> 6 in + let n1 = ik_size ik1 in + let n2 = ik_size ik2 in + n1 - n2 in + match t1, t2 with + | Sil.Tint ik1, Sil.Tint ik2 -> + Some (ik_compare ik1 ik2) + | _ -> None + +(** Check <= on the size of comparable types *) +let check_type_size_leq t1 t2 = match type_size_compare t1 t2 with + | None -> false + | Some n -> n <= 0 + +(** Check < on the size of comparable types *) +let check_type_size_lt t1 t2 = match type_size_compare t1 t2 with + | None -> false + | Some n -> n < 0 + +(** Reasoning about inequalities *) +module Inequalities : sig +(** type for inequalities (and implied disequalities) *) + type t + + (** Extract inequalities and disequalities from [pi] *) + val from_pi : Sil.atom list -> t + + (** Extract inequalities and disequalities from [sigma] *) + val from_sigma : Sil.hpred list -> t + + (** Extract inequalities and disequalities from [prop] *) + val from_prop : Prop.normal Prop.t -> t + + (** Join two sets of inequalities *) + val join : t -> t -> t + + (** Pretty print inequalities and disequalities *) + val pp : printenv -> Format.formatter -> t -> unit + + (** Check [t |- e1!=e2]. Result [false] means "don't know". *) + val check_ne : t -> Sil.exp -> Sil.exp -> bool + + (** Check [t |- e1<=e2]. Result [false] means "don't know". *) + val check_le : t -> Sil.exp -> Sil.exp -> bool + + (** Check [t |- e1 Sil.exp -> Sil.exp -> bool + + (** Find a Sil.Int.t n such that [t |- e<=n] if possible. *) + val compute_upper_bound : t -> Sil.exp -> Sil.Int.t option + + (** Find a Sil.Int.t n such that [t |- n Sil.exp -> Sil.Int.t option + + (** Return [true] if a simple inconsistency is detected *) + val inconsistent : t -> bool + + (** Pretty print <= *) + val d_leqs : t -> unit + + (** Pretty print < *) + val d_lts : t -> unit + + (** Pretty print <> *) + val d_neqs : t -> unit +end = struct + + module ExpMap = + Map.Make (struct + type t = Sil.exp + let compare = Sil.exp_compare end) + + type t = { + mutable leqs: (Sil.exp * Sil.exp) list; (** le fasts [e1 <= e2] *) + mutable lts: (Sil.exp * Sil.exp) list; (** lt facts [e1 < e2] *) + mutable neqs: (Sil.exp * Sil.exp) list; (** ne facts [e1 != e2] *) + } + + let inconsistent_ineq = { leqs = [(Sil.exp_one, Sil.exp_zero)]; lts = []; neqs = [] } + + let leq_compare (e1, e2) (f1, f2) = + let c1 = Sil.exp_compare e1 f1 in + if c1 <> 0 then c1 else Sil.exp_compare e2 f2 + let lt_compare (e1, e2) (f1, f2) = + let c2 = Sil.exp_compare e2 f2 in + if c2 <> 0 then c2 else - (Sil.exp_compare e1 f1) + + let leqs_sort_then_remove_redundancy leqs = + let leqs_sorted = list_sort leq_compare leqs in + let have_same_key leq1 leq2 = + match leq1, leq2 with + | (e1, Sil.Const (Sil.Cint n1)), (e2, Sil.Const (Sil.Cint n2)) -> + Sil.exp_equal e1 e2 && Sil.Int.leq n1 n2 + | _, _ -> false in + remove_redundancy have_same_key [] leqs_sorted + let lts_sort_then_remove_redundancy lts = + let lts_sorted = list_sort lt_compare lts in + let have_same_key lt1 lt2 = + match lt1, lt2 with + | (Sil.Const (Sil.Cint n1), e1), (Sil.Const (Sil.Cint n2), e2) -> + Sil.exp_equal e1 e2 && Sil.Int.geq n1 n2 + | _, _ -> false in + remove_redundancy have_same_key [] lts_sorted + + let saturate { leqs = leqs; lts = lts; neqs = neqs } = + let diff_constraints1 = + list_fold_left + DiffConstr.from_lt + (list_fold_left DiffConstr.from_leq [] leqs) + lts in + let inconsistent, diff_constraints2 = DiffConstr.saturate diff_constraints1 in + if inconsistent then inconsistent_ineq + else begin + let umap_add umap e new_upper = + try + let old_upper = ExpMap.find e umap in + if Sil.Int.leq old_upper new_upper then umap else ExpMap.add e new_upper umap + with Not_found -> ExpMap.add e new_upper umap in + let lmap_add lmap e new_lower = + try + let old_lower = ExpMap.find e lmap in + if Sil.Int.geq old_lower new_lower then lmap else ExpMap.add e new_lower lmap + with Not_found -> ExpMap.add e new_lower lmap in + let rec umap_create_from_leqs umap = function + | [] -> umap + | (e1, Sil.Const (Sil.Cint upper1)):: leqs_rest -> + let umap' = umap_add umap e1 upper1 in + umap_create_from_leqs umap' leqs_rest + | _:: leqs_rest -> umap_create_from_leqs umap leqs_rest in + let rec lmap_create_from_lts lmap = function + | [] -> lmap + | (Sil.Const (Sil.Cint lower1), e1):: lts_rest -> + let lmap' = lmap_add lmap e1 lower1 in + lmap_create_from_lts lmap' lts_rest + | _:: lts_rest -> lmap_create_from_lts lmap lts_rest in + let rec umap_improve_by_difference_constraints umap = function + | [] -> umap + | constr:: constrs_rest -> + try + let e1, e2, n = DiffConstr.to_triple constr (* e1 - e2 <= n *) in + let upper2 = ExpMap.find e2 umap in + let new_upper1 = upper2 ++ n in + let new_umap = umap_add umap e1 new_upper1 in + umap_improve_by_difference_constraints new_umap constrs_rest + with Not_found -> + umap_improve_by_difference_constraints umap constrs_rest in + let rec lmap_improve_by_difference_constraints lmap = function + | [] -> lmap + | constr:: constrs_rest -> (* e2 - e1 > -n-1 *) + try + let e1, e2, n = DiffConstr.to_triple constr (* e2 - e1 > -n-1 *) in + let lower1 = ExpMap.find e1 lmap in + let new_lower2 = lower1 -- n -- Sil.Int.one in + let new_lmap = lmap_add lmap e2 new_lower2 in + lmap_improve_by_difference_constraints new_lmap constrs_rest + with Not_found -> + lmap_improve_by_difference_constraints lmap constrs_rest in + let leqs_res = + let umap = umap_create_from_leqs ExpMap.empty leqs in + let umap' = umap_improve_by_difference_constraints umap diff_constraints2 in + let leqs' = ExpMap.fold (fun e upper acc_leqs -> (e, Sil.exp_int upper):: acc_leqs) umap' [] in + let leqs'' = (list_map DiffConstr.to_leq diff_constraints2) @ leqs' in + leqs_sort_then_remove_redundancy leqs'' in + let lts_res = + let lmap = lmap_create_from_lts ExpMap.empty lts in + let lmap' = lmap_improve_by_difference_constraints lmap diff_constraints2 in + let lts' = ExpMap.fold (fun e lower acc_lts -> (Sil.exp_int lower, e):: acc_lts) lmap' [] in + let lts'' = (list_map DiffConstr.to_lt diff_constraints2) @ lts' in + lts_sort_then_remove_redundancy lts'' in + { leqs = leqs_res; lts = lts_res; neqs = neqs } + end + + (** Extract inequalities and disequalities from [pi] *) + let from_pi pi = + let leqs = ref [] in (* <= facts *) + let lts = ref [] in (* < facts *) + let neqs = ref [] in (* != facts *) + let process_atom = function + | Sil.Aneq (e1, e2) -> (* != *) + neqs := (e1, e2) :: !neqs + | Sil.Aeq (Sil.BinOp (Sil.Le, e1, e2), Sil.Const (Sil.Cint i)) when Sil.Int.isone i -> (* <= *) + leqs := (e1, e2) :: !leqs + | Sil.Aeq (Sil.BinOp (Sil.Lt, e1, e2), Sil.Const (Sil.Cint i)) when Sil.Int.isone i -> (* < *) + lts := (e1, e2) :: !lts + | Sil.Aeq _ -> () in + list_iter process_atom pi; + saturate { leqs = !leqs; lts = !lts; neqs = !neqs } + + let from_sigma sigma = + let leqs = ref [] in + let lts = ref [] in + let add_lt_minus1_e e = + lts := (Sil.exp_minus_one, e)::!lts in + let texp_is_unsigned = function + | Sil.Sizeof (Sil.Tint ik, _) -> Sil.ikind_is_unsigned ik + | _ -> false in + let strexp_lt_minus1 = function + | Sil.Eexp (e, _) -> add_lt_minus1_e e + | _ -> () in + let rec strexp_extract = function + | Sil.Eexp _ -> () + | Sil.Estruct (fsel, _) -> + list_iter (fun (_, se) -> strexp_extract se) fsel + | Sil.Earray (size, isel, _) -> + add_lt_minus1_e size; + list_iter (fun (idx, se) -> + add_lt_minus1_e idx; + strexp_extract se) isel in + let hpred_extract = function + | Sil.Hpointsto(_, se, texp) -> + if texp_is_unsigned texp then strexp_lt_minus1 se; + strexp_extract se + | Sil.Hlseg _ | Sil.Hdllseg _ -> () in + list_iter hpred_extract sigma; + saturate { leqs = !leqs; lts = !lts; neqs = [] } + + let join ineq1 ineq2 = + let leqs_new = ineq1.leqs @ ineq2.leqs in + let lts_new = ineq1.lts @ ineq2.lts in + let neqs_new = ineq1.neqs @ ineq2.neqs in + saturate { leqs = leqs_new; lts = lts_new; neqs = neqs_new } + + let from_prop prop = + let sigma = Prop.get_sigma prop in + let pi = Prop.get_pi prop in + let ineq_sigma = from_sigma sigma in + let ineq_pi = from_pi pi in + saturate (join ineq_sigma ineq_pi) + + (** Return true if the two pairs of expressions are equal *) + let exp_pair_eq (e1, e2) (f1, f2) = + Sil.exp_equal e1 f1 && Sil.exp_equal e2 f2 + + (** Check [t |- e1<=e2]. Result [false] means "don't know". *) + let check_le { leqs = leqs; lts = lts; neqs = _ } e1 e2 = + (* L.d_str "check_le "; Sil.d_exp e1; L.d_str " "; Sil.d_exp e2; L.d_ln (); *) + match e1, e2 with + | Sil.Const (Sil.Cint n1), Sil.Const (Sil.Cint n2) -> Sil.Int.leq n1 n2 + | Sil.BinOp (Sil.MinusA, Sil.Sizeof (t1, _), Sil.Sizeof (t2, _)), Sil.Const(Sil.Cint n2) + when Sil.Int.isminusone n2 && type_size_comparable t1 t2 -> (* [ sizeof(t1) - sizeof(t2) <= -1 ] *) + check_type_size_lt t1 t2 + | e, Sil.Const (Sil.Cint n) -> (* [e <= n' <= n |- e <= n] *) + list_exists (function + | e', Sil.Const (Sil.Cint n') -> Sil.exp_equal e e' && Sil.Int.leq n' n + | _, _ -> false) leqs + | Sil.Const (Sil.Cint n), e -> (* [ n-1 <= n' < e |- n <= e] *) + list_exists (function + | Sil.Const (Sil.Cint n'), e' -> Sil.exp_equal e e' && Sil.Int.leq (n -- Sil.Int.one) n' + | _, _ -> false) lts + | _ -> Sil.exp_equal e1 e2 + + (** Check [prop |- e1 Sil.Int.lt n1 n2 + | Sil.Const (Sil.Cint n), e -> (* [n <= n' < e |- n < e] *) + list_exists (function + | Sil.Const (Sil.Cint n'), e' -> Sil.exp_equal e e' && Sil.Int.leq n n' + | _, _ -> false) lts + | e, Sil.Const (Sil.Cint n) -> (* [e <= n' <= n-1 |- e < n] *) + list_exists (function + | e', Sil.Const (Sil.Cint n') -> Sil.exp_equal e e' && Sil.Int.leq n' (n -- Sil.Int.one) + | _, _ -> false) leqs + | _ -> false + + (** Check [prop |- e1!=e2]. Result [false] means "don't know". *) + let check_ne ineq _e1 _e2 = + let e1, e2 = if Sil.exp_compare _e1 _e2 <= 0 then _e1, _e2 else _e2, _e1 in + list_exists (exp_pair_eq (e1, e2)) ineq.neqs || check_lt ineq e1 e2 || check_lt ineq e2 e1 + + (** Find a Sil.Int.t n such that [t |- e<=n] if possible. *) + let compute_upper_bound { leqs = leqs; lts = _; neqs = _ } e1 = + match e1 with + | Sil.Const (Sil.Cint n1) -> Some n1 + | _ -> + let e_upper_list = + list_filter (function + | e', Sil.Const (Sil.Cint _) -> Sil.exp_equal e1 e' + | _, _ -> false) leqs in + let upper_list = + list_map (function + | _, Sil.Const (Sil.Cint n) -> n + | _ -> assert false) e_upper_list in + if upper_list == [] then None + else Some (compute_min_from_nonempty_int_list upper_list) + + (** Find a Sil.Int.t n such that [t |- n < e] if possible. *) + let compute_lower_bound { leqs = _; lts = lts; neqs = _ } e1 = + match e1 with + | Sil.Const (Sil.Cint n1) -> Some (n1 -- Sil.Int.one) + | Sil.Sizeof _ -> Some Sil.Int.zero + | _ -> + let e_lower_list = + list_filter (function + | Sil.Const (Sil.Cint _), e' -> Sil.exp_equal e1 e' + | _, _ -> false) lts in + let lower_list = + list_map (function + | Sil.Const (Sil.Cint n), _ -> n + | _ -> assert false) e_lower_list in + if lower_list == [] then None + else Some (compute_max_from_nonempty_int_list lower_list) + + (** Return [true] if a simple inconsistency is detected *) + let inconsistent ({ leqs = leqs; lts = lts; neqs = neqs } as ineq) = + let inconsistent_neq (e1, e2) = + check_le ineq e1 e2 && check_le ineq e2 e1 in + let inconsistent_leq (e1, e2) = check_lt ineq e2 e1 in + let inconsistent_lt (e1, e2) = check_le ineq e2 e1 in + list_exists inconsistent_neq neqs || + list_exists inconsistent_leq leqs || + list_exists inconsistent_lt lts + + (** Pretty print inequalities and disequalities *) + let pp pe fmt { leqs = leqs; lts = lts; neqs = neqs } = + let pp_leq fmt (e1, e2) = F.fprintf fmt "%a<=%a" (Sil.pp_exp pe) e1 (Sil.pp_exp pe) e2 in + let pp_lt fmt (e1, e2) = F.fprintf fmt "%a<%a" (Sil.pp_exp pe) e1 (Sil.pp_exp pe) e2 in + let pp_neq fmt (e1, e2) = F.fprintf fmt "%a!=%a" (Sil.pp_exp pe) e1 (Sil.pp_exp pe) e2 in + Format.fprintf fmt "%a %a %a" (pp_seq pp_leq) leqs (pp_seq pp_lt) lts (pp_seq pp_neq) neqs + + let d_leqs { leqs = leqs; lts = lts; neqs = neqs } = + let elist = list_map (fun (e1, e2) -> Sil.BinOp(Sil.Le, e1, e2)) leqs in + Sil.d_exp_list elist + + let d_lts { leqs = leqs; lts = lts; neqs = neqs } = + let elist = list_map (fun (e1, e2) -> Sil.BinOp(Sil.Lt, e1, e2)) lts in + Sil.d_exp_list elist + + let d_neqs { leqs = leqs; lts = lts; neqs = neqs } = + let elist = list_map (fun (e1, e2) -> Sil.BinOp(Sil.Ne, e1, e2)) lts in + Sil.d_exp_list elist +end +(* End of module Inequalities *) + +(** Check [prop |- e1=e2]. Result [false] means "don't know". *) +let check_equal prop e1 e2 = + let n_e1 = Prop.exp_normalize_prop prop e1 in + let n_e2 = Prop.exp_normalize_prop prop e2 in + let check_equal () = + Sil.exp_equal n_e1 n_e2 in + let check_equal_const () = + match n_e1, n_e2 with + | Sil.BinOp (Sil.PlusA, e1, Sil.Const (Sil.Cint d)), e2 + | e2, Sil.BinOp (Sil.PlusA, e1, Sil.Const (Sil.Cint d)) -> + if Sil.exp_equal e1 e2 then Sil.Int.iszero d + else false + | Sil.Const c1, Sil.Lindex(Sil.Const c2, Sil.Const (Sil.Cint i)) when Sil.Int.iszero i -> + Sil.const_equal c1 c2 + | Sil.Lindex(Sil.Const c1, Sil.Const (Sil.Cint i)), Sil.Const c2 when Sil.Int.iszero i -> + Sil.const_equal c1 c2 + | _, _ -> false in + let check_equal_pi () = + let eq = Sil.Aeq(n_e1, n_e2) in + let n_eq = Prop.atom_normalize_prop prop eq in + let pi = Prop.get_pi prop in + list_exists (Sil.atom_equal n_eq) pi in + check_equal () || check_equal_const () || check_equal_pi () + +(** Check [ |- e=0]. Result [false] means "don't know". *) +let check_zero e = + check_equal Prop.prop_emp e Sil.exp_zero + +(** [is_root prop base_exp exp] checks whether [base_exp = +exp.offlist] for some list of offsets [offlist]. If so, it returns +[Some(offlist)]. Otherwise, it returns [None]. Assumes that +[base_exp] points to the beginning of a structure, not the middle. +*) +let is_root prop base_exp exp = + let rec f offlist_past e = match e with + | Sil.Var _ | Sil.Const _ | Sil.UnOp _ | Sil.BinOp _ | Sil.Lvar _ | Sil.Sizeof _ -> + if check_equal prop base_exp e + then Some offlist_past + else None + | Sil.Cast(t, sub_exp) -> f offlist_past sub_exp + | Sil.Lfield(sub_exp, fldname, typ) -> f (Sil.Off_fld (fldname, typ) :: offlist_past) sub_exp + | Sil.Lindex(sub_exp, e) -> f (Sil.Off_index e :: offlist_past) sub_exp + in f [] exp + +(** Get upper and lower bounds of an expression, if any *) +let get_bounds prop _e = + let e_norm = Prop.exp_normalize_prop prop _e in + let e_root, off = match e_norm with + | Sil.BinOp (Sil.PlusA, e, Sil.Const (Sil.Cint n1)) -> + e, Sil.Int.neg n1 + | Sil.BinOp (Sil.MinusA, e, Sil.Const (Sil.Cint n1)) -> + e, n1 + | _ -> + e_norm, Sil.Int.zero in + let ineq = Inequalities.from_prop prop in + let upper_opt = Inequalities.compute_upper_bound ineq e_root in + let lower_opt = Inequalities.compute_lower_bound ineq e_root in + let (+++) n_opt k = match n_opt with + | None -> None + | Some n -> Some (n ++ k) in + upper_opt +++ off, lower_opt +++ off + +(** Check whether [prop |- e1!=e2]. *) +let check_disequal prop e1 e2 = + let spatial_part = Prop.get_sigma prop in + let n_e1 = Prop.exp_normalize_prop prop e1 in + let n_e2 = Prop.exp_normalize_prop prop e2 in + let check_disequal_const () = + match n_e1, n_e2 with + | Sil.Const c1, Sil.Const c2 -> + (Sil.const_kind_equal c1 c2) && not (Sil.const_equal c1 c2) + | Sil.Const c1, Sil.Lindex(Sil.Const c2, Sil.Const (Sil.Cint d)) -> + if Sil.Int.iszero d + then not (Sil.const_equal c1 c2) (* offset=0 is no offset *) + else Sil.const_equal c1 c2 (* same base, different offsets *) + | Sil.BinOp (Sil.PlusA, e1, Sil.Const (Sil.Cint d1)), Sil.BinOp (Sil.PlusA, e2, Sil.Const (Sil.Cint d2)) -> + if Sil.exp_equal e1 e2 then Sil.Int.neq d1 d2 + else false + | Sil.BinOp (Sil.PlusA, e1, Sil.Const (Sil.Cint d)), e2 + | e2, Sil.BinOp (Sil.PlusA, e1, Sil.Const (Sil.Cint d)) -> + if Sil.exp_equal e1 e2 then not (Sil.Int.iszero d) + else false + | Sil.Lindex(Sil.Const c1, Sil.Const (Sil.Cint d)), Sil.Const c2 -> + if Sil.Int.iszero d then not (Sil.const_equal c1 c2) else Sil.const_equal c1 c2 + | Sil.Lindex(Sil.Const c1, Sil.Const d1), Sil.Lindex (Sil.Const c2, Sil.Const d2) -> + Sil.const_equal c1 c2 && not (Sil.const_equal d1 d2) + | _, _ -> false in + let ineq = lazy (Inequalities.from_prop prop) in + let check_pi_implies_disequal e1 e2 = + Inequalities.check_ne (Lazy.force ineq) e1 e2 in + let neq_spatial_part () = + let rec f sigma_irrelevant e = function + | [] -> None + | Sil.Hpointsto (base, _, _) as hpred :: sigma_rest -> + (match is_root prop base e with + | None -> + let sigma_irrelevant' = hpred :: sigma_irrelevant + in f sigma_irrelevant' e sigma_rest + | Some _ -> + let sigma_irrelevant' = (list_rev sigma_irrelevant) @ sigma_rest + in Some (true, sigma_irrelevant')) + | Sil.Hlseg (k, _, e1, e2, _) as hpred :: sigma_rest -> + (match is_root prop e1 e with + | None -> + let sigma_irrelevant' = hpred :: sigma_irrelevant + in f sigma_irrelevant' e sigma_rest + | Some _ -> + if (k == Sil.Lseg_NE || check_pi_implies_disequal e1 e2) then + let sigma_irrelevant' = (list_rev sigma_irrelevant) @ sigma_rest + in Some (true, sigma_irrelevant') + else if (Sil.exp_equal e2 Sil.exp_zero) then + let sigma_irrelevant' = (list_rev sigma_irrelevant) @ sigma_rest + in Some (false, sigma_irrelevant') + else + let sigma_rest' = (list_rev sigma_irrelevant) @ sigma_rest + in f [] e2 sigma_rest') + | Sil.Hdllseg (Sil.Lseg_NE, _, iF, oB, oF, iB, _) :: sigma_rest -> + if is_root prop iF e != None || is_root prop iB e != None then + let sigma_irrelevant' = (list_rev sigma_irrelevant) @ sigma_rest + in Some (true, sigma_irrelevant') + else + let sigma_irrelevant' = (list_rev sigma_irrelevant) @ sigma_rest + in Some (false, sigma_irrelevant') + | Sil.Hdllseg (Sil.Lseg_PE, _, iF, oB, oF, iB, _) as hpred :: sigma_rest -> + (match is_root prop iF e with + | None -> + let sigma_irrelevant' = hpred :: sigma_irrelevant + in f sigma_irrelevant' e sigma_rest + | Some _ -> + if (check_pi_implies_disequal iF oF) then + let sigma_irrelevant' = (list_rev sigma_irrelevant) @ sigma_rest + in Some (true, sigma_irrelevant') + else if (Sil.exp_equal oF Sil.exp_zero) then + let sigma_irrelevant' = (list_rev sigma_irrelevant) @ sigma_rest + in Some (false, sigma_irrelevant') + else + let sigma_rest' = (list_rev sigma_irrelevant) @ sigma_rest + in f [] oF sigma_rest') in + let f_null_check sigma_irrelevant e sigma_rest = + if not (Sil.exp_equal e Sil.exp_zero) then f sigma_irrelevant e sigma_rest + else + let sigma_irrelevant' = (list_rev sigma_irrelevant) @ sigma_rest + in Some (false, sigma_irrelevant') + in match f_null_check [] n_e1 spatial_part with + | None -> false + | Some (e1_allocated, spatial_part_leftover) -> + (match f_null_check [] n_e2 spatial_part_leftover with + | None -> false + | Some ((e2_allocated : bool), _) -> e1_allocated || e2_allocated) in + let neq_pure_part () = + check_pi_implies_disequal n_e1 n_e2 in + check_disequal_const () || neq_pure_part () || neq_spatial_part () + +(** Check [prop |- e1<=e2], to be called from normalized atom *) +let check_le_normalized prop e1 e2 = + (* L.d_str "check_le_normalized "; Sil.d_exp e1; L.d_str " "; Sil.d_exp e2; L.d_ln (); *) + let eL, eR, off = match e1, e2 with + | Sil.BinOp(Sil.MinusA, f1, f2), Sil.Const (Sil.Cint n) -> + if Sil.exp_equal f1 f2 + then Sil.exp_zero, Sil.exp_zero, n + else f1, f2, n + | _ -> + e1, e2, Sil.Int.zero in + let ineq = Inequalities.from_prop prop in + let upper_lower_check () = + let upperL_opt = Inequalities.compute_upper_bound ineq eL in + let lowerR_opt = Inequalities.compute_lower_bound ineq eR in + match upperL_opt, lowerR_opt with + | None, _ | _, None -> false + | Some upper1, Some lower2 -> Sil.Int.leq upper1 (lower2 ++ Sil.Int.one ++ off) in + (upper_lower_check ()) + || (Inequalities.check_le ineq e1 e2) + || (check_equal prop e1 e2) + +(** Check [prop |- e1 false + | Some upper1, Some lower2 -> Sil.Int.leq upper1 lower2 in + (upper_lower_check ()) || (Inequalities.check_lt ineq e1 e2) + +(* given an atom and a proposition returns a unique identifier. *) +(* We use this to distinguish among different queries *) +let get_smt_key a p = + let tmp_filename = Filename.temp_file "smt_query" ".cns" in + let outc_tmp = open_out tmp_filename in + let fmt_tmp = F.formatter_of_out_channel outc_tmp in + let pe = Utils.pe_text in + let () = F.fprintf fmt_tmp "%a%a" (Sil.pp_atom pe) a (Prop.pp_prop pe) p in + close_out outc_tmp; + Digest.to_hex (Digest.file tmp_filename) + +(** Check whether [prop |- a]. False means dont know. *) +let check_atom prop a0 = + let a = Prop.atom_normalize_prop prop a0 in + let prop_no_fp = Prop.replace_sigma_footprint [] (Prop.replace_pi_footprint [] prop) in + if !Config.smt_output then begin + let pe = Utils.pe_text in + let key = get_smt_key a prop_no_fp in + let key_filename = DB.Results_dir.path_to_filename DB.Results_dir.Abs_source_dir [(key ^ ".cns")] in + let outc = open_out (DB.filename_to_string key_filename) in + let fmt = F.formatter_of_out_channel outc in + L.d_str ("ID: "^key); L.d_ln (); + L.d_str "CHECK_ATOM_BOUND: "; Sil.d_atom a; L.d_ln (); + L.d_str "WHERE:"; L.d_ln(); Prop.d_prop prop_no_fp; L.d_ln (); L.d_ln (); + let () = F.fprintf fmt "ID: %s @\nCHECK_ATOM_BOUND: %a@\nWHERE:@\n%a" key (Sil.pp_atom pe) a (Prop.pp_prop pe) prop_no_fp in + close_out outc; + end; + match a with + | Sil.Aeq (Sil.BinOp (Sil.Le, e1, e2), Sil.Const (Sil.Cint i)) when Sil.Int.isone i -> check_le_normalized prop e1 e2 + | Sil.Aeq (Sil.BinOp (Sil.Lt, e1, e2), Sil.Const (Sil.Cint i)) when Sil.Int.isone i -> check_lt_normalized prop e1 e2 + | Sil.Aeq (e1, e2) -> check_equal prop e1 e2 + | Sil.Aneq (e1, e2) -> check_disequal prop e1 e2 + +(** Check [prop |- e1<=e2]. Result [false] means "don't know". *) +let check_le prop e1 e2 = + let e1_le_e2 = Sil.BinOp (Sil.Le, e1, e2) in + check_atom prop (Prop.mk_inequality e1_le_e2) + +(** Check [prop |- e1 + is_root prop base n_e != None + | Sil.Hlseg (k, _, e1, e2, _) -> + if k == Sil.Lseg_NE || check_disequal prop e1 e2 then + is_root prop e1 n_e != None + else false + | Sil.Hdllseg (k, _, iF, oB, oF, iB, _) -> + if k == Sil.Lseg_NE || check_disequal prop iF oF || check_disequal prop iB oB then + is_root prop iF n_e != None || is_root prop iB n_e != None + else false + in list_exists f spatial_part + +(** Compute an upper bound of an expression *) +let compute_upper_bound_of_exp p e = + let ineq = Inequalities.from_prop p in + Inequalities.compute_upper_bound ineq e + +let pair_compare compare1 compare2 (x1, x2) (y1, y2) = + let n1 = compare1 x1 y1 in + if n1 <> 0 then n1 else compare2 x2 y2 + +(** Check if two hpreds have the same allocated lhs *) +let check_inconsistency_two_hpreds prop = + let sigma = Prop.get_sigma prop in + let rec f e sigma_seen = function + | [] -> false + | (Sil.Hpointsto (e1, _, _) as hpred) :: sigma_rest + | (Sil.Hlseg (Sil.Lseg_NE, _, e1, _, _) as hpred) :: sigma_rest -> + if Sil.exp_equal e1 e then true + else f e (hpred:: sigma_seen) sigma_rest + | (Sil.Hdllseg (Sil.Lseg_NE, _, iF, _, _, iB, _) as hpred) :: sigma_rest -> + if Sil.exp_equal iF e || Sil.exp_equal iB e then true + else f e (hpred:: sigma_seen) sigma_rest + | Sil.Hlseg (Sil.Lseg_PE, _, e1, Sil.Const (Sil.Cint i), _) as hpred :: sigma_rest when Sil.Int.iszero i -> + if Sil.exp_equal e1 e then true + else f e (hpred:: sigma_seen) sigma_rest + | Sil.Hlseg (Sil.Lseg_PE, _, e1, e2, _) as hpred :: sigma_rest -> + if Sil.exp_equal e1 e + then + let prop' = Prop.normalize (Prop.from_sigma (sigma_seen@sigma_rest)) in + let prop_new = Prop.conjoin_eq e1 e2 prop' in + let sigma_new = Prop.get_sigma prop_new in + let e_new = Prop.exp_normalize_prop prop_new e + in f e_new [] sigma_new + else f e (hpred:: sigma_seen) sigma_rest + | Sil.Hdllseg (Sil.Lseg_PE, _, e1, e2, Sil.Const (Sil.Cint i), _, _) as hpred :: sigma_rest when Sil.Int.iszero i -> + if Sil.exp_equal e1 e then true + else f e (hpred:: sigma_seen) sigma_rest + | Sil.Hdllseg (Sil.Lseg_PE, _, e1, e2, e3, e4, _) as hpred :: sigma_rest -> + if Sil.exp_equal e1 e + then + let prop' = Prop.normalize (Prop.from_sigma (sigma_seen@sigma_rest)) in + let prop_new = Prop.conjoin_eq e1 e3 prop' in + let sigma_new = Prop.get_sigma prop_new in + let e_new = Prop.exp_normalize_prop prop_new e + in f e_new [] sigma_new + else f e (hpred:: sigma_seen) sigma_rest in + let rec check sigma_seen = function + | [] -> false + | (Sil.Hpointsto (e1, _, _) as hpred) :: sigma_rest + | (Sil.Hlseg (Sil.Lseg_NE, _, e1, _, _) as hpred) :: sigma_rest -> + if (f e1 [] (sigma_seen@sigma_rest)) then true + else check (hpred:: sigma_seen) sigma_rest + | Sil.Hdllseg (Sil.Lseg_NE, _, iF, _, _, iB, _) as hpred :: sigma_rest -> + if f iF [] (sigma_seen@sigma_rest) || f iB [] (sigma_seen@sigma_rest) then true + else check (hpred:: sigma_seen) sigma_rest + | (Sil.Hlseg (Sil.Lseg_PE, _, _, _, _) as hpred) :: sigma_rest + | (Sil.Hdllseg (Sil.Lseg_PE, _, _, _, _, _, _) as hpred) :: sigma_rest -> + check (hpred:: sigma_seen) sigma_rest in + check [] sigma + +(** Inconsistency checking ignoring footprint. *) +let check_inconsistency_base prop = + let pi = Prop.get_pi prop in + let sigma = Prop.get_sigma prop in + let inconsistent_ptsto _ = + check_allocatedness prop Sil.exp_zero in + let inconsistent_this () = (* "this" cannot be null in Java *) + let do_hpred = function + | Sil.Hpointsto (Sil.Lvar pv, Sil.Eexp (e, _), _) -> + !Sil.curr_language = Sil.Java && + Sil.pvar_is_this pv && + Sil.exp_equal e Sil.exp_zero && + Sil.pvar_is_seed pv + | _ -> false in + list_exists do_hpred sigma in + let inconsistent_self () = (* "self" cannot be null in ObjC, outside of initializers *) + let procdesc = Cfg.Node.get_proc_desc (State.get_node ()) in + let procname = Cfg.Procdesc.get_proc_name procdesc in + let procedure_attr = Cfg.Procdesc.get_attributes procdesc in + let do_hpred = function + | Sil.Hpointsto (Sil.Lvar pv, Sil.Eexp (e, _), _) -> + !Sil.curr_language = Sil.C_CPP && + Sil.exp_equal e Sil.exp_zero && + Sil.pvar_is_seed pv && + Sil.pvar_get_name pv = Mangled.from_string "self" && + procedure_attr.Sil.is_objc_instance_method && + not (Procname.is_constructor procname) + | _ -> false in + list_exists do_hpred sigma in + let inconsistent_atom = function + | Sil.Aeq (e1, e2) -> + (match e1, e2 with + | Sil.Const c1, Sil.Const c2 -> not (Sil.const_equal c1 c2) + | _ -> check_disequal prop e1 e2) + | Sil.Aneq (e1, e2) -> + (match e1, e2 with + | Sil.Const c1, Sil.Const c2 -> Sil.const_equal c1 c2 + | _ -> (Sil.exp_compare e1 e2 = 0)) in + let inconsistent_inequalities () = + let ineq = Inequalities.from_prop prop in + (* + L.d_strln "Inequalities:"; + L.d_strln "Prop: "; Prop.d_prop prop; L.d_ln (); + L.d_str "leqs: "; Inequalities.d_leqs ineq; L.d_ln (); + L.d_str "lts: "; Inequalities.d_lts ineq; L.d_ln (); + L.d_str "neqs: "; Inequalities.d_neqs ineq; L.d_ln (); + *) + Inequalities.inconsistent ineq in + inconsistent_ptsto () + || check_inconsistency_two_hpreds prop + || list_exists inconsistent_atom pi + || inconsistent_inequalities () + || inconsistent_this () + || inconsistent_self () + +(** Inconsistency checking. *) +let check_inconsistency prop = + (check_inconsistency_base prop) + || + (check_inconsistency_base (Prop.normalize (Prop.extract_footprint prop))) + +(** Inconsistency checking for the pi part ignoring footprint. *) +let check_inconsistency_pi pi = + check_inconsistency_base (Prop.normalize (Prop.from_pi pi)) + +(** {2 Abduction prover} *) + +type subst2 = Sil.subst * Sil.subst + +type exc_body = + | EXC_FALSE + | EXC_FALSE_HPRED of Sil.hpred + | EXC_FALSE_EXPS of Sil.exp * Sil.exp + | EXC_FALSE_SEXPS of Sil.strexp * Sil.strexp + | EXC_FALSE_ATOM of Sil.atom + | EXC_FALSE_SIGMA of Sil.hpred list + +exception IMPL_EXC of string * subst2 * exc_body + +exception MISSING_EXC of string + +type check = + | Bounds_check + | Class_cast_check of Sil.exp * Sil.exp * Sil.exp + +let d_typings typings = + let d_elem (exp, texp) = + Sil.d_exp exp; L.d_str ": "; Sil.d_texp_full texp; L.d_str " " in + list_iter d_elem typings + +(** Module to encapsulate operations on the internal state of the prover *) +module ProverState : sig + val reset : Prop.normal Prop.t -> Prop.exposed Prop.t -> unit + val checks : check list ref + + type bounds_check = (** type for array bounds checks *) + | BCsize_imply of Sil.exp * Sil.exp * Sil.exp list (** coming from array_size_imply *) + | BCfrom_pre of Sil.atom (** coming implicitly from preconditions *) + + val add_bounds_check : bounds_check -> unit + val add_frame_fld : Sil.hpred -> unit + val add_frame_typ : Sil.exp * Sil.exp -> unit + val add_missing_fld : Sil.hpred -> unit + val add_missing_pi : Sil.atom -> unit + val add_missing_sigma : Sil.hpred list -> unit + val add_missing_typ : Sil.exp * Sil.exp -> unit + + val atom_is_array_bounds_check : Sil.atom -> bool (** check if atom in pre is a bounds check *) + + val get_bounds_checks : unit -> bounds_check list + val get_frame_fld : unit -> Sil.hpred list + val get_frame_typ : unit -> (Sil.exp * Sil.exp) list + val get_missing_fld : unit -> Sil.hpred list + val get_missing_pi : unit -> Sil.atom list + val get_missing_sigma : unit -> Sil.hpred list + val get_missing_typ : unit -> (Sil.exp * Sil.exp) list + + val d_implication : Sil.subst * Sil.subst -> 'a Prop.t * 'b Prop.t -> unit + val d_implication_error : string * (Sil.subst * Sil.subst) * exc_body -> unit +end = struct + type bounds_check = + | BCsize_imply of Sil.exp * Sil.exp * Sil.exp list + | BCfrom_pre of Sil.atom + + let implication_lhs = ref Prop.prop_emp + let implication_rhs = ref (Prop.expose Prop.prop_emp) + let fav_in_array_size = ref (Sil.fav_new ()) (* free variables in array size position *) + let bounds_checks = ref [] (* delayed bounds check for arrays *) + let frame_fld = ref [] + let missing_fld = ref [] + let missing_pi = ref [] + let missing_sigma = ref [] + let frame_typ = ref [] + let missing_typ = ref [] + let checks = ref [] + + (** free vars in array size position in current part of prop *) + let prop_fav_size prop = + let fav = Sil.fav_new () in + let do_hpred = function + | Sil.Hpointsto (_, Sil.Earray (Sil.Var _ as size, _, _), _) -> + Sil.exp_fav_add fav size + | _ -> () in + list_iter do_hpred (Prop.get_sigma prop); + fav + + let reset lhs rhs = + checks := []; + implication_lhs := lhs; + implication_rhs := rhs; + fav_in_array_size := prop_fav_size rhs; + bounds_checks := []; + frame_fld := []; + frame_typ := []; + missing_fld := []; + missing_pi := []; + missing_sigma := []; + missing_typ := [] + + let add_bounds_check bounds_check = + bounds_checks := bounds_check :: !bounds_checks + + let add_frame_fld hpred = + frame_fld := hpred :: !frame_fld + + let add_missing_fld hpred = + missing_fld := hpred :: !missing_fld + + let add_frame_typ typing = + frame_typ := typing :: !frame_typ + + let add_missing_typ typing = + missing_typ := typing :: !missing_typ + + let add_missing_pi a = + missing_pi := a :: !missing_pi + + let add_missing_sigma sigma = + missing_sigma := sigma @ !missing_sigma + + (** atom considered array bounds check if it contains vars present in array size position in the pre *) + let atom_is_array_bounds_check atom = + let fav_a = Sil.atom_fav atom in + Prop.atom_is_inequality atom && + Sil.fav_exists fav_a (fun a -> Sil.fav_mem !fav_in_array_size a) + + let get_bounds_checks () = !bounds_checks + let get_frame_fld () = !frame_fld + let get_frame_typ () = !frame_typ + let get_missing_fld () = !missing_fld + let get_missing_pi () = !missing_pi + let get_missing_sigma () = !missing_sigma + let get_missing_typ () = !missing_typ + + let _d_missing sub = + L.d_strln "SUB: "; + L.d_increase_indent 1; Prop.d_sub sub; L.d_decrease_indent 1; + if !missing_pi != [] && !missing_sigma != [] + then (L.d_ln (); Prop.d_pi !missing_pi; L.d_str "*"; L.d_ln (); Prop.d_sigma !missing_sigma) + else if !missing_pi != [] + then (L.d_ln (); Prop.d_pi !missing_pi) + else if !missing_sigma != [] + then (L.d_ln (); Prop.d_sigma !missing_sigma); + if !missing_fld != [] then + begin + L.d_ln (); + L.d_strln "MISSING FLD: "; L.d_increase_indent 1; Prop.d_sigma !missing_fld; L.d_decrease_indent 1 + end; + if !missing_typ != [] then + begin + L.d_ln (); + L.d_strln "MISSING TYPING: "; L.d_increase_indent 1; d_typings !missing_typ; L.d_decrease_indent 1 + end + + let d_missing sub = (* optional print of missing: if print something, prepend with newline *) + if !missing_pi != [] || !missing_sigma!=[] || !missing_fld != [] || !missing_typ != [] || Sil.sub_to_list sub != [] then + begin + L.d_ln (); + L.d_str "["; + _d_missing sub; + L.d_str "]" + end + + let d_frame_fld () = (* optional print of frame fld: if print something, prepend with newline *) + if !frame_fld != [] then + begin + L.d_ln (); + L.d_strln "[FRAME FLD:"; + L.d_increase_indent 1; Prop.d_sigma !frame_fld; L.d_str "]"; L.d_decrease_indent 1 + end + + let d_frame_typ () = (* optional print of frame typ: if print something, prepend with newline *) + if !frame_typ != [] then + begin + L.d_ln (); + L.d_strln "[FRAME TYPING:"; + L.d_increase_indent 1; d_typings !frame_typ; L.d_str "]"; L.d_decrease_indent 1 + end + + (** Dump an implication *) + let d_implication (sub1, sub2) (p1, p2) = + let p1, p2 = Prop.prop_sub sub1 p1, Prop.prop_sub sub2 p2 in + L.d_strln "SUB:"; + L.d_increase_indent 1; Prop.d_sub sub1; L.d_decrease_indent 1; L.d_ln (); + Prop.d_prop p1; + d_missing sub2; L.d_ln (); + L.d_strln "|-"; + Prop.d_prop p2; + d_frame_fld (); + d_frame_typ () + + let d_implication_error (s, subs, body) = + let p1, p2 = !implication_lhs,!implication_rhs in + let d_inner () = match body with + | EXC_FALSE -> + () + | EXC_FALSE_HPRED hpred -> + L.d_str " on "; + Sil.d_hpred hpred; + | EXC_FALSE_EXPS (e1, e2) -> + L.d_str " on "; + Sil.d_exp e1; L.d_str ","; Sil.d_exp e2; + | EXC_FALSE_SEXPS (se1, se2) -> + L.d_str " on "; + Sil.d_sexp se1; L.d_str ","; Sil.d_sexp se2; + | EXC_FALSE_ATOM a -> + L.d_str " on "; + Sil.d_atom a; + | EXC_FALSE_SIGMA sigma -> + L.d_str " on "; + Prop.d_sigma sigma in + L.d_ln (); + L.d_strln "$$$$$$$ Implication"; + d_implication subs (p1, p2); L.d_ln (); + L.d_str ("$$$$$$ error: " ^ s); d_inner (); + L.d_strln " returning FALSE"; + L.d_ln () +end + +let d_impl = ProverState.d_implication +let d_impl_err = ProverState.d_implication_error + +(** extend a substitution *) +let extend_sub sub v e = + let new_sub = Sil.sub_of_list [v, e] in + Sil.sub_join new_sub (Sil.sub_range_map (Sil.exp_sub new_sub) sub) + +(** Extend [sub1] and [sub2] to witnesses that each instance of +[e1[sub1]] is an instance of [e2[sub2]]. Raise IMPL_FALSE if not +possible. *) +let exp_imply calc_missing subs e1_in e2_in : subst2 = + let e1 = Prop.exp_normalize_noabs (fst subs) e1_in in + let e2 = Prop.exp_normalize_noabs (snd subs) e2_in in + let var_imply subs v1 v2 : subst2 = + match Ident.is_primed v1, Ident.is_primed v2 with + | false, false -> + if Ident.equal v1 v2 then subs + else if calc_missing && Ident.is_footprint v1 && Ident.is_footprint v2 + then + let () = ProverState.add_missing_pi (Sil.Aeq (e1_in, e2_in)) in + subs + else raise (IMPL_EXC ("exps", subs, (EXC_FALSE_EXPS (e1, e2)))) + | true, false -> raise (IMPL_EXC ("exps", subs, (EXC_FALSE_EXPS (e1, e2)))) + | false, true -> + let sub2' = extend_sub (snd subs) v2 (Sil.exp_sub (fst subs) (Sil.Var v1)) in + (fst subs, sub2') + | true, true -> + let v1' = Ident.create_fresh Ident.knormal in + let sub1' = extend_sub (fst subs) v1 (Sil.Var v1') in + let sub2' = extend_sub (snd subs) v2 (Sil.Var v1') in + (sub1', sub2') in + let rec do_imply subs e1 e2 : subst2 = + L.d_str "do_imply "; Sil.d_exp e1; L.d_str " "; Sil.d_exp e2; L.d_ln (); + match e1, e2 with + | Sil.Var v1, Sil.Var v2 -> + var_imply subs v1 v2 + | e1, Sil.Var v2 -> + let occurs_check v e = (* check whether [v] occurs in normalized [e] *) + if Sil.fav_mem (Sil.exp_fav e) v + && Sil.fav_mem (Sil.exp_fav (Prop.exp_normalize_prop Prop.prop_emp e)) v + then raise (IMPL_EXC ("occurs check", subs, (EXC_FALSE_EXPS (e1, e2)))) in + if Ident.is_primed v2 then + let () = occurs_check v2 e1 in + let sub2' = extend_sub (snd subs) v2 e1 in + (fst subs, sub2') + else + raise (IMPL_EXC ("expressions not equal", subs, (EXC_FALSE_EXPS (e1, e2)))) + | e1, Sil.BinOp (Sil.PlusA, Sil.Var v2, e2) + | e1, Sil.BinOp (Sil.PlusA, e2, Sil.Var v2) when Ident.is_primed v2 || Ident.is_footprint v2 -> + do_imply subs (Sil.BinOp (Sil.MinusA, e1, e2)) (Sil.Var v2) + | Sil.Var v1, e2 -> + if calc_missing then + let () = ProverState.add_missing_pi (Sil.Aeq (e1_in, e2_in)) in + subs + else raise (IMPL_EXC ("expressions not equal", subs, (EXC_FALSE_EXPS (e1, e2)))) + | Sil.Lvar pv1, Sil.Const _ when Sil.pvar_is_global pv1 -> + if calc_missing then + let () = ProverState.add_missing_pi (Sil.Aeq (e1_in, e2_in)) in + subs + else raise (IMPL_EXC ("expressions not equal", subs, (EXC_FALSE_EXPS (e1, e2)))) + | Sil.Lvar v1, Sil.Lvar v2 -> + if Sil.pvar_equal v1 v2 then subs + else raise (IMPL_EXC ("expressions not equal", subs, (EXC_FALSE_EXPS (e1, e2)))) + | Sil.Const c1, Sil.Const c2 -> + if (Sil.const_equal c1 c2) then subs + else raise (IMPL_EXC ("constants not equal", subs, (EXC_FALSE_EXPS (e1, e2)))) + | Sil.Const (Sil.Cint n1), Sil.BinOp (Sil.PlusPI, _, _) -> + raise (IMPL_EXC ("pointer+index cannot evaluate to a constant", subs, (EXC_FALSE_EXPS (e1, e2)))) + | Sil.Const (Sil.Cint n1), Sil.BinOp (Sil.PlusA, f1, Sil.Const (Sil.Cint n2)) -> + do_imply subs (Sil.exp_int (n1 -- n2)) f1 + | Sil.BinOp(op1, e1, f1), Sil.BinOp(op2, e2, f2) when op1 == op2 -> + do_imply (do_imply subs e1 e2) f1 f2 + | Sil.BinOp (Sil.PlusA, Sil.Var v1, e1), e2 -> + do_imply subs (Sil.Var v1) (Sil.BinOp (Sil.MinusA, e2, e1)) + | Sil.BinOp (Sil.PlusPI, Sil.Lvar pv1, e1), e2 -> + do_imply subs (Sil.Lvar pv1) (Sil.BinOp (Sil.MinusA, e2, e1)) + | e1, Sil.Const _ -> + raise (IMPL_EXC ("lhs not constant", subs, (EXC_FALSE_EXPS (e1, e2)))) + | Sil.Lfield(e1, fd1, t1), Sil.Lfield(e2, fd2, t2) when fd1 == fd2 -> + do_imply subs e1 e2 + | Sil.Lindex(e1, f1), Sil.Lindex(e2, f2) -> + do_imply (do_imply subs e1 e2) f1 f2 + | _ -> + d_impl_err ("exp_imply not implemented", subs, (EXC_FALSE_EXPS (e1, e2))); + raise (Exceptions.Abduction_case_not_implemented (try assert false with Assert_failure x -> x)) in + do_imply subs e1 e2 + +(** Convert a path (from lhs of a |-> to a field name present only in +the rhs) into an id. If the lhs was a footprint var, the id is a +new footprint var. Othewise it is a var with the path in the name +and stamp - 1 *) +let path_to_id path = + let rec f = function + | Sil.Var id -> + if Ident.is_footprint id then None + else Some (Ident.name_to_string (Ident.get_name id) ^ (string_of_int (Ident.get_stamp id))) + | Sil.Lfield (e, fld, t) -> + (match f e with + | None -> None + | Some s -> Some (s ^ "_" ^ (Ident.fieldname_to_string fld))) + | Sil.Lindex (e, ind) -> + (match f e with + | None -> None + | Some s -> Some (s ^ "_" ^ (Sil.exp_to_string ind))) + | Sil.Lvar pv -> + Some (Sil.exp_to_string path) + | Sil.Const (Sil.Cstr s) -> + Some ("_const_str_" ^ s) + | Sil.Const (Sil.Cclass c) -> + Some ("_const_class_" ^ Ident.name_to_string c) + | Sil.Const _ -> None + | _ -> + L.d_str "path_to_id undefined on "; Sil.d_exp path; L.d_ln (); + assert false (* None *) in + if !Config.footprint then Ident.create_fresh Ident.kfootprint + else match f path with + | None -> Ident.create_fresh Ident.kfootprint + | Some s -> Ident.create_path s + +(** Implication for the size of arrays *) +let array_size_imply calc_missing subs size1 size2 indices2 = + match size1, size2 with + | _, Sil.Var _ + | _, Sil.BinOp (Sil.PlusA, Sil.Var _, _) + | _, Sil.BinOp (Sil.PlusA, _, Sil.Var _) + | Sil.BinOp (Sil.Mult, _, _), _ -> + (try exp_imply calc_missing subs size1 size2 with + | IMPL_EXC (s, subs', x) -> + raise (IMPL_EXC ("array size:" ^ s, subs', x))) + | _ -> + ProverState.add_bounds_check (ProverState.BCsize_imply (size1, size2, indices2)); + subs + +(** Extend [sub1] and [sub2] to witnesses that each instance of +[se1[sub1]] is an instance of [se2[sub2]]. Raise IMPL_FALSE if not +possible. *) +let rec sexp_imply source calc_index_frame calc_missing subs se1 se2 typ2 : subst2 * (Sil.strexp option) * (Sil.strexp option) = + (* L.d_str "sexp_imply "; Sil.d_sexp se1; L.d_str " "; Sil.d_sexp se2; L.d_str " : "; Sil.d_typ_full typ2; L.d_ln(); *) + match se1, se2 with + | Sil.Eexp (e1, inst1), Sil.Eexp (e2, inst2) -> + (exp_imply calc_missing subs e1 e2, None, None) + | Sil.Estruct (fsel1, inst1), Sil.Estruct (fsel2, _) -> + let subs', fld_frame, fld_missing = struct_imply source calc_missing subs fsel1 fsel2 typ2 in + let fld_frame_opt = if fld_frame != [] then Some (Sil.Estruct (fld_frame, inst1)) else None in + let fld_missing_opt = if fld_missing != [] then Some (Sil.Estruct (fld_missing, inst1)) else None in + subs', fld_frame_opt, fld_missing_opt + | Sil.Estruct _, Sil.Eexp (e2, inst2) -> + begin + let e2' = Sil.exp_sub (snd subs) e2 in + match e2' with + | Sil.Var id2 when Ident.is_primed id2 -> + let id2' = Ident.create_fresh Ident.knormal in + let sub2' = extend_sub (snd subs) id2 (Sil.Var id2') in + (fst subs, sub2'), None, None + | _ -> + d_impl_err ("sexp_imply not implemented", subs, (EXC_FALSE_SEXPS (se1, se2))); + raise (Exceptions.Abduction_case_not_implemented (try assert false with Assert_failure x -> x)) + end + | Sil.Earray (size1, esel1, inst1), Sil.Earray (size2, esel2, _) -> + let indices2 = list_map fst esel2 in + let subs' = array_size_imply calc_missing subs size1 size2 indices2 in + if Sil.strexp_equal se1 se2 then subs', None, None + else begin + let subs'', index_frame, index_missing = array_imply source calc_index_frame calc_missing subs' esel1 esel2 typ2 in + let index_frame_opt = if index_frame != [] then Some (Sil.Earray (size1, index_frame, inst1)) else None in + let index_missing_opt = if index_missing != [] && (!Config.Experiment.allow_missing_index_in_proc_call || !Config.footprint) then Some (Sil.Earray (size1, index_missing, inst1)) else None in + subs'', index_frame_opt, index_missing_opt + end + | Sil.Eexp (_, inst), Sil.Estruct (fsel, inst') -> + d_impl_err ("WARNING: function call with parameters of struct type, treating as unknown", subs, (EXC_FALSE_SEXPS (se1, se2))); + let fsel' = + let g (f, se) = (f, Sil.Eexp (Sil.Var (Ident.create_fresh Ident.knormal), inst)) in + list_map g fsel in + sexp_imply source calc_index_frame calc_missing subs (Sil.Estruct (fsel', inst')) se2 typ2 + | Sil.Eexp _, Sil.Earray (size, esel, inst) + | Sil.Estruct _, Sil.Earray (size, esel, inst) -> + let se1' = Sil.Earray (size, [(Sil.exp_zero, se1)], inst) in + sexp_imply source calc_index_frame calc_missing subs se1' se2 typ2 + | Sil.Earray (size, _, _), Sil.Eexp (e, inst) -> + let se2' = Sil.Earray (size, [(Sil.exp_zero, se2)], inst) in + let typ2' = Sil.Tarray (typ2, size) in + sexp_imply source true calc_missing subs se1 se2' typ2' (* calculate index_frame because the rhs is a singleton array *) + | _ -> + d_impl_err ("sexp_imply not implemented", subs, (EXC_FALSE_SEXPS (se1, se2))); + raise (Exceptions.Abduction_case_not_implemented (try assert false with Assert_failure x -> x)) + +and struct_imply source calc_missing subs fsel1 fsel2 typ2 : subst2 * ((Ident.fieldname * Sil.strexp) list) * ((Ident.fieldname * Sil.strexp) list) = + match fsel1, fsel2 with + | _, [] -> subs, fsel1, [] + | (f1, se1) :: fsel1', (f2, se2) :: fsel2' -> + begin + match Ident.fieldname_compare f1 f2 with + | 0 -> + let typ' = Sil.struct_typ_fld (Some Sil.Tvoid) f2 typ2 in + let subs', se_frame, se_missing = sexp_imply (Sil.Lfield (source, f2, typ2)) false calc_missing subs se1 se2 typ' in + let subs'', fld_frame, fld_missing = struct_imply source calc_missing subs' fsel1' fsel2' typ2 in + let fld_frame' = match se_frame with + | None -> fld_frame + | Some se -> (f1, se):: fld_frame in + let fld_missing' = match se_missing with + | None -> fld_missing + | Some se -> (f1, se):: fld_missing in + subs'', fld_frame', fld_missing' + | n when n < 0 -> + let subs', fld_frame, fld_missing = struct_imply source calc_missing subs fsel1' fsel2 typ2 in + subs', ((f1, se1) :: fld_frame), fld_missing + | _ -> + let typ' = Sil.struct_typ_fld (Some Sil.Tvoid) f2 typ2 in + let subs' = sexp_imply_nolhs (Sil.Lfield (source, f2, typ2)) calc_missing subs se2 typ' in + let subs', fld_frame, fld_missing = struct_imply source calc_missing subs' fsel1 fsel2' typ2 in + let fld_missing' = (f2, se2) :: fld_missing in + subs', fld_frame, fld_missing' + end + | [], (f2, se2) :: fsel2' -> + let typ' = Sil.struct_typ_fld (Some Sil.Tvoid) f2 typ2 in + let subs' = sexp_imply_nolhs (Sil.Lfield (source, f2, typ2)) calc_missing subs se2 typ' in + let subs'', fld_frame, fld_missing = struct_imply source calc_missing subs' [] fsel2' typ2 in + subs'', fld_frame, (f2, se2):: fld_missing + +and array_imply source calc_index_frame calc_missing subs esel1 esel2 typ2 +: subst2 * ((Sil.exp * Sil.strexp) list) * ((Sil.exp * Sil.strexp) list) += + let typ_elem = Sil.array_typ_elem (Some Sil.Tvoid) typ2 in + match esel1, esel2 with + | _,[] -> subs, esel1, [] + | (e1, se1) :: esel1', (e2, se2) :: esel2' -> + let e1n = Prop.exp_normalize_noabs (fst subs) e1 in + let e2n = Prop.exp_normalize_noabs (snd subs) e2 in + let n = Sil.exp_compare e1n e2n in + if n < 0 then array_imply source calc_index_frame calc_missing subs esel1' esel2 typ2 + else if n > 0 then array_imply source calc_index_frame calc_missing subs esel1 esel2' typ2 + else (* n=0 *) + let subs', _, _ = sexp_imply (Sil.Lindex (source, e1)) false calc_missing subs se1 se2 typ_elem in + array_imply source calc_index_frame calc_missing subs' esel1' esel2' typ2 + | [], (e2, se2) :: esel2' -> + let subs' = sexp_imply_nolhs (Sil.Lindex (source, e2)) calc_missing subs se2 typ_elem in + let subs'', index_frame, index_missing = array_imply source calc_index_frame calc_missing subs' [] esel2' typ2 in + let index_missing' = (e2, se2) :: index_missing in + subs'', index_frame, index_missing' + +and sexp_imply_nolhs source calc_missing subs se2 typ2 = + match se2 with + | Sil.Eexp (_e2, inst) -> + let e2 = Sil.exp_sub (snd subs) _e2 in + begin + match e2 with + | Sil.Var v2 when Ident.is_primed v2 -> + let v2' = path_to_id source in + (* L.d_str "called path_to_id on "; Sil.d_exp e2; L.d_str " returns "; Sil.d_exp (Sil.Var v2'); L.d_ln (); *) + let sub2' = extend_sub (snd subs) v2 (Sil.Var v2') in + (fst subs, sub2') + | Sil.Var _ -> + if calc_missing then subs + else raise (IMPL_EXC ("exp only in rhs is not a primed var", subs, EXC_FALSE)) + | Sil.Const _ when calc_missing -> + let id = path_to_id source in + ProverState.add_missing_pi (Sil.Aeq (Sil.Var id, _e2)); + subs + | _ -> + raise (IMPL_EXC ("exp only in rhs is not a primed var", subs, EXC_FALSE)) + end + | Sil.Estruct (fsel2, _) -> + (fun (x, y, z) -> x) (struct_imply source calc_missing subs [] fsel2 typ2) + | Sil.Earray (_, esel2, _) -> + (fun (x, y, z) -> x) (array_imply source false calc_missing subs [] esel2 typ2) + +let rec exp_list_imply calc_missing subs l1 l2 = match l1, l2 with + | [],[] -> subs + | e1:: l1, e2:: l2 -> + exp_list_imply calc_missing (exp_imply calc_missing subs e1 e2) l1 l2 + | _ -> assert false + +let filter_ptsto_lhs sub e0 = function + | Sil.Hpointsto (e, _, _) -> if Sil.exp_equal e0 (Sil.exp_sub sub e) then Some () else None + | _ -> None + +let filter_ne_lhs sub e0 = function + | Sil.Hpointsto (e, _, _) -> if Sil.exp_equal e0 (Sil.exp_sub sub e) then Some () else None + | Sil.Hlseg (Sil.Lseg_NE, _, e, _, _) -> if Sil.exp_equal e0 (Sil.exp_sub sub e) then Some () else None + | Sil.Hdllseg (Sil.Lseg_NE, _, e, _, _, e', _) -> + if Sil.exp_equal e0 (Sil.exp_sub sub e) || Sil.exp_equal e0 (Sil.exp_sub sub e') + then Some () + else None + | _ -> None + +let filter_hpred sub hpred2 hpred1 = match (Sil.hpred_sub sub hpred1), hpred2 with + | Sil.Hlseg(Sil.Lseg_NE, hpara1, e1, f1, el1), Sil.Hlseg(Sil.Lseg_PE, hpara2, e2, f2, el2) -> + if Sil.hpred_equal (Sil.Hlseg(Sil.Lseg_PE, hpara1, e1, f1, el1)) hpred2 then Some false else None + | Sil.Hlseg(Sil.Lseg_PE, hpara1, e1, f1, el1), Sil.Hlseg(Sil.Lseg_NE, hpara2, e2, f2, el2) -> + if Sil.hpred_equal (Sil.Hlseg(Sil.Lseg_NE, hpara1, e1, f1, el1)) hpred2 then Some true else None (* return missing disequality *) + | Sil.Hpointsto(e1, se1, te1), Sil.Hlseg(k, hpara2, e2, f2, el2) -> + if Sil.exp_equal e1 e2 then Some false else None + | hpred1, hpred2 -> if Sil.hpred_equal hpred1 hpred2 then Some false else None + +let hpred_has_primed_lhs sub hpred = + let rec find_primed e = match e with + | Sil.Lfield (e, _, _) -> + find_primed e + | Sil.Lindex (e, _) -> + find_primed e + | Sil.BinOp (Sil.PlusPI, e1, e2) -> + find_primed e1 + | _ -> + Sil.fav_exists (Sil.exp_fav e) Ident.is_primed in + let exp_has_primed e = find_primed (Sil.exp_sub sub e) in + match hpred with + | Sil.Hpointsto (e, _, _) -> + exp_has_primed e + | Sil.Hlseg (_, _, e, _, _) -> + exp_has_primed e + | Sil.Hdllseg (_, _, iF, oB, oF, iB, _) -> + exp_has_primed iF && exp_has_primed iB + +let move_primed_lhs_from_front subs sigma = match sigma with + | [] -> sigma + | hpred:: sigma' -> + if hpred_has_primed_lhs (snd subs) hpred then + let (sigma_primed, sigma_unprimed) = list_partition (hpred_has_primed_lhs (snd subs)) sigma + in match sigma_unprimed with + | [] -> raise (IMPL_EXC ("every hpred has primed lhs, cannot proceed", subs, (EXC_FALSE_SIGMA sigma))) + | _:: _ -> sigma_unprimed @ sigma_primed + else sigma + +let name_n = Ident.string_to_name "n" + +(** [expand_hpred_pointer calc_index_frame hpred] expands [hpred] if it is a |-> whose lhs is a Lfield or Lindex or ptr+off. +Return [(changed, calc_index_frame', hpred')] where [changed] indicates whether the predicate has changed. *) +let expand_hpred_pointer calc_index_frame hpred : bool * bool * Sil.hpred = + let rec expand changed calc_index_frame hpred = match hpred with + | Sil.Hpointsto (Sil.Lfield (e, fld, typ_fld), se, t) -> + let t' = match t, typ_fld with + | _, Sil.Tstruct _ -> (* the struct type of fld is known *) + Sil.Sizeof (typ_fld, Sil.Subtype.exact) + | Sil.Sizeof (_t, st), _ -> (* the struct type of fld is not known -- typically Tvoid *) + Sil.Sizeof (Sil.Tstruct ([(fld, _t, Sil.item_annotation_empty)], [], Sil.Struct, None, [], [], Sil.item_annotation_empty), st) (* None as we don't know the stuct name *) + | _ -> raise (Failure "expand_hpred_pointer: Unexpected non-sizeof type in Lfield") in + let hpred' = Sil.Hpointsto (e, Sil.Estruct ([(fld, se)], Sil.inst_none), t') in + expand true true hpred' + | Sil.Hpointsto (Sil.Lindex (e, ind), se, t) -> + let size = Sil.exp_get_undefined false in + let t' = match t with + | Sil.Sizeof (_t, st) -> + Sil.Sizeof (Sil.Tarray (_t, size), st) + | _ -> raise (Failure "expand_hpred_pointer: Unexpected non-sizeof type in Lindex") in + let hpred' = Sil.Hpointsto (e, Sil.Earray (size, [(ind, se)], Sil.inst_none), t') in + expand true true hpred' + | Sil.Hpointsto (Sil.BinOp (Sil.PlusPI, e1, e2), Sil.Earray (size, esel, inst), t) -> + let shift_exp e = Sil.BinOp (Sil.PlusA, e, e2) in + let size' = shift_exp size in + let esel' = list_map (fun (e, se) -> (shift_exp e, se)) esel in + let hpred' = Sil.Hpointsto (e1, Sil.Earray (size', esel', inst), t) in + expand true calc_index_frame hpred' + | _ -> changed, calc_index_frame, hpred in + expand false calc_index_frame hpred + +let object_type = Mangled.from_string "java.lang.Object" + +let serializable_type = Mangled.from_string "java.io.Serializable" + +let cloneable_type = Mangled.from_string "java.lang.Cloneable" + +let is_interface tenv c = + match Sil.tenv_lookup tenv (Sil.TN_csu (Sil.Class, c)) with + | Some (Sil.Tstruct (fields, sfields, Sil.Class, Some c1', supers1, methods, iann)) -> + (list_length fields = 0) && (list_length methods = 0) + | _ -> false + +(** check if c1 is a subclass of c2 *) +let check_subclass_tenv tenv c1 c2 = + let rec check (_, c) = + Mangled.equal c c2 || (Mangled.equal c2 object_type) || + match Sil.tenv_lookup tenv (Sil.TN_csu (Sil.Class, c)) with + | Some (Sil.Tstruct (_, _, Sil.Class, Some c1', supers1, _, _)) -> + list_exists check supers1 + | _ -> false in + (check (Sil.Class, c1)) + +let check_subclass tenv c1 c2 = + let f = check_subclass_tenv tenv in + Sil.Subtype.check_subtype f c1 c2 + +(** check that t1 and t2 are the same primitive type *) +let check_subtype_basic_type t1 t2 = + match t2 with + | (Sil.Tint Sil.IInt) | (Sil.Tint Sil.IBool) + | (Sil.Tint Sil.IChar) | (Sil.Tfloat Sil.FDouble) + | (Sil.Tfloat Sil.FFloat) | (Sil.Tint Sil.ILong) + | (Sil.Tint Sil.IShort) -> Sil.typ_equal t1 t2 + | _ -> false + +(** check if t1 is a subtype of t2 *) +let rec check_subtype tenv t1 t2 = + match t1, t2 with + | Sil.Tstruct (_, _, Sil.Class, Some c1, _, _, _), Sil.Tstruct (_, _, Sil.Class, Some c2, _, _, _) -> + (check_subclass tenv c1 c2) + + | Sil.Tarray (dom_type1, _), Sil.Tarray (dom_type2, _) -> + check_subtype tenv dom_type1 dom_type2 + + | Sil.Tptr (dom_type1, _), Sil.Tptr (dom_type2, _) -> + check_subtype tenv dom_type1 dom_type2 + + | Sil.Tarray _, Sil.Tstruct (_, _, Sil.Class, Some c2, _, _, _) -> + (Mangled.equal c2 serializable_type) || (Mangled.equal c2 cloneable_type) || (Mangled.equal c2 object_type) + + | _ -> (check_subtype_basic_type t1 t2) + +let rec case_analysis_type tenv (t1, st1) (t2, st2) = + match t1, t2 with + | Sil.Tstruct (_, _, Sil.Class, Some c1, _, _, _), Sil.Tstruct (_, _, Sil.Class, Some c2, _, _, _) -> + (Sil.Subtype.case_analysis (c1, st1) (c2, st2) (check_subclass tenv) (is_interface tenv)) + + | Sil.Tarray (dom_type1, _), Sil.Tarray (dom_type2, _) -> + (case_analysis_type tenv (dom_type1, st1) (dom_type2, st2)) + + | Sil.Tptr (dom_type1, _), Sil.Tptr (dom_type2, _) -> + (case_analysis_type tenv (dom_type1, st1) (dom_type2, st2)) + + | Sil.Tstruct (_, _, Sil.Class, Some c1, _, _, _), Sil.Tarray _ -> + if ((Mangled.equal c1 serializable_type) || (Mangled.equal c1 cloneable_type) || (Mangled.equal c1 object_type)) && + (st1 <> Sil.Subtype.exact) then (Some st1, None) + else (None, Some st1) + + | _ -> if (check_subtype_basic_type t1 t2) then (Some st1, None) + else (None, Some st1) + +(** perform case analysis on [texp1 <: texp2], and return the updated types in the true and false case, if they are possible *) +let subtype_case_analysis tenv texp1 texp2 = + match texp1, texp2 with + | Sil.Sizeof (t1, st1), Sil.Sizeof (t2, st2) -> + begin + let pos_opt, neg_opt = case_analysis_type tenv (t1, st1) (t2, st2) in + let pos_type_opt = match pos_opt with + | None -> None + | Some st1' -> + let t1' = if (check_subtype tenv t1 t2) then t1 else t2 in + Some (Sil.Sizeof (t1', st1')) in + let neg_type_opt = match neg_opt with + | None -> None + | Some st1' -> Some (Sil.Sizeof (t1, st1')) in + pos_type_opt, neg_type_opt + end + | _ -> (* don't know, consider both possibilities *) + Some texp1, Some texp1 + +let cast_exception tenv texp1 texp2 e1 subs = + let _ = match texp1, texp2 with + | Sil.Sizeof (t1, st1), Sil.Sizeof (t2, st2) -> + if (!Config.developer_mode) || ((Sil.Subtype.is_cast st2) && not (check_subtype tenv t1 t2)) then + ProverState.checks := Class_cast_check (texp1, texp2, e1) :: !ProverState.checks + | _ -> () in + raise (IMPL_EXC ("class cast exception", subs, EXC_FALSE)) + +(** Check the equality of two types ignoring flags in the subtyping components *) +let texp_equal_modulo_subtype_flag texp1 texp2 = match texp1, texp2 with + | Sil.Sizeof(t1, st1), Sil.Sizeof (t2, st2) -> + Sil.typ_equal t1 t2 && Sil.Subtype.equal_modulo_flag st1 st2 + | _ -> Sil.exp_equal texp1 texp2 + +(** check implication between type expressions *) +let texp_imply tenv subs texp1 texp2 e1 calc_missing = + let types_subject_to_cast = (* check whether the types could be subject to dynamic cast: classes and arrays *) + match texp1, texp2 with + | Sil.Sizeof (Sil.Tstruct _, _), Sil.Sizeof (Sil.Tstruct _, _) + | Sil.Sizeof (Sil.Tarray _, _), Sil.Sizeof (Sil.Tarray _, _) + | Sil.Sizeof (Sil.Tarray _, _), Sil.Sizeof (Sil.Tstruct _, _) + | Sil.Sizeof (Sil.Tstruct _, _), Sil.Sizeof (Sil.Tarray _, _) -> true + | _ -> false in + if !Sil.curr_language = Sil.Java && types_subject_to_cast then + begin + let pos_type_opt, neg_type_opt = subtype_case_analysis tenv texp1 texp2 in + let has_changed = match pos_type_opt with + | Some texp1' -> + not (texp_equal_modulo_subtype_flag texp1' texp1) + | None -> false in + if (calc_missing) then (* footprint *) + begin + match pos_type_opt with + | None -> cast_exception tenv texp1 texp2 e1 subs + | Some texp1' -> + if has_changed then None, pos_type_opt (* missing *) + else pos_type_opt, None (* frame *) + end + else (* re-execution *) + begin + match neg_type_opt with + | Some _ -> cast_exception tenv texp1 texp2 e1 subs + | None -> + if has_changed then cast_exception tenv texp1 texp2 e1 subs (* missing *) + else pos_type_opt, None (* frame *) + end + end + else + None, None + +(** pre-process implication between a non-array and an array: the non-array is turned into an array of size given by its type +only active in type_size mode *) +let sexp_imply_preprocess se1 texp1 se2 = match se1, texp1, se2 with + | Sil.Eexp (e1, inst), Sil.Sizeof _, Sil.Earray _ when !Config.type_size -> + let se1' = Sil.Earray (texp1, [(Sil.exp_zero, se1)], inst) in + L.d_strln_color Orange "sexp_imply_preprocess"; L.d_str " se1: "; Sil.d_sexp se1; L.d_ln (); L.d_str " se1': "; Sil.d_sexp se1'; L.d_ln (); + se1' + | _ -> se1 + +(** handle parameter subtype for java: when the type of a callee variable in the caller is a strict subtype +of the one in the callee, add a type frame and type missing *) +let handle_parameter_subtype tenv prop1 sigma2 subs (e1, se1, texp1) (se2, texp2) = + let is_callee = match e1 with + | Sil.Lvar pv -> Sil.pvar_is_callee pv + | _ -> false in + let is_allocated_lhs e = + let filter = function + | Sil.Hpointsto(e', _, _) -> Sil.exp_equal e' e + | _ -> false in + list_exists filter (Prop.get_sigma prop1) in + let type_rhs e = + let sub_opt = ref None in + let filter = function + | Sil.Hpointsto(e', _, Sil.Sizeof(t, sub)) when Sil.exp_equal e' e -> + sub_opt := Some (t, sub); + true + | _ -> false in + if list_exists filter sigma2 then !sub_opt else None in + let add_subtype () = match texp1, texp2, se1, se2 with + | Sil.Sizeof(Sil.Tptr (_t1, _), _), Sil.Sizeof(Sil.Tptr (_t2, _), sub2), Sil.Eexp(e1', _), Sil.Eexp(e2', _) when not (is_allocated_lhs e1') -> + begin + let t1, t2 = Sil.expand_type tenv _t1, Sil.expand_type tenv _t2 in + match type_rhs e2' with + | Some (t2_ptsto , sub2) -> + if not (Sil.typ_equal t1 t2) && check_subtype tenv t1 t2 + then begin + let pos_type_opt, _ = subtype_case_analysis tenv (Sil.Sizeof(t1, Sil.Subtype.subtypes)) (Sil.Sizeof(t2_ptsto, sub2)) in + match pos_type_opt with + | Some t1_noptr -> + ProverState.add_frame_typ (e1', t1_noptr); + ProverState.add_missing_typ (e2', t1_noptr) + | None -> cast_exception tenv texp1 texp2 e1 subs + end + | None -> () + end + | _ -> () in + if is_callee && !Config.footprint && (!Config.Experiment.activate_subtyping_in_cpp || !Sil.curr_language = Sil.Java) then add_subtype () + +let rec hpred_imply tenv calc_index_frame calc_missing subs prop1 sigma2 hpred2 : subst2 * Prop.normal Prop.t = match hpred2 with + | Sil.Hpointsto (_e2, se2, texp2) -> + let e2 = Sil.exp_sub (snd subs) _e2 in + let _ = match e2 with + | Sil.Lvar p -> () + | Sil.Var v -> if Ident.is_primed v then + (d_impl_err ("rhs |-> not implemented", subs, (EXC_FALSE_HPRED hpred2)); + raise (Exceptions.Abduction_case_not_implemented (try assert false with Assert_failure x -> x))) + | _ -> () in + (match Prop.prop_iter_create prop1 with + | None -> raise (IMPL_EXC ("lhs is empty", subs, EXC_FALSE)) + | Some iter1 -> + (match Prop.prop_iter_find iter1 (filter_ne_lhs (fst subs) e2) with + | None -> raise (IMPL_EXC ("lhs does not have e|->", subs, (EXC_FALSE_HPRED hpred2))) + | Some iter1' -> + (match Prop.prop_iter_current iter1' with + | Sil.Hpointsto (e1, se1, texp1), _ -> + (try + let typ2 = Sil.texp_to_typ (Some Sil.Tvoid) texp2 in + let typing_frame, typing_missing = texp_imply tenv subs texp1 texp2 e1 calc_missing in + let se1' = sexp_imply_preprocess se1 texp1 se2 in + let subs', fld_frame, fld_missing = sexp_imply e1 calc_index_frame calc_missing subs se1' se2 typ2 in + if calc_missing then + begin + handle_parameter_subtype tenv prop1 sigma2 subs (e1, se1, texp1) (se2, texp2); + (match fld_missing with + | Some fld_missing -> + ProverState.add_missing_fld (Sil.Hpointsto(_e2, fld_missing, texp1)) + | None -> ()); + (match fld_frame with + | Some fld_frame -> + ProverState.add_frame_fld (Sil.Hpointsto(e1, fld_frame, texp1)) + | None -> ()); + (match typing_missing with + | Some t_missing -> + ProverState.add_missing_typ (_e2, t_missing) + | None -> ()); + (match typing_frame with + | Some t_frame -> + ProverState.add_frame_typ (e1, t_frame) + | None -> ()) + end; + let prop1' = Prop.prop_iter_remove_curr_then_to_prop iter1' + in (subs', prop1') + with + | IMPL_EXC (s, _, body) when calc_missing -> + raise (MISSING_EXC s)) + | Sil.Hlseg (Sil.Lseg_NE, para1, e1, f1, elist1), _ -> (** Unroll lseg *) + let n' = Sil.Var (Ident.create_fresh Ident.kprimed) in + let (_, para_inst1) = Sil.hpara_instantiate para1 e1 n' elist1 in + let hpred_list1 = para_inst1@[Prop.mk_lseg Sil.Lseg_PE para1 n' f1 elist1] in + let iter1'' = Prop.prop_iter_update_current_by_list iter1' hpred_list1 in + L.d_increase_indent 1; + let res = + decrease_indent_when_exception + (fun () -> hpred_imply tenv calc_index_frame calc_missing subs (Prop.prop_iter_to_prop iter1'') sigma2 hpred2) in + L.d_decrease_indent 1; + res + | Sil.Hdllseg (Sil.Lseg_NE, para1, iF1, oB1, oF1, iB1, elist1), _ + when Sil.exp_equal (Sil.exp_sub (fst subs) iF1) e2 -> (** Unroll dllseg forward *) + let n' = Sil.Var (Ident.create_fresh Ident.kprimed) in + let (_, para_inst1) = Sil.hpara_dll_instantiate para1 iF1 oB1 n' elist1 in + let hpred_list1 = para_inst1@[Prop.mk_dllseg Sil.Lseg_PE para1 n' iF1 oF1 iB1 elist1] in + let iter1'' = Prop.prop_iter_update_current_by_list iter1' hpred_list1 in + L.d_increase_indent 1; + let res = + decrease_indent_when_exception + (fun () -> hpred_imply tenv calc_index_frame calc_missing subs (Prop.prop_iter_to_prop iter1'') sigma2 hpred2) in + L.d_decrease_indent 1; + res + | Sil.Hdllseg (Sil.Lseg_NE, para1, iF1, oB1, oF1, iB1, elist1), _ + when Sil.exp_equal (Sil.exp_sub (fst subs) iB1) e2 -> (** Unroll dllseg backward *) + let n' = Sil.Var (Ident.create_fresh Ident.kprimed) in + let (_, para_inst1) = Sil.hpara_dll_instantiate para1 iB1 n' oF1 elist1 in + let hpred_list1 = para_inst1@[Prop.mk_dllseg Sil.Lseg_PE para1 iF1 oB1 iB1 n' elist1] in + let iter1'' = Prop.prop_iter_update_current_by_list iter1' hpred_list1 in + L.d_increase_indent 1; + let res = + decrease_indent_when_exception + (fun () -> hpred_imply tenv calc_index_frame calc_missing subs (Prop.prop_iter_to_prop iter1'') sigma2 hpred2) in + L.d_decrease_indent 1; + res + | _ -> assert false + ) + ) + ) + | Sil.Hlseg (k, para2, _e2, _f2, _elist2) -> (* for now ignore implications between PE and NE *) + let e2, f2 = Sil.exp_sub (snd subs) _e2, Sil.exp_sub (snd subs) _f2 in + let _ = match e2 with + | Sil.Lvar p -> () + | Sil.Var v -> if Ident.is_primed v then + (d_impl_err ("rhs |-> not implemented", subs, (EXC_FALSE_HPRED hpred2)); + raise (Exceptions.Abduction_case_not_implemented (try assert false with Assert_failure x -> x))) + | _ -> () + in + if Sil.exp_equal e2 f2 && k == Sil.Lseg_PE then (subs, prop1) + else + (match Prop.prop_iter_create prop1 with + | None -> raise (IMPL_EXC ("lhs is empty", subs, EXC_FALSE)) + | Some iter1 -> + (match Prop.prop_iter_find iter1 (filter_hpred (fst subs) (Sil.hpred_sub (snd subs) hpred2)) with + | None -> + let elist2 = list_map (fun e -> Sil.exp_sub (snd subs) e) _elist2 in + let _, para_inst2 = Sil.hpara_instantiate para2 e2 f2 elist2 in + L.d_increase_indent 1; + let res = + decrease_indent_when_exception + (fun () -> sigma_imply tenv calc_index_frame false subs prop1 para_inst2) in + (* calc_missing is false as we're checking an instantiation of the original list *) + L.d_decrease_indent 1; + res + | Some iter1' -> + let elist2 = list_map (fun e -> Sil.exp_sub (snd subs) e) _elist2 in + let subs' = exp_list_imply calc_missing subs (f2:: elist2) (f2:: elist2) in (* force instantiation of existentials *) + let prop1' = Prop.prop_iter_remove_curr_then_to_prop iter1' in + let hpred1 = match Prop.prop_iter_current iter1' with + | hpred1, b -> + if b then ProverState.add_missing_pi (Sil.Aneq(_e2, _f2)); (* for PE |- NE *) + hpred1 + in match hpred1 with + | Sil.Hlseg _ -> (subs', prop1') + | Sil.Hpointsto _ -> (* unroll rhs list and try again *) + let n' = Sil.Var (Ident.create_fresh Ident.kprimed) in + let (_, para_inst2) = Sil.hpara_instantiate para2 _e2 n' elist2 in + let hpred_list2 = para_inst2@[Prop.mk_lseg Sil.Lseg_PE para2 n' _f2 _elist2] in + L.d_increase_indent 1; + let res = + decrease_indent_when_exception + (fun () -> + try sigma_imply tenv calc_index_frame calc_missing subs prop1 hpred_list2 + with exn when exn_not_timeout exn -> + begin + (L.d_strln_color Red) "backtracking lseg: trying rhs of length exactly 1"; + let (_, para_inst3) = Sil.hpara_instantiate para2 _e2 _f2 elist2 in + sigma_imply tenv calc_index_frame calc_missing subs prop1 para_inst3 + end) in + L.d_decrease_indent 1; + res + | Sil.Hdllseg _ -> assert false + ) + ) + | Sil.Hdllseg (Sil.Lseg_PE, _, _, _, _, _, _) -> + (d_impl_err ("rhs dllsegPE not implemented", subs, (EXC_FALSE_HPRED hpred2)); + raise (Exceptions.Abduction_case_not_implemented (try assert false with Assert_failure x -> x))) + | Sil.Hdllseg (k, para2, iF2, oB2, oF2, iB2, elist2) -> (* for now ignore implications between PE and NE *) + let iF2, oF2 = Sil.exp_sub (snd subs) iF2, Sil.exp_sub (snd subs) oF2 in + let iB2, oB2 = Sil.exp_sub (snd subs) iB2, Sil.exp_sub (snd subs) oB2 in + let _ = match oF2 with + | Sil.Lvar p -> () + | Sil.Var v -> if Ident.is_primed v then + (d_impl_err ("rhs dllseg not implemented", subs, (EXC_FALSE_HPRED hpred2)); + raise (Exceptions.Abduction_case_not_implemented (try assert false with Assert_failure x -> x))) + | _ -> () + in + let _ = match oB2 with + | Sil.Lvar p -> () + | Sil.Var v -> if Ident.is_primed v then + (d_impl_err ("rhs dllseg not implemented", subs, (EXC_FALSE_HPRED hpred2)); + raise (Exceptions.Abduction_case_not_implemented (try assert false with Assert_failure x -> x))) + | _ -> () + in + (match Prop.prop_iter_create prop1 with + | None -> raise (IMPL_EXC ("lhs is empty", subs, EXC_FALSE)) + | Some iter1 -> + (match Prop.prop_iter_find iter1 (filter_hpred (fst subs) (Sil.hpred_sub (snd subs) hpred2)) with + | None -> + let elist2 = list_map (fun e -> Sil.exp_sub (snd subs) e) elist2 in + let _, para_inst2 = + if Sil.exp_equal iF2 iB2 then + Sil.hpara_dll_instantiate para2 iF2 oB2 oF2 elist2 + else assert false in (** Only base case of rhs list considered for now *) + L.d_increase_indent 1; + let res = + decrease_indent_when_exception + (fun () -> sigma_imply tenv calc_index_frame false subs prop1 para_inst2) in + (* calc_missing is false as we're checking an instantiation of the original list *) + L.d_decrease_indent 1; + res + | Some iter1' -> (** Only consider implications between identical listsegs for now *) + let elist2 = list_map (fun e -> Sil.exp_sub (snd subs) e) elist2 in + let subs' = exp_list_imply calc_missing subs (iF2:: oB2:: oF2:: iB2:: elist2) (iF2:: oB2:: oF2:: iB2:: elist2) in (* force instantiation of existentials *) + let prop1' = Prop.prop_iter_remove_curr_then_to_prop iter1' + in (subs', prop1') + ) + ) + +(** Check that [sigma1] implies [sigma2] and return two substitution +instantiations for the primed variables of [sigma1] and [sigma2] +and a frame. Raise IMPL_FALSE if the implication cannot be +proven. *) +and sigma_imply tenv calc_index_frame calc_missing subs prop1 sigma2 : (subst2 * Prop.normal Prop.t) = + let is_constant_string_class subs = function (* if the hpred represents a constant string, return the string *) + | Sil.Hpointsto (_e2, _, _) -> + let e2 = Sil.exp_sub (snd subs) _e2 in + (match e2 with + | Sil.Const (Sil.Cstr s) -> Some (s, true) + | Sil.Const (Sil.Cclass c) -> Some (Ident.name_to_string c, false) + | _ -> None) + | _ -> None in + let mk_constant_string_hpred s = (* create an hpred from a constant string *) + let size = Sil.exp_int (Sil.Int.of_int (1 + String.length s)) in + let root = Sil.Const (Sil.Cstr s) in + let sexp = + let index = Sil.exp_int (Sil.Int.of_int (String.length s)) in + match !Sil.curr_language with + | Sil.C_CPP -> + Sil.Earray (size, [(index, Sil.Eexp (Sil.exp_zero, Sil.inst_none))], Sil.inst_none) + | Sil.Java -> + let mk_fld_sexp s = + let fld = Ident.create_fieldname (Mangled.from_string s) 0 in + let se = Sil.Eexp (Sil.Var (Ident.create_fresh Ident.kprimed), Sil.Inone) in + (fld, se) in + let fields = ["java.lang.String.count"; "java.lang.String.hash"; "java.lang.String.offset"; "java.lang.String.value"] in + Sil.Estruct (list_map mk_fld_sexp fields, Sil.inst_none) in + let const_string_texp = + match !Sil.curr_language with + | Sil.C_CPP -> Sil.Sizeof (Sil.Tarray (Sil.Tint Sil.IChar, size), Sil.Subtype.exact) + | Sil.Java -> + let object_type = Sil.TN_csu (Sil.Class, Mangled.from_string "java.lang.String") in + let typ = match Sil.tenv_lookup tenv object_type with + | Some typ -> typ + | None -> assert false in + Sil.Sizeof (typ, Sil.Subtype.exact) in + Sil.Hpointsto (root, sexp, const_string_texp) in + let mk_constant_class_hpred s = (* creat an hpred from a constant class *) + let root = Sil.Const (Sil.Cclass (Ident.string_to_name s)) in + let sexp = (* TODO: add appropriate fields *) + Sil.Estruct ([(Ident.create_fieldname (Mangled.from_string "java.lang.Class.name") 0, Sil.Eexp ((Sil.Const (Sil.Cstr s), Sil.Inone)))], Sil.inst_none) in + let class_texp = + let class_type = Sil.TN_csu (Sil.Class, Mangled.from_string "java.lang.Class") in + let typ = match Sil.tenv_lookup tenv class_type with + | Some typ -> typ + | None -> assert false in + Sil.Sizeof (typ, Sil.Subtype.exact) in + Sil.Hpointsto (root, sexp, class_texp) in + try + (match move_primed_lhs_from_front subs sigma2 with + | [] -> + L.d_strln "Final Implication"; + d_impl subs (prop1, Prop.prop_emp); + (subs, prop1) + | hpred2 :: sigma2' -> + L.d_strln "Current Implication"; + d_impl subs (prop1, Prop.normalize (Prop.from_sigma (hpred2 :: sigma2'))); + L.d_ln (); + L.d_ln (); + let normal_case hpred2' = + let (subs', prop1') = + try + L.d_increase_indent 1; + let res = + decrease_indent_when_exception + (fun () -> hpred_imply tenv calc_index_frame calc_missing subs prop1 sigma2 hpred2') in + L.d_decrease_indent 1; + res + with IMPL_EXC _ when calc_missing -> + begin + match is_constant_string_class subs hpred2' with + | Some (s, is_string) -> (* allocate constant string hpred1', do implication, then add hpred1' as missing *) + let hpred1' = if is_string then mk_constant_string_hpred s else mk_constant_class_hpred s in + let prop1' = Prop.normalize (Prop.replace_sigma (hpred1' :: Prop.get_sigma prop1) prop1) in + let subs', frame_prop = hpred_imply tenv calc_index_frame calc_missing subs prop1' sigma2 hpred2' in + (* ProverState.add_missing_sigma [hpred1']; *) + subs', frame_prop + | None -> + let subs' = match hpred2' with + | Sil.Hpointsto (e2, se2, te2) -> + let typ2 = Sil.texp_to_typ (Some Sil.Tvoid) te2 in + sexp_imply_nolhs e2 calc_missing subs se2 typ2 + | _ -> subs in + ProverState.add_missing_sigma [hpred2']; + subs', prop1 + end in + L.d_increase_indent 1; + let res = + decrease_indent_when_exception + (fun () -> sigma_imply tenv calc_index_frame calc_missing subs' prop1' sigma2') in + L.d_decrease_indent 1; + res in + (match hpred2 with + | Sil.Hpointsto(_e2, se2, t) -> + let changed, calc_index_frame', hpred2' = expand_hpred_pointer calc_index_frame (Sil.Hpointsto (Prop.exp_normalize_noabs (snd subs) _e2, se2, t)) in + if changed + then sigma_imply tenv calc_index_frame' calc_missing subs prop1 (hpred2' :: sigma2') (* calc_index_frame=true *) + else normal_case hpred2' + | _ -> normal_case hpred2) + ) + with IMPL_EXC (s, _, _) when calc_missing -> + L.d_strln ("Adding rhs as missing: " ^ s); + ProverState.add_missing_sigma sigma2; + subs, prop1 + +let prepare_prop_for_implication (sub1, sub2) pi1 sigma1 = + let pi1' = (Prop.pi_sub sub2 (ProverState.get_missing_pi ())) @ pi1 in + let sigma1' = (Prop.sigma_sub sub2 (ProverState.get_missing_sigma ())) @ sigma1 in + let ep = Prop.replace_sub sub2 (Prop.replace_sigma sigma1' (Prop.from_pi pi1')) in + Prop.normalize ep + +let imply_pi calc_missing (sub1, sub2) prop pi2 = + let do_atom a = + let a' = Sil.atom_sub sub2 a in + try + if not (check_atom prop a') + then raise (IMPL_EXC ("rhs atom missing in lhs", (sub1, sub2), (EXC_FALSE_ATOM a'))) + with + | IMPL_EXC _ when calc_missing -> + L.d_str "imply_pi: adding missing atom "; Sil.d_atom a; L.d_ln (); + ProverState.add_missing_pi a in + list_iter do_atom pi2 + +let imply_atom calc_missing (sub1, sub2) prop a = + imply_pi calc_missing (sub1, sub2) prop [a] + +(** Check pure implications before looking at the spatial part. Add +necessary instantiations for equalities and check that instantiations +are possible for disequalities. *) +let rec pre_check_pure_implication calc_missing subs pi1 pi2 = + match pi2 with + | [] -> subs + | (Sil.Aeq (e2_in, f2_in) as a) :: pi2' when not (Prop.atom_is_inequality a) -> + let e2, f2 = Sil.exp_sub (snd subs) e2_in, Sil.exp_sub (snd subs) f2_in in + if Sil.exp_equal e2 f2 then pre_check_pure_implication calc_missing subs pi1 pi2' + else + (match e2, f2 with + | Sil.Var v2, f2 + when Ident.is_primed v2 (* && not (Sil.mem_sub v2 (snd subs)) *) -> + (* The commented-out condition should always hold. *) + let sub2' = extend_sub (snd subs) v2 f2 in + pre_check_pure_implication calc_missing (fst subs, sub2') pi1 pi2' + | e2, Sil.Var v2 + when Ident.is_primed v2 (* && not (Sil.mem_sub v2 (snd subs)) *) -> + (* The commented-out condition should always hold. *) + let sub2' = extend_sub (snd subs) v2 e2 in + pre_check_pure_implication calc_missing (fst subs, sub2') pi1 pi2' + | e2, f2 -> + let pi1' = Prop.pi_sub (fst subs) pi1 in + let prop_for_impl = prepare_prop_for_implication subs pi1' [] in + imply_atom calc_missing subs prop_for_impl (Sil.Aeq (e2_in, f2_in)); + pre_check_pure_implication calc_missing subs pi1 pi2' + ) + | Sil.Aeq (e1, e2) :: pi2' -> (* must be an inequality *) + pre_check_pure_implication calc_missing subs pi1 pi2' + | Sil.Aneq (Sil.Var v, f2):: pi2' -> + if not (Ident.is_primed v || calc_missing) + then raise (IMPL_EXC("ineq e2=f2 in rhs with e2 not primed var", (Sil.sub_empty, Sil.sub_empty), EXC_FALSE)) + else pre_check_pure_implication calc_missing subs pi1 pi2' + | Sil.Aneq (e1, e2):: pi2' -> + if calc_missing then pre_check_pure_implication calc_missing subs pi1 pi2' + else raise (IMPL_EXC ("ineq e2=f2 in rhs with e2 not primed var", (Sil.sub_empty, Sil.sub_empty), EXC_FALSE)) + +(** Perform the array bound checks delayed (to instantiate variables) by the prover. +If there is a provable violation of the array bounds, set the prover status to Bounds_check +and make the proof fail. *) +let check_array_bounds (sub1, sub2) prop = + let check_failed atom = + ProverState.checks := Bounds_check :: !ProverState.checks; + L.d_str_color Red "bounds_check failed: provable atom: "; Sil.d_atom atom; L.d_ln(); + if (not !Config.Experiment.bound_error_allowed_in_procedure_call) then raise (IMPL_EXC ("bounds check", (sub1, sub2), EXC_FALSE)) in + let fail_if_le e' e'' = + let lt_ineq = Prop.mk_inequality (Sil.BinOp(Sil.Le, e', e'')) in + if check_atom prop lt_ineq then check_failed lt_ineq in + let check_bound = function + | ProverState.BCsize_imply (_size1, _size2, _indices2) -> + let size1 = Sil.exp_sub sub1 _size1 in + let size2 = Sil.exp_sub sub2 _size2 in + (* L.d_strln_color Orange "check_bound "; Sil.d_exp size1; L.d_str " "; Sil.d_exp size2; L.d_ln(); *) + let indices_to_check = match size2 with + | _ -> [Sil.BinOp(Sil.PlusA, size2, Sil.exp_minus_one)] (* only check size *) in + list_iter (fail_if_le size1) indices_to_check + | ProverState.BCfrom_pre _atom -> + let atom_neg = Prop.atom_negate (Sil.atom_sub sub2 _atom) in + (* L.d_strln_color Orange "BCFrom_pre"; Sil.d_atom atom_neg; L.d_ln (); *) + if check_atom prop atom_neg then check_failed atom_neg in + list_iter check_bound (ProverState.get_bounds_checks ()) + +(** [check_implication_base] returns true if [prop1|-prop2], +ignoring the footprint part of the props *) +let check_implication_base pname tenv check_frame_empty calc_missing prop1 prop2 = + try + ProverState.reset prop1 prop2; + let filter (id, e) = + Ident.is_normal id && Sil.fav_for_all (Sil.exp_fav e) Ident.is_normal in + let sub1_base = + Sil.sub_filter_pair filter (Prop.get_sub prop1) in + let pi1, pi2 = Prop.get_pure prop1, Prop.get_pure prop2 in + let sigma1, sigma2 = Prop.get_sigma prop1, Prop.get_sigma prop2 in + let subs = pre_check_pure_implication calc_missing (Prop.get_sub prop1, sub1_base) pi1 pi2 in + let pi2_bcheck, pi2_nobcheck = (* find bounds checks implicit in pi2 *) + list_partition ProverState.atom_is_array_bounds_check pi2 in + list_iter (fun a -> ProverState.add_bounds_check (ProverState.BCfrom_pre a)) pi2_bcheck; + L.d_strln "pre_check_pure_implication"; + L.d_strln "pi1:"; + L.d_increase_indent 1; Prop.d_pi pi1; L.d_decrease_indent 1; L.d_ln (); + L.d_strln "pi2:"; + L.d_increase_indent 1; Prop.d_pi pi2; L.d_decrease_indent 1; L.d_ln (); + if pi2_bcheck != [] + then (L.d_str "pi2 bounds checks: "; Prop.d_pi pi2_bcheck; L.d_ln ()); + L.d_strln "returns"; + L.d_strln "sub1: "; + L.d_increase_indent 1; Prop.d_sub (fst subs); L.d_decrease_indent 1; L.d_ln (); + L.d_strln "sub2: "; + L.d_increase_indent 1; Prop.d_sub (snd subs); L.d_decrease_indent 1; L.d_ln (); + let (sub1, sub2), frame_prop = sigma_imply tenv false calc_missing subs prop1 sigma2 in + let pi1' = Prop.pi_sub sub1 pi1 in + let sigma1' = Prop.sigma_sub sub1 sigma1 in + L.d_ln (); + let prop_for_impl = prepare_prop_for_implication (sub1, sub2) pi1' sigma1' in + imply_pi calc_missing (sub1, sub2) prop_for_impl pi2_nobcheck; (* only deal with pi2 without bound checks *) + check_array_bounds (sub1, sub2) prop_for_impl; (* handle implicit bound checks, plus those from array_size_imply *) + L.d_strln "Result of Abduction"; + L.d_increase_indent 1; d_impl (sub1, sub2) (prop1, prop2); L.d_decrease_indent 1; L.d_ln (); + L.d_strln"returning TRUE"; + let frame = Prop.get_sigma frame_prop in + if check_frame_empty && frame != [] then raise (IMPL_EXC("frame not empty", subs, EXC_FALSE)); + Some ((sub1, sub2), frame) + with + | IMPL_EXC (s, subs, body) -> + d_impl_err (s, subs, body); + None + | MISSING_EXC s -> + L.d_strln ("WARNING: footprint failed to find MISSING because: " ^ s); + None + | (Exceptions.Abduction_case_not_implemented mloc as exn) -> + Reporting.log_error pname exn; + None + +type implication_result = + | ImplOK of (check list * Sil.subst * Sil.subst * Sil.hpred list * (Sil.atom list) * (Sil.hpred list) * (Sil.hpred list) * (Sil.hpred list) * ((Sil.exp * Sil.exp) list) * ((Sil.exp * Sil.exp) list)) + | ImplFail of check list + +(** [check_implication_for_footprint p1 p2] returns +[Some(sub, frame, missing)] if [sub(p1 * missing) |- sub(p2 * frame)] +where [sub] is a substitution which instantiates the +primed vars of [p1] and [p2], which are assumed to be disjoint. *) +let check_implication_for_footprint pname tenv p1 (p2: Prop.exposed Prop.t) = + let check_frame_empty = false in + let calc_missing = true in + match check_implication_base pname tenv check_frame_empty calc_missing p1 p2 with + | Some ((sub1, sub2), frame) -> + ImplOK (!ProverState.checks, sub1, sub2, frame, ProverState.get_missing_pi (), ProverState.get_missing_sigma (), ProverState.get_frame_fld (), ProverState.get_missing_fld (), ProverState.get_frame_typ (), ProverState.get_missing_typ ()) + | None -> ImplFail !ProverState.checks + +(** [check_implication p1 p2] returns true if [p1|-p2] *) +let check_implication pname tenv p1 p2 = + let check p1 p2 = + let check_frame_empty = true in + let calc_missing = false in + match check_implication_base pname tenv check_frame_empty calc_missing p1 p2 with + | Some _ -> true + | None -> false in + check p1 p2 && + (if !Config.footprint then check (Prop.normalize (Prop.extract_footprint p1)) (Prop.extract_footprint p2) else true) + +(** {2 Cover: miminum set of pi's whose disjunction is equivalent to true} *) + +(** check if the pi's in [cases] cover true *) +let is_cover cases = + let cnt = ref 0 in (* counter for timeout checks, as this function can take exponential time *) + let check () = + incr cnt; + if (!cnt mod 100 = 0) then SymOp.check_wallclock_alarm () in + let rec _is_cover acc_pi cases = + check (); + match cases with + | [] -> check_inconsistency_pi acc_pi + | (pi, _):: cases' -> + list_for_all (fun a -> _is_cover ((Prop.atom_negate a) :: acc_pi) cases') pi in + _is_cover [] cases + +exception NO_COVER + +(** Find miminum set of pi's in [cases] whose disjunction covers true *) +let find_minimum_pure_cover cases = + let cases = + let compare (pi1, _) (pi2, _) = int_compare (list_length pi1) (list_length pi2) + in list_sort compare cases in + let rec grow seen todo = match todo with + | [] -> raise NO_COVER + | (pi, x):: todo' -> + if is_cover ((pi, x):: seen) then (pi, x):: seen + else grow ((pi, x):: seen) todo' in + let rec _shrink seen todo = match todo with + | [] -> seen + | (pi, x):: todo' -> + if is_cover (seen @ todo') then _shrink seen todo' + else _shrink ((pi, x):: seen) todo' in + let shrink cases = + if list_length cases > 2 then _shrink [] cases + else cases + in try Some (shrink (grow [] cases)) + with NO_COVER -> None + + diff --git a/infer/src/backend/prover.mli b/infer/src/backend/prover.mli new file mode 100644 index 000000000..31fe68a36 --- /dev/null +++ b/infer/src/backend/prover.mli @@ -0,0 +1,104 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Functions for Theorem Proving *) + +open Ident +open Sil + +(** {2 Ordinary Theorem Proving} *) + +(** Check [ |- e=0]. Result [false] means "don't know". *) +val check_zero : exp -> bool + +(** Check [prop |- exp1=exp2]. Result [false] means "don't know". *) +val check_equal : Prop.normal Prop.t -> exp -> exp -> bool + +(** Check whether [prop |- exp1!=exp2]. Result [false] means "don't know". *) +val check_disequal : Prop.normal Prop.t -> exp -> exp -> bool + +val check_le : Prop.normal Prop.t -> exp -> exp -> bool + +(** Return true if the two types have sizes which can be compared *) +val type_size_comparable : Sil.typ -> Sil.typ -> bool + +(** Check <= on the size of comparable types *) +val check_type_size_leq : Sil.typ -> Sil.typ -> bool + +(** Check < on the size of comparable types *) +val check_type_size_lt : Sil.typ -> Sil.typ -> bool + +(** Check whether [prop |- a]. Result [false] means "don't know". *) +val check_atom : Prop.normal Prop.t -> atom -> bool + +(** Inconsistency checking ignoring footprint. *) +val check_inconsistency_base : Prop.normal Prop.t -> bool + +(** Inconsistency checking. *) +val check_inconsistency : Prop.normal Prop.t -> bool + +(** Check whether [prop |- allocated(exp)]. *) +val check_allocatedness : Prop.normal Prop.t -> exp -> bool + +(** [is_root prop base_exp exp] checks whether [base_exp = +exp.offlist] for some list of offsets [offlist]. If so, it returns +[Some(offlist)]. Otherwise, it returns [None]. Assumes that +[base_exp] points to the beginning of a structure, not the middle. *) +val is_root : Prop.normal Prop.t -> exp -> exp -> offset list option + +(** [expand_hpred_pointer calc_index_frame hpred] expands [hpred] if it is a |-> whose lhs is a Lfield or Lindex or ptr+off. +Return [(changed, calc_index_frame', hpred')] where [changed] indicates whether the predicate has changed. *) +val expand_hpred_pointer : bool -> Sil.hpred -> bool * bool * Sil.hpred + +(** Get upper and lower bounds of an expression, if any *) +val get_bounds : Prop.normal Prop.t -> Sil.exp -> Sil.Int.t option * Sil.Int.t option + +(** {2 Abduction prover} *) + +(** [check_implication p1 p2] returns true if [p1|-p2] *) +val check_implication : Procname.t -> Sil.tenv -> Prop.normal Prop.t -> Prop.exposed Prop.t -> bool + +type check = + | Bounds_check + | Class_cast_check of Sil.exp * Sil.exp * Sil.exp + +val d_typings : (Sil.exp * Sil.exp) list -> unit + +type implication_result = + | ImplOK of (check list * Sil.subst * Sil.subst * Sil.hpred list * (Sil.atom list) * (Sil.hpred list) * (Sil.hpred list) * (Sil.hpred list) * ((Sil.exp * Sil.exp) list) * ((Sil.exp * Sil.exp) list)) + | ImplFail of check list + +(** [check_implication_for_footprint p1 p2] returns +[Some(sub, frame, missing)] if [sub(p1 * missing) |- sub(p2 * +frame)] where [sub] is a substitution which instantiates the +primed vars of [p1] and [p2], which are assumed to be disjoint. *) +val check_implication_for_footprint : +Procname.t -> Sil.tenv -> Prop.normal Prop.t -> Prop.exposed Prop.t -> implication_result + +(** {2 Cover: miminum set of pi's whose disjunction is equivalent to true} *) + +(** Find miminum set of pi's in [cases] whose disjunction covers true *) +val find_minimum_pure_cover : (Sil.atom list * 'a) list -> (Sil.atom list * 'a) list option + +(** {2 Compute various lower or upper bounds} *) + +(** Computer an upper bound of an expression *) +val compute_upper_bound_of_exp : Prop.normal Prop.t -> Sil.exp -> Sil.Int.t option + + +(** {2 Subtype checking} *) + +(** check_subtype t1 t2 checks whether t1 is a subtype of t2, given the type environment tenv. *) +val check_subtype : Sil.tenv -> Sil.typ -> Sil.typ -> bool + +(** subtype_case_analysis tenv tecp1 texp2 performs case analysis on [texp1 <: texp2], +and returns the updated types in the true and false case, if they are possible *) +val subtype_case_analysis : Sil.tenv -> Sil.exp -> Sil.exp -> Sil.exp option * Sil.exp option + + + + + diff --git a/infer/src/backend/rearrange.ml b/infer/src/backend/rearrange.ml new file mode 100644 index 000000000..0fd2fe81c --- /dev/null +++ b/infer/src/backend/rearrange.ml @@ -0,0 +1,1102 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Re-arrangement and extension of structures with fresh variables *) + +module L = Logging +module F = Format +open Utils + +let (++) = Sil.Int.add + +let list_product l1 l2 = + let l1' = list_rev l1 in + let l2' = list_rev l2 in + list_fold_left + (fun acc x -> list_fold_left (fun acc' y -> (x, y):: acc') acc l2') + [] l1' + +let rec list_rev_and_concat l1 l2 = + match l1 with + | [] -> l2 + | x1:: l1' -> list_rev_and_concat l1' (x1:: l2) + +let pp_off fmt off = + list_iter (fun n -> match n with + | Sil.Off_fld (f, t) -> F.fprintf fmt "%a " Ident.pp_fieldname f + | Sil.Off_index e -> F.fprintf fmt "%a " (Sil.pp_exp pe_text) e) off + +(** Check whether the index is out of bounds. +If the size is - 1, no check is performed. +If the index is provably out of bound, a bound error is given. +If the size is a constant and the index is not provably in bound, a warning is given. +*) +let check_bad_index pname tenv p size index loc = + let size_is_constant = match size with + | Sil.Const _ -> true + | _ -> false in + let index_provably_out_of_bound () = + let index_too_large = Prop.mk_inequality (Sil.BinOp(Sil.Le, size, index)) in + let index_negative = Prop.mk_inequality (Sil.BinOp(Sil.Le, index, Sil.exp_minus_one)) in + (Prover.check_atom p index_too_large) || (Prover.check_atom p index_negative) in + let index_provably_in_bound () = + let size_minus_one = Sil.BinOp(Sil.PlusA, size, Sil.exp_minus_one) in + let index_not_too_large = Prop.mk_inequality (Sil.BinOp(Sil.Le, index, size_minus_one)) in + let index_nonnegative = Prop.mk_inequality (Sil.BinOp(Sil.Le, Sil.exp_zero, index)) in + Prover.check_zero index || (* index 0 always in bound, even when we know nothing about size *) + ((Prover.check_atom p index_not_too_large) && (Prover.check_atom p index_nonnegative)) in + let index_has_bounds () = + match Prover.get_bounds p index with + | Some _, Some _ -> true + | _ -> false in + let get_const_opt = function + | Sil.Const (Sil.Cint n) -> Some n + | _ -> None in + if not (index_provably_in_bound ()) then + begin + let size_const_opt = get_const_opt size in + let index_const_opt = get_const_opt index in + if index_provably_out_of_bound () then + let deref_str = Localise.deref_str_array_bound size_const_opt index_const_opt in + let exn = Exceptions.Array_out_of_bounds_l1 (Errdesc.explain_array_access deref_str p loc, try assert false with Assert_failure x -> x) in + let pre_opt = State.get_normalized_pre (Abs.abstract_no_symop pname) in + Reporting.log_warning pname ~pre: pre_opt exn + else if size_is_constant then + let deref_str = Localise.deref_str_array_bound size_const_opt index_const_opt in + let desc = Errdesc.explain_array_access deref_str p loc in + let exn = if index_has_bounds () + then Exceptions.Array_out_of_bounds_l2 (desc, try assert false with Assert_failure x -> x) + else Exceptions.Array_out_of_bounds_l3 (desc, try assert false with Assert_failure x -> x) in + let pre_opt = State.get_normalized_pre (Abs.abstract_no_symop pname) in + Reporting.log_warning pname ~pre: pre_opt exn + end + +(** Perform bounds checking *) +let bounds_check pname tenv prop size e = + if !Config.trace_rearrange then + begin + L.d_str "Bounds check index:"; Sil.d_exp e; + L.d_str " size: "; Sil.d_exp size; + L.d_ln() + end; + check_bad_index pname tenv prop size e + +let rec create_struct_values pname tenv orig_prop footprint_part kind max_stamp t + (off: Sil.offset list) inst : Sil.atom list * Sil.strexp * Sil.typ = + if !Config.trace_rearrange then + begin + L.d_increase_indent 1; + L.d_strln "entering create_struct_values"; + L.d_str "typ: "; Sil.d_typ_full t; L.d_ln (); + L.d_str "off: "; Sil.d_offset_list off; L.d_ln (); L.d_ln () + end; + let new_id () = + incr max_stamp; + Ident.create kind !max_stamp in + let res = + match t, off with + | Sil.Tstruct (ftal, sftal, _, _, _, _, _),[] -> + ([], Sil.Estruct ([], inst), t) + | Sil.Tstruct (ftal, sftal, csu, nameo, supers, def_mthds, iann), (Sil.Off_fld (f, _)):: off' -> + let _, t', _ = + try list_find (fun (f', _, _) -> Ident.fieldname_equal f f') ftal + with Not_found -> raise (Exceptions.Bad_footprint (try assert false with Assert_failure x -> x)) in + let atoms', se', res_t' = + create_struct_values + pname tenv orig_prop footprint_part kind max_stamp t' off' inst in + let se = Sil.Estruct ([(f, se')], inst) in + let replace_typ_of_f (f', t', a') = if Ident.fieldname_equal f f' then (f, res_t', a') else (f', t', a') in + let ftal' = list_sort Sil.fld_typ_ann_compare (list_map replace_typ_of_f ftal) in + (atoms', se, Sil.Tstruct (ftal', sftal, csu, nameo, supers, def_mthds, iann)) + | Sil.Tstruct _, (Sil.Off_index e):: off' -> + let atoms', se', res_t' = + create_struct_values + pname tenv orig_prop footprint_part kind max_stamp t off' inst in + let e' = Sil.array_clean_new_index footprint_part e in + let size = Sil.exp_get_undefined false in + let se = Sil.Earray (size, [(e', se')], inst) in + let res_t = Sil.Tarray (res_t', size) in + (Sil.Aeq(e, e'):: atoms', se, res_t) + | Sil.Tarray(_, size),[] -> + ([], Sil.Earray(size, [], inst), t) + | Sil.Tarray(t', size'), (Sil.Off_index e) :: off' -> + bounds_check pname tenv orig_prop size' e (State.get_loc ()); + + let atoms', se', res_t' = + create_struct_values + pname tenv orig_prop footprint_part kind max_stamp t' off' inst in + let e' = Sil.array_clean_new_index footprint_part e in + let se = Sil.Earray(size', [(e', se')], inst) in + let res_t = Sil.Tarray(res_t', size') in + (Sil.Aeq(e, e'):: atoms', se, res_t) + | Sil.Tarray _, (Sil.Off_fld _) :: _ -> + assert false + + | Sil.Tint _, [] | Sil.Tfloat _, [] | Sil.Tvoid, [] | Sil.Tfun _, [] | Sil.Tptr _, [] -> + let id = new_id () in + ([], Sil.Eexp (Sil.Var id, inst), t) + | Sil.Tint _, [Sil.Off_index e] | Sil.Tfloat _, [Sil.Off_index e] + | Sil.Tvoid, [Sil.Off_index e] + | Sil.Tfun _, [Sil.Off_index e] | Sil.Tptr _, [Sil.Off_index e] -> + (* In this case, we lift t to the t array. *) + let t' = match t with + | Sil.Tptr(t', _) -> t' + | _ -> t in + let size = Sil.Var (new_id ()) in + + let atoms', se', res_t' = + create_struct_values + pname tenv orig_prop footprint_part kind max_stamp t' [] inst in + let e' = Sil.array_clean_new_index footprint_part e in + let se = Sil.Earray(size, [(e', se')], inst) in + let res_t = Sil.Tarray(res_t', size) in + (Sil.Aeq(e, e'):: atoms', se, res_t) + | Sil.Tint _, _ | Sil.Tfloat _, _ | Sil.Tvoid, _ | Sil.Tfun _, _ | Sil.Tptr _, _ -> + L.d_str "create_struct_values type:"; Sil.d_typ_full t; L.d_str " off: "; Sil.d_offset_list off; L.d_ln(); + raise (Exceptions.Bad_footprint (try assert false with Assert_failure x -> x)) + + | Sil.Tvar _, _ | Sil.Tenum _, _ -> + L.d_str "create_struct_values type:"; Sil.d_typ_full t; L.d_str " off: "; Sil.d_offset_list off; L.d_ln(); + assert false in + + if !Config.trace_rearrange then + begin + let _, se, _ = res in + L.d_strln "exiting create_struct_values, returning"; + Sil.d_sexp se; + L.d_decrease_indent 1; + L.d_ln (); L.d_ln () + end; + res + +(** Extend the strexp by populating the path indicated by [off]. +This means that it will add missing flds and do the case - analysis +for array accesses. This does not catch the array - bounds errors. +If we want to implement the checks for array bounds errors, +we need to change this function. *) +let rec _strexp_extend_values + pname tenv orig_prop footprint_part kind max_stamp + se typ (off : Sil.offset list) inst = + match off, se, typ with + | [], Sil.Eexp _, _ + | [], Sil.Estruct _, _ -> + [([], se, typ)] + | [], Sil.Earray _, _ -> + let off_new = Sil.Off_index(Sil.exp_zero):: off in + _strexp_extend_values + pname tenv orig_prop footprint_part kind max_stamp se typ off_new inst + | (Sil.Off_fld (f, _)):: _, Sil.Earray _, Sil.Tarray _ -> + let off_new = Sil.Off_index(Sil.exp_zero):: off in + _strexp_extend_values + pname tenv orig_prop footprint_part kind max_stamp se typ off_new inst + | (Sil.Off_fld (f, _)):: off', Sil.Estruct (fsel, inst'), Sil.Tstruct (ftal, sftal, csu, nameo, supers, def_mthds, iann) -> + let replace_fv new_v fv = if Ident.fieldname_equal (fst fv) f then (f, new_v) else fv in + let typ' = + try (fun (x, y, z) -> y) (list_find (fun (f', t', a') -> Ident.fieldname_equal f f') ftal) + with Not_found -> raise (Exceptions.Missing_fld (f, try assert false with Assert_failure x -> x)) in + begin + try + let _, se' = list_find (fun (f', _) -> Ident.fieldname_equal f f') fsel in + let atoms_se_typ_list' = + _strexp_extend_values + pname tenv orig_prop footprint_part kind max_stamp se' typ' off' inst in + let replace acc (res_atoms', res_se', res_typ') = + let replace_fse = replace_fv res_se' in + let res_fsel' = list_sort Sil.fld_strexp_compare (list_map replace_fse fsel) in + let replace_fta (f, t, a) = let f', t' = replace_fv res_typ' (f, t) in (f', t', a) in + let res_ftl' = list_sort Sil.fld_typ_ann_compare (list_map replace_fta ftal) in + (res_atoms', Sil.Estruct (res_fsel', inst'), Sil.Tstruct (res_ftl', sftal, csu, nameo, supers, def_mthds, iann)) :: acc in + list_fold_left replace [] atoms_se_typ_list' + with Not_found -> + let atoms', se', res_typ' = + create_struct_values + pname tenv orig_prop footprint_part kind max_stamp typ' off' inst in + let res_fsel' = list_sort Sil.fld_strexp_compare ((f, se'):: fsel) in + let replace_fta (f', t', a') = if Ident.fieldname_equal f' f then (f, res_typ', a') else (f', t', a') in + let res_ftl' = list_sort Sil.fld_typ_ann_compare (list_map replace_fta ftal) in + [(atoms', Sil.Estruct (res_fsel', inst'), Sil.Tstruct (res_ftl', sftal, csu, nameo, supers, def_mthds, iann))] + end + | (Sil.Off_fld (f, _)):: off', _, _ -> + raise (Exceptions.Bad_footprint (try assert false with Assert_failure x -> x)) + + | (Sil.Off_index _):: _, Sil.Eexp _, Sil.Tint _ + | (Sil.Off_index _):: _, Sil.Eexp _, Sil.Tfloat _ + | (Sil.Off_index _):: _, Sil.Eexp _, Sil.Tvoid + | (Sil.Off_index _):: _, Sil.Eexp _, Sil.Tfun _ + | (Sil.Off_index _):: _, Sil.Eexp _, Sil.Tptr _ + | (Sil.Off_index _):: _, Sil.Estruct _, Sil.Tstruct _ -> + (* L.d_strln_color Orange "turn into an array"; *) + let size = match se with + | Sil.Eexp (_, Sil.Ialloc) -> Sil.exp_one (* if allocated explicitly, we know size is 1 *) + | _ -> + if !Config.type_size then Sil.exp_one (* Sil.Sizeof (typ, Sil.Subtype.exact) *) + else Sil.exp_get_undefined false in + + let se_new = Sil.Earray(size, [(Sil.exp_zero, se)], inst) in + let typ_new = Sil.Tarray(typ, size) in + _strexp_extend_values + pname tenv orig_prop footprint_part kind max_stamp se_new typ_new off inst + | (Sil.Off_index e):: off', Sil.Earray(size, esel, inst_arr), Sil.Tarray(typ', size_for_typ') -> + bounds_check pname tenv orig_prop size e (State.get_loc ()); + begin + try + let _, se' = list_find (fun (e', _) -> Sil.exp_equal e e') esel in + let atoms_se_typ_list' = + _strexp_extend_values + pname tenv orig_prop footprint_part kind max_stamp se' typ' off' inst in + let replace acc (res_atoms', res_se', res_typ') = + let replace_ise ise = if Sil.exp_equal e (fst ise) then (e, res_se') else ise in + let res_esel' = list_map replace_ise esel in + if (Sil.typ_equal res_typ' typ') || (list_length res_esel' = 1) + then (res_atoms', Sil.Earray(size, res_esel', inst_arr), Sil.Tarray(res_typ', size_for_typ')) :: acc + else raise (Exceptions.Bad_footprint (try assert false with Assert_failure x -> x)) in + list_fold_left replace [] atoms_se_typ_list' + with Not_found -> + array_case_analysis_index pname tenv orig_prop + footprint_part kind max_stamp + size esel + size_for_typ' typ' + e off' inst_arr inst + end + | _, _, _ -> + raise (Exceptions.Bad_footprint (try assert false with Assert_failure x -> x)) + +and array_case_analysis_index pname tenv orig_prop + footprint_part kind max_stamp + array_size array_cont + typ_array_size typ_cont + index off inst_arr inst += + let check_sound t' = + if not (Sil.typ_equal typ_cont t' || array_cont == []) + then raise (Exceptions.Bad_footprint (try assert false with Assert_failure x -> x)) in + let index_in_array = + list_exists (fun (i, _) -> Prover.check_equal Prop.prop_emp index i) array_cont in + let array_is_full = + match array_size with + | Sil.Const (Sil.Cint n') -> Sil.Int.geq (Sil.Int.of_int (list_length array_cont)) n' + | _ -> false in + + if index_in_array then + let array_default = Sil.Earray(array_size, array_cont, inst_arr) in + let typ_default = Sil.Tarray(typ_cont, typ_array_size) in + [([], array_default, typ_default)] + else if !Config.footprint then begin + let atoms, elem_se, elem_typ = + create_struct_values + pname tenv orig_prop footprint_part kind max_stamp typ_cont off inst in + check_sound elem_typ; + let cont_new = list_sort Sil.exp_strexp_compare ((index, elem_se):: array_cont) in + let array_new = Sil.Earray(array_size, cont_new, inst_arr) in + let typ_new = Sil.Tarray(elem_typ, typ_array_size) in + [(atoms, array_new, typ_new)] + end + else begin + let res_new = + if array_is_full then [] + else begin + let atoms, elem_se, elem_typ = + create_struct_values + pname tenv orig_prop footprint_part kind max_stamp typ_cont off inst in + check_sound elem_typ; + let cont_new = list_sort Sil.exp_strexp_compare ((index, elem_se):: array_cont) in + let array_new = Sil.Earray(array_size, cont_new, inst_arr) in + let typ_new = Sil.Tarray(elem_typ, typ_array_size) in + [(atoms, array_new, typ_new)] + end in + let rec handle_case acc isel_seen_rev = function + | [] -> list_flatten (list_rev (res_new:: acc)) + | (i, se) as ise :: isel_unseen -> + let atoms_se_typ_list = + _strexp_extend_values + pname tenv orig_prop footprint_part kind max_stamp se typ_cont off inst in + let atoms_se_typ_list' = + list_fold_left (fun acc' (atoms', se', typ') -> + check_sound typ'; + let atoms_new = Sil.Aeq(index, i) :: atoms' in + let isel_new = list_rev_and_concat isel_seen_rev ((i, se'):: isel_unseen) in + let array_new = Sil.Earray(array_size, isel_new, inst_arr) in + let typ_new = Sil.Tarray(typ', typ_array_size) in + (atoms_new, array_new, typ_new):: acc' + ) [] atoms_se_typ_list in + let acc_new = atoms_se_typ_list' :: acc in + let isel_seen_rev_new = ise :: isel_seen_rev in + handle_case acc_new isel_seen_rev_new isel_unseen in + handle_case [] [] array_cont + end + +let exp_has_only_footprint_ids e = + let fav = Sil.exp_fav e in + Sil.fav_filter_ident fav (fun id -> not (Ident.is_footprint id)); + Sil.fav_is_empty fav + +let laundry_offset_for_footprint max_stamp offs_in = + let rec laundry offs_seen eqs offs = + match offs with + | [] -> + (list_rev offs_seen, list_rev eqs) + | (Sil.Off_fld _ as off):: offs' -> + let offs_seen' = off:: offs_seen in + laundry offs_seen' eqs offs' + | (Sil.Off_index(idx) as off):: offs' -> + if exp_has_only_footprint_ids idx then + let offs_seen' = off:: offs_seen in + laundry offs_seen' eqs offs' + else + let () = incr max_stamp in + let fid_new = Ident.create Ident.kfootprint !max_stamp in + let exp_new = Sil.Var fid_new in + let off_new = Sil.Off_index exp_new in + let offs_seen' = off_new:: offs_seen in + let eqs' = (fid_new, idx):: eqs in + laundry offs_seen' eqs' offs' in + laundry [] [] offs_in + +let strexp_extend_values + pname tenv orig_prop footprint_part kind max_stamp + se te (off : Sil.offset list) inst = + let typ = Sil.texp_to_typ None te in + let off', laundry_atoms = + let off', eqs = laundry_offset_for_footprint max_stamp off in + (* do laundry_offset whether footprint_part is true or not, so max_stamp is modified anyway *) + if footprint_part then + off', list_map (fun (id, e) -> Prop.mk_eq (Sil.Var id) e) eqs + else off, [] in + if !Config.trace_rearrange then (L.d_str "entering strexp_extend_values se: "; Sil.d_sexp se; L.d_str " typ: "; + Sil.d_typ_full typ; L.d_str " off': "; Sil.d_offset_list off'; L.d_strln (if footprint_part then " FP" else " RE")); + let atoms_se_typ_list = + _strexp_extend_values + pname tenv orig_prop footprint_part kind max_stamp se typ off' inst in + let atoms_se_typ_list_filtered = + let neg_atom = function Sil.Aeq(e1, e2) -> Sil.Aneq(e1, e2) | Sil.Aneq(e1, e2) -> Sil.Aeq(e1, e2) in + let check_neg_atom atom = Prover.check_atom Prop.prop_emp (neg_atom atom) in + let check_not_inconsistent (atoms, _, _) = not (list_exists check_neg_atom atoms) in + list_filter check_not_inconsistent atoms_se_typ_list in + if !Config.trace_rearrange then L.d_strln "exiting strexp_extend_values"; + let st = match te with + | Sil.Sizeof(_, st) -> st + | _ -> Sil.Subtype.exact in + list_map (fun (atoms', se', typ') -> (laundry_atoms @ atoms', se', Sil.Sizeof (typ', st))) atoms_se_typ_list_filtered + +let rec collect_root_offset exp = + let root = Sil.root_of_lexp exp in + let offsets = Sil.exp_get_offsets exp in + (root, offsets) + +(** Sil.Construct a points-to predicate for an expression, to add to a footprint. *) +let mk_ptsto_exp_footprint + pname tenv orig_prop (lexp, typ) max_stamp inst : Sil.hpred * Sil.hpred * Sil.atom list = + let root, off = collect_root_offset lexp in + if not (exp_has_only_footprint_ids root) + then begin + (* in angelic mode, purposely ignore dangling pointer warnings during the footprint phase -- we + * will fix them during the re - execution phase *) + if not (!Config.angelic_execution && !Config.footprint) then + begin + if !Config.developer_mode then + L.err "!!!! Footprint Error, Bad Root : %a !!!! @\n" (Sil.pp_exp pe_text) lexp; + let deref_str = Localise.deref_str_dangling None in + let err_desc = + Errdesc.explain_dereference deref_str orig_prop (State.get_loc ()) in + raise + (Exceptions.Dangling_pointer_dereference + (None, err_desc, try assert false with Assert_failure x -> x)) + end + end; + let off_foot, eqs = laundry_offset_for_footprint max_stamp off in + let st = match !Sil.curr_language with + | Sil.C_CPP -> Sil.Subtype.exact + | Sil.Java -> Sil.Subtype.subtypes in + let create_ptsto footprint_part off0 = match root, off0, typ with + | Sil.Lvar pvar, [], Sil.Tfun _ -> + let fun_name = Procname.from_string (Mangled.to_string (Sil.pvar_get_name pvar)) in + let fun_exp = Sil.Const (Sil.Cfun fun_name) in + ([], Prop.mk_ptsto root (Sil.Eexp (fun_exp, inst)) (Sil.Sizeof (typ, st))) + | _, [], Sil.Tfun _ -> + let atoms, se, t = + create_struct_values + pname tenv orig_prop footprint_part Ident.kfootprint max_stamp typ off0 inst in + (atoms, Prop.mk_ptsto root se (Sil.Sizeof (t, st))) + | _ -> + let atoms, se, t = + create_struct_values + pname tenv orig_prop footprint_part Ident.kfootprint max_stamp typ off0 inst in + (atoms, Prop.mk_ptsto root se (Sil.Sizeof (t, st))) in + let atoms, ptsto_foot = create_ptsto true off_foot in + let sub = Sil.sub_of_list eqs in + let ptsto = Sil.hpred_sub sub ptsto_foot in + let atoms' = list_map (fun (id, e) -> Prop.mk_eq (Sil.Var id) e) eqs in + (ptsto, ptsto_foot, atoms @ atoms') + +(** Check if the path in exp exists already in the current ptsto predicate. +If it exists, return None. Otherwise, return [Some fld] with [fld] the missing field. *) +let prop_iter_check_fields_ptsto_shallow iter lexp = + let offset = Sil.exp_get_offsets lexp in + let (e, se, t) = + match Prop.prop_iter_current iter with + | Sil.Hpointsto (e, se, t), _ -> (e, se, t) + | _ -> assert false in + let rec check_offset se = function + | [] -> None + | (Sil.Off_fld (fld, _)):: off' -> + (match se with + | Sil.Estruct (fsel, _) -> + (try + let _, se' = list_find (fun (fld', _) -> Sil.fld_equal fld fld') fsel in + check_offset se' off' + with Not_found -> Some fld) + | _ -> Some fld) + | (Sil.Off_index e):: off' -> None in + check_offset se offset + +let fav_max_stamp fav = + let max_stamp = ref 0 in + let f id = max_stamp := max !max_stamp (Ident.get_stamp id) in + list_iter f (Sil.fav_to_list fav); + max_stamp + +(** [prop_iter_extend_ptsto iter lexp] extends the current psto +predicate in [iter] with enough fields to follow the path in +[lexp] -- field splitting model. It also materializes all +indices accessed in lexp. *) +let prop_iter_extend_ptsto pname tenv orig_prop iter lexp inst = + if !Config.trace_rearrange then (L.d_str "entering prop_iter_extend_ptsto lexp: "; Sil.d_exp lexp; L.d_ln ()); + let offset = Sil.exp_get_offsets lexp in + let max_stamp = fav_max_stamp (Prop.prop_iter_fav iter) in + let max_stamp_val = !max_stamp in + let extend_footprint_pred = function + | Sil.Hpointsto(e, se, te) -> + let atoms_se_te_list = + strexp_extend_values + pname tenv orig_prop true Ident.kfootprint (ref max_stamp_val) se te offset inst in + list_map (fun (atoms', se', te') -> (atoms', Sil.Hpointsto (e, se', te'))) atoms_se_te_list + | Sil.Hlseg (k, hpara, e1, e2, el) -> + begin + match hpara.Sil.body with + | Sil.Hpointsto(e', se', te'):: body_rest -> + let atoms_se_te_list = + strexp_extend_values + pname tenv orig_prop true Ident.kfootprint + (ref max_stamp_val) se' te' offset inst in + let atoms_body_list = + list_map (fun (atoms0, se0, te0) -> (atoms0, Sil.Hpointsto(e', se0, te0):: body_rest)) atoms_se_te_list in + let atoms_hpara_list = + list_map (fun (atoms, body') -> (atoms, { hpara with Sil.body = body'})) atoms_body_list in + list_map (fun (atoms, hpara') -> (atoms, Sil.Hlseg(k, hpara', e1, e2, el))) atoms_hpara_list + | _ -> assert false + end + | _ -> assert false in + let atoms_se_te_to_iter e (atoms, se, te) = + let iter' = list_fold_left (Prop.prop_iter_add_atom !Config.footprint) iter atoms in + Prop.prop_iter_update_current iter' (Sil.Hpointsto (e, se, te)) in + let do_extend e se te = + if !Config.trace_rearrange then begin + L.d_strln "entering do_extend"; + L.d_str "e: "; Sil.d_exp e; L.d_str " se : "; Sil.d_sexp se; L.d_str " te: "; Sil.d_texp_full te; + L.d_ln (); L.d_ln () + end; + let extend_kind = match e with (* Determine whether to extend the footprint part or just the normal part *) + | Sil.Var id when not (Ident.is_footprint id) -> Ident.kprimed + | Sil.Lvar pvar when Sil.pvar_is_local pvar -> Ident.kprimed + | _ -> Ident.kfootprint in + let iter_list = + let atoms_se_te_list = + strexp_extend_values + pname tenv orig_prop false extend_kind max_stamp se te offset inst in + list_map (atoms_se_te_to_iter e) atoms_se_te_list in + let res_iter_list = + if Ident.kind_equal extend_kind Ident.kprimed + then iter_list (* normal part already extended: nothing to do *) + else (* extend footprint part *) + let atoms_fp_sigma_list = + let footprint_sigma = Prop.prop_iter_get_footprint_sigma iter in + let sigma_pto, sigma_rest = + list_partition (function + | Sil.Hpointsto(e', _, _) -> Sil.exp_equal e e' + | Sil.Hlseg (_, _, e1, e2, _) -> Sil.exp_equal e e1 + | Sil.Hdllseg (_, _, e_iF, e_oB, e_oF, e_iB, _) -> Sil.exp_equal e e_iF || Sil.exp_equal e e_iB + ) footprint_sigma in + let atoms_sigma_list = + match sigma_pto with + | [hpred] -> + let atoms_hpred_list = extend_footprint_pred hpred in + list_map (fun (atoms, hpred') -> (atoms, hpred' :: sigma_rest)) atoms_hpred_list + | _ -> + L.d_warning "Cannot extend "; Sil.d_exp lexp; L.d_strln " in"; Prop.d_prop (Prop.prop_iter_to_prop iter); L.d_ln(); + [([], footprint_sigma)] in + list_map (fun (atoms, sigma') -> (atoms, list_stable_sort Sil.hpred_compare sigma')) atoms_sigma_list in + let iter_atoms_fp_sigma_list = + list_product iter_list atoms_fp_sigma_list in + list_map (fun (iter, (atoms, fp_sigma)) -> + let iter' = list_fold_left (Prop.prop_iter_add_atom !Config.footprint) iter atoms in + Prop.prop_iter_replace_footprint_sigma iter' fp_sigma + ) iter_atoms_fp_sigma_list in + let res_prop_list = + list_map Prop.prop_iter_to_prop res_iter_list in + begin + L.d_str "in prop_iter_extend_ptsto lexp: "; Sil.d_exp lexp; L.d_ln (); + L.d_strln "prop before:"; + let prop_before = Prop.prop_iter_to_prop iter in + Prop.d_prop prop_before; L.d_ln (); + L.d_ln (); L.d_ln (); + L.d_strln "prop list after:"; + Propgraph.d_proplist prop_before res_prop_list; L.d_ln (); + L.d_ln (); L.d_ln (); + res_iter_list + end in + begin + match Prop.prop_iter_current iter with + | Sil.Hpointsto (e, se, te), _ -> do_extend e se te + | _ -> assert false + end + +(** Add a pointsto for [root(lexp): typ] to the sigma and footprint of a +prop, if it's compatible with the allowed footprint +variables. Then, change it into a iterator. This function ensures +that [root(lexp): typ] is the current hpred of the iterator. typ +is the type of the root of lexp. *) +let prop_iter_add_hpred_footprint_to_prop pname tenv prop (lexp, typ) inst = + let max_stamp = fav_max_stamp (Prop.prop_footprint_fav prop) in + let ptsto, ptsto_foot, atoms = + mk_ptsto_exp_footprint pname tenv prop (lexp, typ) max_stamp inst in + L.d_strln "++++ Adding footprint frame"; + Prop.d_prop (Prop.prop_hpred_star Prop.prop_emp ptsto); + L.d_ln (); L.d_ln (); + let eprop = Prop.expose prop in + let foot_sigma = ptsto_foot :: Prop.get_sigma_footprint eprop in + let nfoot_sigma = Prop.sigma_normalize_prop Prop.prop_emp foot_sigma in + let prop' = Prop.normalize (Prop.replace_sigma_footprint nfoot_sigma eprop) in + let prop_new = list_fold_left (Prop.prop_atom_and ~footprint:!Config.footprint) prop' atoms in + let iter = match (Prop.prop_iter_create prop_new) with + | None -> + let prop_new' = Prop.normalize (Prop.prop_hpred_star prop_new ptsto) in + begin + match (Prop.prop_iter_create prop_new') with + | None -> assert false + | Some iter -> iter + end + | Some iter -> Prop.prop_iter_prev_then_insert iter ptsto in + let offsets_default = Sil.exp_get_offsets lexp in + Prop.prop_iter_set_state iter offsets_default + +(** Add a pointsto for [root(lexp): typ] to the iterator and to the +footprint, if it's compatible with the allowed footprint +variables. This function ensures that [root(lexp): typ] is the +current hpred of the iterator. typ is the type of the root of lexp. *) +let prop_iter_add_hpred_footprint pname tenv orig_prop iter (lexp, typ) inst = + let max_stamp = fav_max_stamp (Prop.prop_iter_footprint_fav iter) in + let ptsto, ptsto_foot, atoms = + mk_ptsto_exp_footprint pname tenv orig_prop (lexp, typ) max_stamp inst in + L.d_strln "++++ Adding footprint frame"; + Prop.d_prop (Prop.prop_hpred_star Prop.prop_emp ptsto); + L.d_ln (); L.d_ln (); + let foot_sigma = ptsto_foot :: (Prop.prop_iter_get_footprint_sigma iter) in + let iter_foot = Prop.prop_iter_prev_then_insert iter ptsto in + let iter_foot_atoms = list_fold_left (Prop.prop_iter_add_atom (!Config.footprint)) iter_foot atoms in + let iter' = Prop.prop_iter_replace_footprint_sigma iter_foot_atoms foot_sigma in + let offsets_default = Sil.exp_get_offsets lexp in + Prop.prop_iter_set_state iter' offsets_default + +let sort_ftl ftl = + let compare (f1, _) (f2, _) = Sil.fld_compare f1 f2 in + list_sort compare ftl + +exception ARRAY_ACCESS + +let rearrange_arith lexp prop = + if !Config.trace_rearrange then begin + L.d_strln "entering rearrange_arith"; + L.d_str "lexp: "; Sil.d_exp lexp; L.d_ln (); + L.d_str "prop: "; L.d_ln (); Prop.d_prop prop; L.d_ln (); L.d_ln () + end; + if (!Config.array_level >= 2) then raise ARRAY_ACCESS + else + let root = Sil.root_of_lexp lexp in + if Prover.check_allocatedness prop root then + raise ARRAY_ACCESS + else + raise (Exceptions.Symexec_memory_error (try assert false with Assert_failure x -> x)) + +let pp_rearrangement_error message prop lexp = + L.d_strln (".... Rearrangement Error .... " ^ message); + L.d_str "Exp:"; Sil.d_exp lexp; L.d_ln (); + L.d_str "Prop:"; L.d_ln (); Prop.d_prop prop; L.d_ln (); L.d_ln () + +let name_n = Ident.string_to_name "n" + +(** do re-arrangment for an iter whose current element is a pointsto *) +let iter_rearrange_ptsto pname tenv orig_prop iter lexp inst = + if !Config.trace_rearrange then begin + L.d_increase_indent 1; + L.d_strln "entering iter_rearrange_ptsto"; + L.d_str "lexp: "; Sil.d_exp lexp; L.d_ln (); + L.d_strln "prop:"; Prop.d_prop orig_prop; L.d_ln (); + L.d_strln "iter:"; Prop.d_prop (Prop.prop_iter_to_prop iter); + L.d_ln (); L.d_ln () + end; + let check_field_splitting () = + match prop_iter_check_fields_ptsto_shallow iter lexp with + | None -> () + | Some fld -> + begin + pp_rearrangement_error "field splitting check failed" orig_prop lexp; + raise (Exceptions.Missing_fld (fld, try assert false with Assert_failure x -> x)) + end in + let res = + if !Config.footprint + then + prop_iter_extend_ptsto pname tenv orig_prop iter lexp inst + else + begin + check_field_splitting (); + match Prop.prop_iter_current iter with + | Sil.Hpointsto (e, se, te), offset -> + let max_stamp = fav_max_stamp (Prop.prop_iter_fav iter) in + let atoms_se_te_list = + strexp_extend_values + pname tenv orig_prop false Ident.kprimed max_stamp se te offset inst in + let handle_case (atoms', se', te') = + let iter' = list_fold_left (Prop.prop_iter_add_atom !Config.footprint) iter atoms' in + Prop.prop_iter_update_current iter' (Sil.Hpointsto (e, se', te')) in + let filter it = + let p = Prop.prop_iter_to_prop it in + not (Prover.check_inconsistency p) in + list_filter filter (list_map handle_case atoms_se_te_list) + | _ -> [iter] + end in + begin + if !Config.trace_rearrange then begin + L.d_strln "exiting iter_rearrange_ptsto, returning results"; + Prop.d_proplist_with_typ (list_map Prop.prop_iter_to_prop res); + L.d_decrease_indent 1; + L.d_ln (); L.d_ln () + end; + res + end + +(** do re-arrangment for an iter whose current element is a nonempty listseg *) +let iter_rearrange_ne_lseg recurse_on_iters iter para e1 e2 elist = + if (!Config.nelseg) then + let iter_inductive_case = + let n' = Sil.Var (Ident.create_fresh Ident.kprimed) in + let (_, para_inst1) = Sil.hpara_instantiate para e1 n' elist in + let hpred_list1 = para_inst1@[Prop.mk_lseg Sil.Lseg_NE para n' e2 elist] in + Prop.prop_iter_update_current_by_list iter hpred_list1 in + let iter_base_case = + let (_, para_inst) = Sil.hpara_instantiate para e1 e2 elist in + Prop.prop_iter_update_current_by_list iter para_inst in + recurse_on_iters [iter_inductive_case; iter_base_case] + else + let iter_inductive_case = + let n' = Sil.Var (Ident.create_fresh Ident.kprimed) in + let (_, para_inst1) = Sil.hpara_instantiate para e1 n' elist in + let hpred_list1 = para_inst1@[Prop.mk_lseg Sil.Lseg_PE para n' e2 elist] in + Prop.prop_iter_update_current_by_list iter hpred_list1 in + recurse_on_iters [iter_inductive_case] + +(** do re-arrangment for an iter whose current element is a nonempty dllseg to be unrolled from lhs *) +let iter_rearrange_ne_dllseg_first recurse_on_iters iter para_dll e1 e2 e3 e4 elist = + let iter_inductive_case = + let n' = Sil.Var (Ident.create_fresh Ident.kprimed) in + let (_, para_dll_inst1) = Sil.hpara_dll_instantiate para_dll e1 e2 n' elist in + let hpred_list1 = para_dll_inst1@[Prop.mk_dllseg Sil.Lseg_NE para_dll n' e1 e3 e4 elist] in + Prop.prop_iter_update_current_by_list iter hpred_list1 in + let iter_base_case = + let (_, para_dll_inst) = Sil.hpara_dll_instantiate para_dll e1 e2 e3 elist in + let iter' = Prop.prop_iter_update_current_by_list iter para_dll_inst in + let prop' = Prop.prop_iter_to_prop iter' in + let prop'' = Prop.conjoin_eq ~footprint: (!Config.footprint) e1 e4 prop' in + match (Prop.prop_iter_create prop'') with + | None -> assert false + | Some iter' -> iter' in + recurse_on_iters [iter_inductive_case; iter_base_case] + +(** do re-arrangment for an iter whose current element is a nonempty dllseg to be unrolled from rhs *) +let iter_rearrange_ne_dllseg_last recurse_on_iters iter para_dll e1 e2 e3 e4 elist = + let iter_inductive_case = + let n' = Sil.Var (Ident.create_fresh Ident.kprimed) in + let (_, para_dll_inst1) = Sil.hpara_dll_instantiate para_dll e4 n' e3 elist in + let hpred_list1 = para_dll_inst1@[Prop.mk_dllseg Sil.Lseg_NE para_dll e1 e2 e4 n' elist] in + Prop.prop_iter_update_current_by_list iter hpred_list1 in + let iter_base_case = + let (_, para_dll_inst) = Sil.hpara_dll_instantiate para_dll e4 e2 e3 elist in + let iter' = Prop.prop_iter_update_current_by_list iter para_dll_inst in + let prop' = Prop.prop_iter_to_prop iter' in + let prop'' = Prop.conjoin_eq ~footprint: (!Config.footprint) e1 e4 prop' in + match (Prop.prop_iter_create prop'') with + | None -> assert false + | Some iter' -> iter' in + recurse_on_iters [iter_inductive_case; iter_base_case] + +(** do re-arrangment for an iter whose current element is a possibly empty listseg *) +let iter_rearrange_pe_lseg recurse_on_iters default_case_iter iter para e1 e2 elist = + let iter_nonemp_case = + let n' = Sil.Var (Ident.create_fresh Ident.kprimed) in + let (_, para_inst1) = Sil.hpara_instantiate para e1 n' elist in + let hpred_list1 = para_inst1@[Prop.mk_lseg Sil.Lseg_PE para n' e2 elist] in + Prop.prop_iter_update_current_by_list iter hpred_list1 in + let iter_subcases = + let removed_prop = Prop.prop_iter_remove_curr_then_to_prop iter in + let prop' = Prop.conjoin_eq ~footprint: (!Config.footprint) e1 e2 removed_prop in + match (Prop.prop_iter_create prop') with + | None -> + let iter' = default_case_iter (Prop.prop_iter_set_state iter ()) in + [Prop.prop_iter_set_state iter' ()] + | Some iter' -> [iter_nonemp_case; iter'] in + recurse_on_iters iter_subcases + +(** do re-arrangment for an iter whose current element is a possibly empty dllseg to be unrolled from lhs *) +let iter_rearrange_pe_dllseg_first recurse_on_iters default_case_iter iter para_dll e1 e2 e3 e4 elist = + let iter_inductive_case = + let n' = Sil.Var (Ident.create_fresh Ident.kprimed) in + let (_, para_dll_inst1) = Sil.hpara_dll_instantiate para_dll e1 e2 n' elist in + let hpred_list1 = para_dll_inst1@[Prop.mk_dllseg Sil.Lseg_PE para_dll n' e1 e3 e4 elist] in + Prop.prop_iter_update_current_by_list iter hpred_list1 in + let iter_subcases = + let removed_prop = Prop.prop_iter_remove_curr_then_to_prop iter in + let prop' = Prop.conjoin_eq ~footprint: (!Config.footprint) e1 e3 removed_prop in + let prop'' = Prop.conjoin_eq ~footprint: (!Config.footprint) e2 e4 prop' in + match (Prop.prop_iter_create prop'') with + | None -> + let iter' = default_case_iter (Prop.prop_iter_set_state iter ()) in + [Prop.prop_iter_set_state iter' ()] + | Some iter' -> [iter_inductive_case; iter'] in + recurse_on_iters iter_subcases + +(** do re-arrangment for an iter whose current element is a possibly empty dllseg to be unrolled from rhs *) +let iter_rearrange_pe_dllseg_last recurse_on_iters default_case_iter iter para_dll e1 e2 e3 e4 elist = + let iter_inductive_case = + let n' = Sil.Var (Ident.create_fresh Ident.kprimed) in + let (_, para_dll_inst1) = Sil.hpara_dll_instantiate para_dll e4 n' e3 elist in + let hpred_list1 = para_dll_inst1@[Prop.mk_dllseg Sil.Lseg_PE para_dll e1 e2 e4 n' elist] in + Prop.prop_iter_update_current_by_list iter hpred_list1 in + let iter_subcases = + let removed_prop = Prop.prop_iter_remove_curr_then_to_prop iter in + let prop' = Prop.conjoin_eq ~footprint: (!Config.footprint) e1 e3 removed_prop in + let prop'' = Prop.conjoin_eq ~footprint: (!Config.footprint) e2 e4 prop' in + match (Prop.prop_iter_create prop'') with + | None -> + let iter' = default_case_iter (Prop.prop_iter_set_state iter ()) in + [Prop.prop_iter_set_state iter' ()] + | Some iter' -> [iter_inductive_case; iter'] in + recurse_on_iters iter_subcases + +(** find the type at the offset from the given type expression, if any *) +let type_at_offset texp off = + let rec strip_offset off typ = match off, typ with + | [], _ -> Some typ + | (Sil.Off_fld (f, _)):: off', Sil.Tstruct (ftal, sftal, _, _, _, _, _) -> + (try + let typ' = + (fun (x, y, z) -> y) + (list_find (fun (f', t', a') -> Ident.fieldname_equal f f') ftal) in + strip_offset off' typ' + with Not_found -> None) + | (Sil.Off_index _):: off', Sil.Tarray (typ', _) -> + strip_offset off' typ' + | _ -> None in + match texp with + | Sil.Sizeof(typ, _) -> + strip_offset off typ + | _ -> None + +(** Check that the size of a type coming from an instruction does not exceed the size of the type from the pointsto predicate +For example, that a pointer to int is not used to assign to a char *) +let check_type_size pname prop texp off typ_from_instr = + L.d_strln_color Orange "check_type_size"; + L.d_str "off: "; Sil.d_offset_list off; L.d_ln (); + L.d_str "typ_from_instr: "; Sil.d_typ_full typ_from_instr; L.d_ln (); + match type_at_offset texp off with + | Some typ_of_object -> + L.d_str "typ_o: "; Sil.d_typ_full typ_of_object; L.d_ln (); + if Prover.type_size_comparable typ_from_instr typ_of_object && Prover.check_type_size_leq typ_from_instr typ_of_object = false + then begin + let deref_str = Localise.deref_str_pointer_size_mismatch typ_from_instr typ_of_object in + let loc = State.get_loc () in + let exn = + Exceptions.Pointer_size_mismatch ( + Errdesc.explain_dereference deref_str prop loc, + try assert false with Assert_failure x -> x) in + let pre_opt = State.get_normalized_pre (Abs.abstract_no_symop pname) in + Reporting.log_warning pname ~pre: pre_opt exn + end + | None -> + L.d_str "texp: "; Sil.d_texp_full texp; L.d_ln () + +(** Exposes lexp |->- from iter. In case that it is not possible to +* expose lexp |->-, this function prints an error message and +* faults. There are four things to note. First, typ is the type of the +* root of lexp. Second, prop should mean the same as iter. Third, the +* result [] means that the given input iter is inconsistent. This +* happens when the theorem prover can prove the inconsistency of prop, +* only after unrolling some predicates in prop. This function ensures +* that the theorem prover cannot prove the inconsistency of any of the +* new iters in the result. *) +let rec iter_rearrange + pname tenv lexp typ_from_instr prop iter + inst: (Sil.offset list) Prop.prop_iter list = + let typ = match Sil.exp_get_offsets lexp with + | Sil.Off_fld (f, ((Sil.Tstruct _) as struct_typ)) :: _ -> (* access through field: get the struct type from the field *) + if !Config.trace_rearrange then begin + L.d_increase_indent 1; + L.d_str "iter_rearrange: root of lexp accesses field "; L.d_strln (Ident.fieldname_to_string f); + L.d_str " type from instruction: "; Sil.d_typ_full typ_from_instr; L.d_ln(); + L.d_str " struct type from field: "; Sil.d_typ_full struct_typ; L.d_ln(); + L.d_decrease_indent 1; + L.d_ln(); + end; + struct_typ + | _ -> + typ_from_instr in + if !Config.trace_rearrange then begin + L.d_increase_indent 1; + L.d_strln "entering iter_rearrange"; + L.d_str "lexp: "; Sil.d_exp lexp; L.d_ln (); + L.d_str "typ: "; Sil.d_typ_full typ; L.d_ln (); + L.d_strln "prop:"; Prop.d_prop prop; L.d_ln (); + L.d_strln "iter:"; Prop.d_prop (Prop.prop_iter_to_prop iter); + L.d_ln (); L.d_ln () + end; + let default_case_iter (iter': unit Prop.prop_iter) = + if !Config.trace_rearrange then L.d_strln "entering default_case_iter"; + if !Config.footprint then + prop_iter_add_hpred_footprint pname tenv prop iter' (lexp, typ) inst + else + if (!Config.array_level >= 1 && not !Config.footprint && Sil.exp_pointer_arith lexp) + then rearrange_arith lexp prop + else begin + pp_rearrangement_error "cannot find predicate with root" prop lexp; + if not !Config.footprint then Printer.force_delayed_prints (); + raise (Exceptions.Symexec_memory_error (try assert false with Assert_failure x -> x)) + end in + let recurse_on_iters iters = + let f_one_iter iter' = + let prop' = Prop.prop_iter_to_prop iter' in + if Prover.check_inconsistency prop' then [] + else iter_rearrange pname tenv (Prop.lexp_normalize_prop prop' lexp) typ prop' iter' inst in + let rec f_many_iters iters_lst = function + | [] -> list_flatten (list_rev iters_lst) + | iter':: iters' -> + let iters_res' = f_one_iter iter' in + f_many_iters (iters_res':: iters_lst) iters' in + f_many_iters [] iters in + let filter = function + | Sil.Hpointsto (base, _, _) | Sil.Hlseg (_, _, base, _, _) -> + Prover.is_root prop base lexp + | Sil.Hdllseg (_, _, first, _, _, last, _) -> + let result_first = Prover.is_root prop first lexp in + match result_first with + | None -> Prover.is_root prop last lexp + | Some _ -> result_first in + let res = + match Prop.prop_iter_find iter filter with + | None -> + [default_case_iter iter] + | Some iter -> + match Prop.prop_iter_current iter with + | (Sil.Hpointsto (_, _, texp), off) -> + if !Config.type_size then check_type_size pname prop texp off typ_from_instr; + iter_rearrange_ptsto pname tenv prop iter lexp inst + | (Sil.Hlseg (Sil.Lseg_NE, para, e1, e2, elist), _) -> + iter_rearrange_ne_lseg recurse_on_iters iter para e1 e2 elist + | (Sil.Hlseg (Sil.Lseg_PE, para, e1, e2, elist), _) -> + iter_rearrange_pe_lseg recurse_on_iters default_case_iter iter para e1 e2 elist + | (Sil.Hdllseg (Sil.Lseg_NE, para_dll, e1, e2, e3, e4, elist), _) -> + begin + match Prover.is_root prop e1 lexp, Prover.is_root prop e4 lexp with + | None, None -> assert false + | Some _, _ -> iter_rearrange_ne_dllseg_first recurse_on_iters iter para_dll e1 e2 e3 e4 elist + | _, Some _ -> iter_rearrange_ne_dllseg_last recurse_on_iters iter para_dll e1 e2 e3 e4 elist + end + | (Sil.Hdllseg (Sil.Lseg_PE, para_dll, e1, e2, e3, e4, elist), _) -> + begin + match Prover.is_root prop e1 lexp, Prover.is_root prop e4 lexp with + | None, None -> assert false + | Some _, _ -> iter_rearrange_pe_dllseg_first recurse_on_iters default_case_iter iter para_dll e1 e2 e3 e4 elist + | _, Some _ -> iter_rearrange_pe_dllseg_last recurse_on_iters default_case_iter iter para_dll e1 e2 e3 e4 elist + end in + if !Config.trace_rearrange then begin + L.d_strln "exiting iter_rearrange, returning results"; + Prop.d_proplist_with_typ (list_map Prop.prop_iter_to_prop res); + L.d_decrease_indent 1; + L.d_ln (); L.d_ln () + end; + res + +(** Check for dereference errors: dereferencing 0, a freed value, or an undefined value *) +let check_dereference_error pdesc (prop : Prop.normal Prop.t) lexp loc = + let nullable_obj_str = ref "" in + (* return true if deref_exp is pointed to by a field or parameter with a @Nullable annotation *) + let is_pt_by_nullable_fld_or_param deref_exp = + let ann_sig = Models.get_annotated_signature pdesc (Cfg.Procdesc.get_proc_name pdesc) in + list_exists + (fun hpred -> + match hpred with + | Sil.Hpointsto (Sil.Lvar pvar, Sil.Eexp (exp, _), _) + when Sil.exp_equal exp deref_exp && Annotations.param_is_nullable pvar ann_sig -> + nullable_obj_str := Sil.pvar_to_string pvar; + true + | Sil.Hpointsto (_, Sil.Estruct (flds, inst), Sil.Sizeof (typ, _)) -> + let is_nullable fld = + match Annotations.get_field_type_and_annotation fld typ with + | Some (_, annot) -> Annotations.ia_is_nullable annot + | _ -> false in + let is_strexp_pt_by_nullable_fld (fld, strexp) = + match strexp with + | Sil.Eexp (exp, _) when Sil.exp_equal exp deref_exp && is_nullable fld -> + nullable_obj_str := Ident.fieldname_to_string fld; + true + | _ -> false in + list_exists is_strexp_pt_by_nullable_fld flds + | _ -> false) + (Prop.get_sigma prop) in + let root = Sil.root_of_lexp lexp in + let is_deref_of_nullable = + let is_definitely_non_null exp prop = + Prover.check_disequal prop exp Sil.exp_zero in + !Config.report_nullable_inconsistency && !Sil.curr_language = Sil.Java && + not (is_definitely_non_null root prop) && is_pt_by_nullable_fld_or_param root in + let relevant_attributes_getters = [ + Prop.get_resource_undef_attribute; + Prop.get_variadic_function_argument_attribute ] in + let get_relevant_attributes exp = + let rec fold_getters = function + | [] -> None + | getter:: tl -> match getter prop exp with + | Some _ as some_attr -> some_attr + | None -> fold_getters tl in + fold_getters relevant_attributes_getters in + let attribute_opt = match get_relevant_attributes root with + | Some att -> Some att + | None -> (* try to remove an offset if any, and find the attribute there *) + let root_no_offset = match root with + | Sil.BinOp((Sil.PlusPI | Sil.PlusA | Sil.MinusPI | Sil.MinusA), base, _) -> base + | _ -> root in + get_relevant_attributes root_no_offset in + let is_premature_nil_termination = match attribute_opt with + | Some (Sil.Avariadic_function_argument _) -> true + | _ -> false in + if not is_premature_nil_termination + && (Prover.check_zero (Sil.root_of_lexp root) || is_deref_of_nullable) then + begin + let deref_str = + if is_deref_of_nullable then Localise.deref_str_nullable None !nullable_obj_str + else Localise.deref_str_null None in + let err_desc = + Errdesc.explain_dereference ~use_buckets: true ~is_nullable: is_deref_of_nullable + deref_str prop loc in + if Localise.is_parameter_not_null_checked_desc err_desc then + raise (Exceptions.Parameter_not_null_checked (err_desc, try assert false with Assert_failure x -> x)) + else if Localise.is_field_not_null_checked_desc err_desc then + raise (Exceptions.Field_not_null_checked (err_desc, try assert false with Assert_failure x -> x)) + else raise (Exceptions.Null_dereference (err_desc, try assert false with Assert_failure x -> x)) + end; + match attribute_opt with + | Some (Sil.Adangling dk) -> + let deref_str = Localise.deref_str_dangling (Some dk) in + let err_desc = Errdesc.explain_dereference deref_str prop (State.get_loc ()) in + raise (Exceptions.Dangling_pointer_dereference (Some dk, err_desc, try assert false with Assert_failure x -> x)) + | Some (Sil.Aundef (s, undef_loc, _)) -> + if !Config.angelic_execution then () + else + let deref_str = Localise.deref_str_undef (s, undef_loc) in + let err_desc = Errdesc.explain_dereference deref_str prop loc in + raise (Exceptions.Skip_pointer_dereference (err_desc, try assert false with Assert_failure x -> x)) + | Some (Sil.Aresource ({ Sil.ra_kind = Sil.Rrelease } as ra)) -> + let deref_str = Localise.deref_str_freed ra in + let err_desc = Errdesc.explain_dereference ~use_buckets: true deref_str prop loc in + raise (Exceptions.Use_after_free (err_desc, try assert false with Assert_failure x -> x)) + | Some (Sil.Avariadic_function_argument (pn, n, i)) -> + let deref_str = Localise.deref_str_nil_argument_in_variadic_method pn n i in + let err_desc = + Errdesc.explain_dereference ~use_buckets: true ~is_premature_nil: true + deref_str prop loc in + raise (Exceptions.Premature_nil_termination (err_desc, try assert false with Assert_failure x -> x)) + | _ -> + if Prover.check_equal Prop.prop_emp (Sil.root_of_lexp root) Sil.exp_minus_one then + let deref_str = Localise.deref_str_dangling None in + let err_desc = Errdesc.explain_dereference deref_str prop loc in + raise (Exceptions.Dangling_pointer_dereference (None, err_desc, try assert false with Assert_failure x -> x)) + +(* Check that an expression representin an objc block can be null and raise a [B1] null exception.*) +(* It's used to check that we don't call possibly null blocks *) +let check_call_to_objc_block_error pdesc prop fun_exp loc = + let fun_exp_may_be_null () = (* may be null if we don't know if it is definitely not null *) + not (Prover.check_disequal prop (Sil.root_of_lexp fun_exp) Sil.exp_zero) in + let try_explaining_exp e = (* when e is a temp var, try to find the pvar defining e*) + match e with + | Sil.Var id -> + (match (Errdesc.find_ident_assignment (State.get_node ()) id) with + | Some (_, e') -> e' + | None -> e) + | _ -> e in + let get_exp_called () = (* Exp called in the block's function call*) + match State.get_instr () with + | Some Sil.Call(_, Sil.Var id, _, _, _) -> + Errdesc.find_ident_assignment (State.get_node ()) id + | _ -> None in + let is_fun_exp_captured_var () = (* Called expression is a captured variable of the block *) + match get_exp_called () with + | Some (_, Sil.Lvar pvar) -> (* pvar is the block *) + let name = Sil.pvar_get_name pvar in + list_exists (fun (cn, _) -> (Mangled.to_string name) = (Mangled.to_string cn)) (Cfg.Procdesc.get_captured pdesc) + | _ -> false in + let is_field_deref () = (*Called expression is a field *) + match get_exp_called () with + | Some (_, (Sil.Lfield(e', fn, t))) -> + let e'' = try_explaining_exp e' in + Some (Sil.Lfield(e'', fn, t)), true (* the block dereferences is a field of an object*) + | Some (_, e) -> Some e, false + | _ -> None, false in + if (!Sil.curr_language = Sil.C_CPP) && fun_exp_may_be_null () && not (is_fun_exp_captured_var ()) then begin + let deref_str = Localise.deref_str_null None in + let err_desc_nobuckets = Errdesc.explain_dereference ~is_nullable: true deref_str prop loc in + match fun_exp with + | Sil.Var id when Ident.is_footprint id -> + let e_opt, is_field_deref = is_field_deref () in + let err_desc_nobuckets' = (match e_opt with + | Some e -> Localise.parameter_field_not_null_checked_desc err_desc_nobuckets e + | _ -> err_desc_nobuckets) in + let err_desc = + Localise.error_desc_set_bucket + err_desc_nobuckets' Localise.BucketLevel.b1 !Config.show_buckets in + if is_field_deref then + raise (Exceptions.Field_not_null_checked (err_desc, try assert false with Assert_failure x -> x)) + else + raise (Exceptions.Parameter_not_null_checked (err_desc, try assert false with Assert_failure x -> x)) + | _ -> (* HP: fun_exp is not a footprint therefore, either is a local or it's a modified param *) + let err_desc = + Localise.error_desc_set_bucket + err_desc_nobuckets Localise.BucketLevel.b1 !Config.show_buckets in + raise (Exceptions.Null_dereference (err_desc, try assert false with Assert_failure x -> x)) + end + +(** [rearrange lexp prop] rearranges [prop] into the form [prop' * lexp|->strexp:typ]. +It returns an iterator with [lexp |-> strexp: typ] as current predicate +and the path (an [offsetlist]) which leads to [lexp] as the iterator state. *) +let rearrange pdesc tenv lexp typ prop loc : (Sil.offset list) Prop.prop_iter list = + let nlexp = match Prop.exp_normalize_prop prop lexp with + | Sil.BinOp(Sil.PlusPI, ep, e) -> (* array access with pointer arithmetic *) + Sil.Lindex(ep, e) + | e -> e in + let ptr_tested_for_zero = + Prover.check_disequal prop (Sil.root_of_lexp nlexp) Sil.exp_zero in + let inst = Sil.inst_rearrange (not ptr_tested_for_zero) loc (State.get_path_pos ()) in + L.d_strln ".... Rearrangement Start ...."; + L.d_str "Exp: "; Sil.d_exp nlexp; L.d_ln (); + L.d_str "Prop: "; L.d_ln(); Prop.d_prop prop; L.d_ln (); L.d_ln (); + check_dereference_error pdesc prop nlexp (State.get_loc ()); + let pname = Cfg.Procdesc.get_proc_name pdesc in + match Prop.prop_iter_create prop with + | None -> + if !Config.footprint then + [prop_iter_add_hpred_footprint_to_prop pname tenv prop (nlexp, typ) inst] + else + begin + pp_rearrangement_error "sigma is empty" prop nlexp; + raise (Exceptions.Symexec_memory_error (try assert false with Assert_failure x -> x)) + end + | Some iter -> iter_rearrange pname tenv nlexp typ prop iter inst diff --git a/infer/src/backend/rearrange.mli b/infer/src/backend/rearrange.mli new file mode 100644 index 000000000..6022985db --- /dev/null +++ b/infer/src/backend/rearrange.mli @@ -0,0 +1,25 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Re-arrangement and extension of structures with fresh variables *) (* TODO: this description is not clear *) + +exception ARRAY_ACCESS + +(** Check for dereference errors: dereferencing 0, a freed value, or an undefined value *) +val check_dereference_error : +Cfg.Procdesc.t -> Prop.normal Prop.t -> Sil.exp -> Sil.location -> unit + +(** Check that an expression representing an objc block can be null and raise a [B1] null exception.*) +(** It's used to check that we don't call possibly null blocks *) +val check_call_to_objc_block_error : Cfg.Procdesc.t -> Prop.normal Prop.t -> Sil.exp -> Sil.location -> unit + +(** [rearrange lexp prop] rearranges [prop] into the form [prop' * lexp|->strexp:typ]. +It returns an iterator with [lexp |-> strexp: typ] as current predicate +and the path (an [offsetlist]) which leads to [lexp] as the iterator state. *) +val rearrange : +Cfg.Procdesc.t -> Sil.tenv -> Sil.exp -> +Sil.typ -> Prop.normal Prop.t -> +Sil.location -> (Sil.offset list) Prop.prop_iter list diff --git a/infer/src/backend/reporting.ml b/infer/src/backend/reporting.ml new file mode 100644 index 000000000..482adc829 --- /dev/null +++ b/infer/src/backend/reporting.ml @@ -0,0 +1,48 @@ +(* +* Copyright (c) 2015 - Facebook. +* All rights reserved. +*) + +open Utils +module L = Logging + +type log_issue = + Procname.t -> + ?loc: Sil.location option -> + ?node_id: (int * int) option -> + ?session: int option -> + ?ltr: Errlog.loc_trace option -> + ?pre: Prop.normal Prop.t option -> + exn -> + unit + +let log_issue + err_kind + proc_name + ?(loc = None) + ?(node_id = None) + ?(session = None) + ?(ltr = None) + ?(pre = None) + exn = + match Specs.get_summary proc_name with + | Some summary -> + let err_log = summary.Specs.stats.Specs.err_log in + let loc = match loc with + | None -> State.get_loc () + | Some loc -> loc in + let node_id = match node_id with + | None -> State.get_node_id_key () + | Some node_id -> node_id in + let session = match session with + | None -> State.get_session () + | Some session -> session in + let ltr = match ltr with + | None -> State.get_loc_trace () + | Some ltr -> ltr in + Errlog.log_issue err_kind err_log loc node_id session ltr pre exn + | None -> () + +let log_error = log_issue Exceptions.Kerror +let log_warning = log_issue Exceptions.Kwarning +let log_info = log_issue Exceptions.Kinfo diff --git a/infer/src/backend/reporting.mli b/infer/src/backend/reporting.mli new file mode 100644 index 000000000..66e871dbf --- /dev/null +++ b/infer/src/backend/reporting.mli @@ -0,0 +1,24 @@ +(* +* Copyright (c) 2015 - Facebook. +* All rights reserved. +*) + +(** Type of functions to report issues to the error_log in a spec. *) +type log_issue = + Procname.t -> + ?loc: Sil.location option -> + ?node_id: (int * int) option -> + ?session: int option -> + ?ltr: Errlog.loc_trace option -> + ?pre: Prop.normal Prop.t option -> + exn -> + unit + +(** Report an error in the given procedure. *) +val log_error : log_issue + +(** Report a warning in the given procedure. *) +val log_warning : log_issue + +(** Report an info in the given procedure. *) +val log_info : log_issue diff --git a/infer/src/backend/serialization.ml b/infer/src/backend/serialization.ml new file mode 100644 index 000000000..7d81022af --- /dev/null +++ b/infer/src/backend/serialization.ml @@ -0,0 +1,70 @@ +(* +* Copyright (c) 2009 - 2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +module L = Logging +module F = Format +open Utils + +(** Generic serializer *) +type 'a serializer = (string -> 'a option) * (DB.filename -> 'a option) * (DB.filename -> 'a -> unit) + +(** Serialization key, used to distinguish versions of serializers and avoid assert faults *) +type key = int + +(** current key for tenv, procedure summary, cfg, error trace, call graph *) +let tenv_key, summary_key, cfg_key, trace_key, cg_key, analysis_results_key, cluster_key = (425184201, 160179325, 1062389858, 221487792, 477305409, 799050016, 579094948) + +(** version of the binary files, to be incremented for each change *) +let version = 24 + +(** Generate random keys, to be used in an ocaml toplevel *) +let generate_keys () = + Random.self_init (); + let max_rand_int = 0x3FFFFFFF (* determined by Rand library *) in + let gen () = Random.int max_rand_int in + gen (), gen (), gen (), gen (), gen (), gen () + + +let create_serializer (key : key) : 'a serializer = + let match_data ((key': key), (version': int), (value: 'a)) source_msg = + if key <> key' then + begin + L.err "Wrong key in when loading data from %s@\n" source_msg; + None + end + else if version <> version' then + begin + L.err "Wrong version in when loading data from %s@\n" source_msg; + None + end + else Some value in + let from_string (str : string) : 'a option = + try + match_data (Marshal.from_string str 0) "string" + with Sys_error s -> None in + let from_file (_fname : DB.filename) : 'a option = + try + let fname = DB.filename_to_string _fname in + let inc = open_in_bin fname in + let value_option = match_data (Marshal.from_channel inc) fname in + close_in inc; + value_option + with Sys_error s -> None in + let to_file (fname : DB.filename) (value : 'a) = + let outc = open_out_bin (DB.filename_to_string fname) in + Marshal.to_channel outc (key, version, value) []; + close_out outc in + (from_string, from_file, to_file) + + +let from_string (serializer : 'a serializer) = + let (s, _, _) = serializer in s + +let from_file (serializer : 'a serializer) = + let (_, s, _) = serializer in s + +let to_file (serializer : 'a serializer) = + let (_, _, s) = serializer in s diff --git a/infer/src/backend/serialization.mli b/infer/src/backend/serialization.mli new file mode 100644 index 000000000..9e79a15a6 --- /dev/null +++ b/infer/src/backend/serialization.mli @@ -0,0 +1,48 @@ +(* +* Copyright (c) 2009-2013 Monoidics ltd. +* Copyright (c) 2013- Facebook. +* All rights reserved. +*) + +(** Serialization of data stuctures *) + +open Utils + +(** Generic serializer *) +type 'a serializer + +(** Serialization key, used to distinguish versions of serializers and avoid assert faults *) +type key + +(** current key for tenv *) +val tenv_key : key + +(** current key for a procedure summary *) +val summary_key : key + +(** current key for a cfg *) +val cfg_key : key + +(** current key for an error trace *) +val trace_key : key + +(** current key for a call graph *) +val cg_key : key + +(** current key for a cluster *) +val cluster_key : key + +(** current key for an analysis results value *) +val analysis_results_key : key + +(** create a serializer from a file name, given an integer key used as double-check of the file type *) +val create_serializer : key -> 'a serializer + +(** extract a from_string function from a serializer *) +val from_string : 'a serializer -> string -> 'a option + +(** extract a from_file function from a serializer *) +val from_file : 'a serializer -> DB.filename -> 'a option + +(** extract a to_file function from a serializer *) +val to_file : 'a serializer -> DB.filename -> 'a -> unit diff --git a/infer/src/backend/sil.ml b/infer/src/backend/sil.ml new file mode 100644 index 000000000..bfccc815a --- /dev/null +++ b/infer/src/backend/sil.ml @@ -0,0 +1,3852 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** The Smallfoot Intermediate Language *) + +module L = Logging +module F = Format +open Utils + +(** {2 Programs and Types} *) + +(** Type to represent one @Annotation. *) +type annotation = + { class_name: string; (* name of the annotation *) + parameters: string list; (* currently only one string parameter *) } + +(** Annotation for one item: a list of annotations with visibility. *) +type item_annotation = (annotation * bool) list + +(** Annotation for a method: return value and list of parameters. *) +type method_annotation = + item_annotation * item_annotation list + +(** Programming language. *) +type language = C_CPP | Java + +(** Visibility modifiers. *) +type access = Default | Public | Private | Protected + +(** Attributes of a procedure. *) +type proc_attributes = + { + access : access; (** visibility access *) + exceptions : string list; (** exceptions thrown by the procedure *) + is_abstract : bool; (** the procedure is abstract *) + mutable is_bridge_method : bool; (** the procedure is a bridge method *) + is_objc_instance_method : bool; (** the procedure is an objective-C instance method *) + mutable is_synthetic_method : bool; (** the procedure is a synthetic method *) + language : language; + method_annotation : method_annotation; + } + +let copy_proc_attributes pa = + { + access = pa.access; + exceptions = pa.exceptions; + is_abstract = pa.is_abstract; + is_bridge_method = pa.is_bridge_method; + is_objc_instance_method = pa.is_objc_instance_method; + is_synthetic_method = pa.is_synthetic_method; + language = pa.language; + method_annotation = pa.method_annotation; + } + +(** Compare function for annotations. *) +let annotation_compare a1 a2 = + let n = string_compare a1.class_name a2.class_name in + if n <> 0 then n else list_compare string_compare a1.parameters a2.parameters + +(** Compare function for annotation items. *) +let item_annotation_compare ia1 ia2 = + let cmp (a1, b1) (a2, b2) = + let n = annotation_compare a1 a2 in + if n <> 0 then n else bool_compare b1 b2 in + list_compare cmp ia1 ia2 + +(** Compare function for Method annotations. *) +let method_annotation_compare (ia1, ial1) (ia2, ial2) = + list_compare item_annotation_compare (ia1 :: ial1) (ia2 :: ial2) + +(** Empty item annotation. *) +let item_annotation_empty = [] + +(** Empty method annotation. *) +let method_annotation_empty = [], [] + +(** Check if the item annotation is empty. *) +let item_annotation_is_empty avl = + avl = [] + +(** Check if the item annodation is empty. *) +let item_annotation_is_empty ia = ia = [] + +(** Check if the method annodation is empty. *) +let method_annotation_is_empty (ia, ial) = + list_for_all item_annotation_is_empty (ia :: ial) + +(** Pretty print an annotation. *) +let pp_annotation fmt annotation = F.fprintf fmt "@@%s" annotation.class_name + +(** Pretty print an item annotation. *) +let pp_item_annotation fmt item_annotation = + let pp fmt (a, v) = pp_annotation fmt a in + F.fprintf fmt "<%a>" (pp_seq pp) item_annotation + +(** Pretty print a method annotation. *) +let pp_method_annotation s fmt (ia, ial) = + F.fprintf fmt "%a %s(%a)" pp_item_annotation ia s (pp_seq pp_item_annotation) ial + +(** current language *) +let curr_language = ref C_CPP + +(** Class, struct, union, (Obj C) protocol *) +type csu = + | Class + | Struct + | Union + | Protocol + +(** Named types. *) +type typename = + | TN_typedef of Mangled.t + | TN_enum of Mangled.t + | TN_csu of csu * Mangled.t + +(** Location in the original source file *) +type location = { + line: int; (** The line number. -1 means "do not know" *) + col: int; (** The column number. -1 means "do not know" *) + file: DB.source_file; (** The name of the source file *) + nLOC : int; (** Lines of code in the source file *) +} + +let dummy_location = { + line = -1; + col = -1; + file = DB.source_file_empty; + nLOC = -1 +} + +(** Kind of global variables *) +type pvar_kind = + | Local_var of Procname.t (** local variable belonging to a function *) + | Callee_var of Procname.t (** local variable belonging to a callee *) + | Abducted_retvar of Procname.t * location (** synthetic variable to represent return value *) + | Global_var (** gloval variable *) + | Seed_var (** variable used to store the initial value of formal parameters *) + +(** Names for program variables. *) +type pvar = { pv_name: Mangled.t; pv_kind: pvar_kind } + +(** Unary operations *) +type unop = + | Neg (** Unary minus *) + | BNot (** Bitwise complement (~) *) + | LNot (** Logical Not (!) *) + +(** Binary operations *) +type binop = + | PlusA (** arithmetic + *) + | PlusPI (** pointer + integer *) + | MinusA (** arithmetic - *) + | MinusPI (** pointer - integer *) + | MinusPP (** pointer - pointer *) + | Mult (** * *) + | Div (** / *) + | Mod (** % *) + | Shiftlt (** shift left *) + | Shiftrt (** shift right *) + + | Lt (** < (arithmetic comparison) *) + | Gt (** > (arithmetic comparison) *) + | Le (** <= (arithmetic comparison) *) + | Ge (** > (arithmetic comparison) *) + | Eq (** == (arithmetic comparison) *) + | Ne (** != (arithmetic comparison) *) + | BAnd (** bitwise and *) + | BXor (** exclusive-or *) + | BOr (** inclusive-or *) + + | LAnd (** logical and. Does not always evaluate both operands. *) + | LOr (** logical or. Does not always evaluate both operands. *) + | PtrFld (** field offset via pointer to field: takes the address of a csu and a Cptr_to_fld constant to form an Lfield expression (see prop.ml) *) + +(** Kinds of integers *) +type ikind = + IChar (** [char] *) + | ISChar (** [signed char] *) + | IUChar (** [unsigned char] *) + | IBool (** [bool] *) + | IInt (** [int] *) + | IUInt (** [unsigned int] *) + | IShort (** [short] *) + | IUShort (** [unsigned short] *) + | ILong (** [long] *) + | IULong (** [unsigned long] *) + | ILongLong (** [long long] (or [_int64] on Microsoft Visual C) *) + | IULongLong (** [unsigned long long] (or [unsigned _int64] on Microsoft Visual C) *) + | I128 (** [__int128_t] *) + | IU128 (** [__uint128_t] *) + +(** Kinds of floating-point numbers*) +type fkind = + | FFloat (** [float] *) + | FDouble (** [double] *) + | FLongDouble (** [long double] *) + +type mem_kind = + | Mmalloc (** memory allocated with malloc *) + | Mnew (** memory allocated with new *) + | Mnew_array (** memory allocated with new[] *) + | Mobjc (** memory allocated with objective-c alloc *) + +(** resource that can be allocated *) +type resource = + | Rmemory of mem_kind + | Rfile + | Rignore + | Rlock + +(** kind of resource action *) +type res_act_kind = + | Racquire + | Rrelease + +(** kind of dangling pointers *) +type dangling_kind = + | DAuninit (** pointer is dangling because it is uninitialized *) + | DAaddr_stack_var (** pointer is dangling because it is the address of a stack variable which went out of scope *) + | DAminusone (** pointer is -1 *) + +(** kind of pointer *) +type ptr_kind = + | Pk_pointer (* C/C++, Java, Objc standard/__strong pointer*) + | Pk_reference (* C++ reference *) + | Pk_objc_weak (* Obj-C __weak pointer*) + | Pk_objc_unsafe_unretained (* Obj-C __unsafe_unretained pointer *) + | Pk_objc_autoreleasing (* Obj-C __autoreleasing pointer *) + +(** position in a path: proc name, node id *) +type path_pos = Procname.t * int + +(** module for subtypes, to be used with Sizeof info *) +module Subtype = struct + + let list_to_string list = + let rec aux list = + match list with + | [] -> "" + | el:: rest -> + let s = (aux rest) in + if (s = "") then (Mangled.to_string el) + else (Mangled.to_string el)^", "^s in + if (list_length list = 0) then "( sub )" + else ("- {"^(aux list)^"}") + + let list_equal list1 list2 = + if (list_length list1 = list_length list2) then + list_for_all2 Mangled.equal list1 list2 + else false + + type t' = + | Exact (** denotes the current type only *) + | Subtypes of Mangled.t list(** denotes the current type and a list of types that are not their subtypes *) + + type kind = + | CAST + | INSTOF + | NORMAL + + type t = t' * kind + + module SubtypesPair = struct + type t = (Mangled.t * Mangled.t) + + let compare (e1 : t)(e2 : t) : int = + pair_compare Mangled.compare Mangled.compare e1 e2 + end + + module SubtypesMap = Map.Make (SubtypesPair) + + type subtMap = bool SubtypesMap.t + + let subtMap = ref SubtypesMap.empty + + let check_subtype f c1 c2 = + try + SubtypesMap.find (c1, c2) !subtMap + with Not_found -> + let is_subt = f c1 c2 in + subtMap := (SubtypesMap.add (c1, c2) is_subt !subtMap); + is_subt + + let flag_to_string flag = + match flag with + | CAST -> "(cast)" + | INSTOF -> "(instof)" + | NORMAL -> "" + + let pp f (t, flag) = + match t with + | Exact -> if !Config.print_types then F.fprintf f "%s" (flag_to_string flag) + | Subtypes list -> if !Config.print_types then F.fprintf f "%s" ((list_to_string list)^(flag_to_string flag)) + + let exact = Exact, NORMAL + let all_subtypes = Subtypes [] + let subtypes = all_subtypes, NORMAL + let subtypes_cast = all_subtypes, CAST + let subtypes_instof = all_subtypes, INSTOF + + let is_cast t = snd t = CAST + + let is_instof t = snd t = INSTOF + + let list_intersect equal l1 l2 = + let in_l2 a = list_mem equal a l2 in + list_filter in_l2 l1 + + let join_flag flag1 flag2 = + match flag1, flag2 with + | CAST, _ -> CAST + | _, CAST -> CAST + | _, _ -> NORMAL + + let join (s1, flag1) (s2, flag2) = + let s = + match s1, s2 with + | Exact, _ -> s2 + | _, Exact -> s1 + | Subtypes l1, Subtypes l2 -> Subtypes (list_intersect Mangled.equal l1 l2) in + let flag = join_flag flag1 flag2 in + s, flag + + let subtypes_compare l1 l2 = + list_compare Mangled.compare l1 l2 + + let compare_flag flag1 flag2 = + match flag1, flag2 with + | CAST, CAST -> 0 + | INSTOF, INSTOF -> 0 + | NORMAL, NORMAL -> 0 + | CAST, _ -> -1 + | _, CAST -> 1 + | INSTOF, NORMAL -> -1 + | NORMAL, INSTOF -> 1 + + let compare_subt s1 s2 = + match s1, s2 with + | Exact, Exact -> 0 + | Exact, _ -> -1 + | _, Exact -> 1 + | Subtypes l1, Subtypes l2 -> + subtypes_compare l1 l2 + + let compare t1 t2 = + pair_compare compare_subt compare_flag t1 t2 + + let equal_modulo_flag (st1, flag1) (st2, flag2) = + compare_subt st1 st2 = 0 + + let update_flag c1 c2 flag flag' = + match flag with + | INSTOF -> + if (Mangled.equal c1 c2) then flag else flag' + | _ -> flag' + + let change_flag st_opt c1 c2 flag' = + match st_opt with + | Some st -> + (match st with + | Exact, flag -> + let new_flag = update_flag c1 c2 flag flag' in + Some (Exact, new_flag) + | Subtypes t, flag -> + let new_flag = update_flag c1 c2 flag flag' in + Some (Subtypes t, new_flag)) + | None -> None + + let normalize_subtypes t_opt c1 c2 flag1 flag2 = + let new_flag = update_flag c1 c2 flag1 flag2 in + match t_opt with + | Some t -> + (match t with + | Exact -> Some (t, new_flag) + | Subtypes l -> + Some (Subtypes (list_sort Mangled.compare l), new_flag)) + | None -> None + + let subtypes_to_string t = + match fst t with + | Exact -> "ex"^(flag_to_string (snd t)) + | Subtypes l -> (list_to_string l)^(flag_to_string (snd t)) + + (* c is a subtype when it does not appear in the list l of no-subtypes *) + let is_subtype f c l = + try ignore( list_find (f c) l); false + with Not_found -> true + + let is_strict_subtype f c1 c2 = + f c1 c2 && not (Mangled.equal c1 c2) + + (* checks for redundancies when adding c to l + Xi in A - { X1,..., Xn } is redundant in two cases: + 1) not (Xi <: A) because removing the subtypes of Xi has no effect unless Xi is a subtype of A + 2) Xi <: Xj because the subtypes of Xi are a subset of the subtypes of Xj *) + let check_redundancies f c l = + let aux (l, add) ci = + let l, should_add = + if (f ci c) then (l, true) + else if (f c ci) then (ci:: l, false) + else (ci:: l, true) in + l, (add && should_add) in + (list_fold_left aux ([], true) l) + + let rec updates_head f c l = + match l with + | [] -> [] + | ci:: rest -> + if (is_strict_subtype f ci c) then ci:: (updates_head f c rest) + else (updates_head f c rest) + + (* adds the classes of l2 to l1 and checks that no redundancies or inconsistencies will occur + A - { X1,..., Xn } is inconsistent if A <: Xi for some i *) + let rec add_not_subtype f c1 l1 l2 = + match l2 with + | [] -> l1 + | c:: rest -> + if (f c1 c) then (add_not_subtype f c1 l1 rest) (* checks for inconsistencies *) + else + let l1', should_add = (check_redundancies f c l1) in (* checks for redundancies *) + let rest' = (add_not_subtype f c1 l1' rest) in + if (should_add) then c:: rest' else rest' + + let get_subtypes (c1, (st1, flag1)) (c2, (st2, flag2)) f is_interface = + (* (L.d_strln_color Orange ((Mangled.to_string c1)^(subtypes_to_string (st1, flag1))^" <: "^ + (Mangled.to_string c2)^(subtypes_to_string (st2, flag2))^" ?"^(string_of_bool is_sub)));*) + let is_sub = f c1 c2 in + let pos_st, neg_st = match st1, st2 with + | Exact, Exact -> + if (is_sub) then (Some st1, None) + else (None, Some st1) + | Exact, Subtypes l2 -> + if is_sub && (is_subtype f c1 l2) then (Some st1, None) + else (None, Some st1) + | Subtypes l1, Exact -> + if (is_sub) then (Some st1, None) + else + let l1' = updates_head f c2 l1 in + if (is_subtype f c2 l1) then (Some (Subtypes l1'), Some (Subtypes (add_not_subtype f c1 l1 [c2]))) + else (None, Some st1) + | Subtypes l1, Subtypes l2 -> + if (is_interface c2) || (is_sub) then + if (is_subtype f c1 l2) then + let l2' = updates_head f c1 l2 in + (Some (Subtypes (add_not_subtype f c1 l1 l2')), None) + else (None, Some st1) + else if ((is_interface c1) || (f c2 c1)) && (is_subtype f c2 l1) then + let l1' = updates_head f c2 l1 in + (Some (Subtypes (add_not_subtype f c2 l1' l2)), Some (Subtypes (add_not_subtype f c1 l1 [c2]))) + else (None, Some st1) in + (normalize_subtypes pos_st c1 c2 flag1 flag2), (normalize_subtypes neg_st c1 c2 flag1 flag2) + + let case_analysis_basic (c1, st) (c2, (st2, flag2)) f = + let (pos_st, neg_st) = + if f c1 c2 then (Some st, None) + else if f c2 c1 then + match st with + | Exact, flag -> + if Mangled.equal c1 c2 + then (Some st, None) + else (None, Some st) + | Subtypes _ , flag -> + if Mangled.equal c1 c2 + then (Some st, None) + else (Some st, Some st) + else (None, Some st) in + (change_flag pos_st c1 c2 flag2), (change_flag neg_st c1 c2 flag2) + + (** [case_analysis (c1, st1) (c2,st2) f] performs case analysis on [c1 <: c2] according to [st1] and [st2] + where f c1 c2 is true if c1 is a subtype of c2. + get_subtypes returning a pair: + - whether [st1] and [st2] admit [c1 <: c2], and in case return the updated subtype [st1] + - whether [st1] and [st2] admit [not(c1 <: c2)], and in case return the updated subtype [st1] *) + let case_analysis (c1, st1) (c2, st2) f is_interface = + let f = check_subtype f in + if (!Config.subtype_multirange) then + get_subtypes (c1, st1) (c2, st2) f is_interface + else case_analysis_basic (c1, st1) (c2, st2) f + +end + +(** module for signed and unsigned integers *) +module Int : sig + type t + val add : t -> t -> t + val compare : t -> t -> int + val compare_value : t -> t -> int + val div : t -> t -> t + val eq : t -> t -> bool + val of_int : int -> t + val of_int32 : int32 -> t + val of_int64 : int64 -> t + val of_int64_unsigned : int64 -> bool -> t + val geq : t -> t -> bool + val gt : t -> t -> bool + val isminusone : t -> bool + val isone : t -> bool + val isnegative : t -> bool + val isnull : t -> bool + val iszero : t -> bool + val leq : t -> t -> bool + val logand : t -> t -> t + val lognot : t -> t + val logor : t -> t -> t + val logxor : t -> t -> t + val lt : t -> t -> bool + val minus_one : t + val mul : t -> t -> t + val neg : t -> t + val neq : t -> t -> bool + val null : t + val one : t + val pp : Format.formatter -> t -> unit + val rem : t -> t -> t + val sub : t -> t -> t + val to_int : t -> int + val to_signed : t -> t option + val to_string : t -> string + val two : t + val zero : t +end = struct + (* the first bool indicates whether this is an unsigned value, and the second whether it is a pointer *) + type t = bool * Int64.t * bool + + let area u i = match i < 0L, u with + | true, false -> 1 (* only representable as signed *) + | false, _ -> 2 (* in the intersection between signed and unsigned *) + | true, true -> 3 (* only representable as unsigned *) + + let to_signed (unsigned, i, ptr) = + if area unsigned i = 3 then None (* not representable as signed *) + else Some (false, i, ptr) + + let compare (unsigned1, i1, ptr1) (unsigned2, i2, ptr2) = + let n = bool_compare unsigned1 unsigned2 in + if n <> 0 then n else Int64.compare i1 i2 + + let compare_value (unsigned1, i1, ptr1) (unsigned2, i2, ptr2) = + let area1 = area unsigned1 i1 in + let area2 = area unsigned2 i2 in + let n = int_compare area1 area2 in + if n <> 0 then n else Int64.compare i1 i2 + + let eq i1 i2 = compare_value i1 i2 = 0 + let neq i1 i2 = compare_value i1 i2 <> 0 + let leq i1 i2 = compare_value i1 i2 <= 0 + let lt i1 i2 = compare_value i1 i2 < 0 + let geq i1 i2 = compare_value i1 i2 >= 0 + let gt i1 i2 = compare_value i1 i2 > 0 + + let of_int64 i = (false, i, false) + let of_int32 i = of_int64 (Int64.of_int32 i) + let of_int64_unsigned i unsigned = (unsigned, i, false) + let of_int i = of_int64 (Int64.of_int i) + let to_int (large, i, ptr) = Int64.to_int i + let null = (false, 0L, true) + let zero = of_int 0 + let one = of_int 1 + let two = of_int 2 + let minus_one = of_int (-1) + + let isone (_, i, ptr) = i = 1L + let iszero (_, i, ptr) = i = 0L + let isnull (_, i, ptr) = i = 0L && ptr + let isminusone (unsigned, i, ptr) = not unsigned && i = -1L + let isnegative (unsigned, i, ptr) = not unsigned && i < 0L + + let neg (unsigned, i, ptr) = (unsigned, Int64.neg i, ptr) + + let lift binop (unsigned1, i1, ptr1) (unsigned2, i2, ptr2) = (unsigned1 || unsigned2, binop i1 i2, ptr1 || ptr2) + + let lift1 unop (unsigned, i, ptr) = (unsigned, unop i, ptr) + + let add i1 i2 = lift Int64.add i1 i2 + + let mul i1 i2 = lift Int64.mul i1 i2 + + let div i1 i2 = lift Int64.div i1 i2 + + let rem i1 i2 = lift Int64.rem i1 i2 + + let logand i1 i2 = lift Int64.logand i1 i2 + + let logor i1 i2 = lift Int64.logor i1 i2 + + let logxor i1 i2 = lift Int64.logxor i1 i2 + + let lognot i = lift1 Int64.lognot i + + let sub i1 i2 = add i1 (neg i2) + + let pp f (unsigned, n, ptr) = + if ptr && n = 0L then F.fprintf f "null" else + if unsigned then F.fprintf f "%Lu" n + else F.fprintf f "%Ld" n + + let to_string i = + Utils.pp_to_string pp i +end + +(** Flags for a procedure call *) +type call_flags = { + cf_virtual : bool; + cf_noreturn : bool; + cf_is_objc_block : bool; +} + +let cf_default = { cf_virtual = false; cf_noreturn = false; cf_is_objc_block = false; } + +(** expression representing the result of decompilation *) +type dexp = + | Darray of dexp * dexp + | Dbinop of binop * dexp * dexp + | Dconst of const + | Dsizeof of typ * Subtype.t + | Dderef of dexp + | Dfcall of dexp * dexp list * location * call_flags + | Darrow of dexp * Ident.fieldname + | Ddot of dexp * Ident.fieldname + | Dpvar of pvar + | Dpvaraddr of pvar + | Dunop of unop * dexp + | Dunknown + | Dretcall of dexp * dexp list * location * call_flags + +(** Value paths: identify an occurrence of a value in a symbolic heap +each expression represents a path, with Dpvar being the simplest one *) +and vpath = + dexp option + +(** acquire/release action on a resource *) +and res_action = + { ra_kind : res_act_kind; (** kind of action *) + ra_res : resource; (** kind of resource *) + ra_pname : Procname.t; (** name of the procedure used to acquire/release the resource *) + ra_loc : location; (** location of the acquire/release *) + ra_vpath: vpath; (** vpath of the resource value *) + } + +(** Attributes *) +and attribute = + | Aresource of res_action (** resource acquire/release *) + | Aautorelease + | Adangling of dangling_kind (** dangling pointer *) + | Aundef of Procname.t * location * path_pos (** undefined value obtained by calling the given procedure *) + | Ataint + | Auntaint + | Adiv0 of path_pos (** value appeared in second argument of division in path position *) + | Aobjc_null of exp (** the exp. is null because of a call to a method with exp as a null receiver *) + | Avariadic_function_argument of Procname.t * int * int (** (pn, n, i) the exp. is used as [i]th + argument of a call to the variadic + function [pn] that has [n] arguments *) + | Aretval of Procname.t (** value was returned from a call to the given procedure *) + +(** Categories of attributes *) +and attribute_category = + | ACresource + | ACautorelease + | ACtaint + | ACdiv0 + | ACobjc_null + | ACvariadic_function_argument + +(** Constants *) +and const = + | Cint of Int.t (** integer constants *) + | Cfun of Procname.t (** function names *) + | Cstr of string (** string constants *) + | Cfloat of float (** float constants *) + | Cattribute of attribute (** attribute used in disequalities to annotate a value *) + | Cexn of exp (** exception *) + | Cclass of Ident.name (** class constant *) + | Cptr_to_fld of Ident.fieldname * typ (** pointer to field constant, and type of the surrounding csu type *) + | Ctuple of exp list (** tuple of values *) + +and struct_fields = (Ident.fieldname * typ * item_annotation) list + +(** types for sil (structured) expressions *) +and typ = + | Tvar of typename (** named type *) + | Tint of ikind (** integer type *) + | Tfloat of fkind (** float type *) + | Tvoid (** void type *) + | Tfun of bool (** function type with noreturn attribute *) + | Tptr of typ * ptr_kind (** pointer type *) + | Tstruct of struct_fields * struct_fields * csu * Mangled.t option * (csu * Mangled.t) list * Procname.t list * item_annotation (** structure type with class/struct/union flag and name and list of superclasses *) + (** Structure type with nonstatic and static fields, class/struct/union flag, name, list of superclasses, + methods defined, and annotations. + The fld - typ pairs are always sorted. This means that we don't support programs that exploit specific layouts + of C structs. *) + | Tarray of typ * exp (** array type with fixed size *) + | Tenum of (Mangled.t * const) list + +(** program expressions *) +and exp = + | Var of Ident.t (** pure variable: it is not an lvalue *) + | UnOp of unop * exp * typ option (** unary operator with type of the result if known *) + | BinOp of binop * exp * exp (** binary operator *) + | Const of const (** constants *) + | Cast of typ * exp (** type cast *) + | Lvar of pvar (** the address of a program variable *) + | Lfield of exp * Ident.fieldname * typ (** a field offset, the type is the surrounding struct type *) + | Lindex of exp * exp (** an array index offset: exp1[exp2] *) + | Sizeof of typ * Subtype.t (** a sizeof expression *) + +(** Unknown location *) +let loc_none = + { line = - 1; col = - 1; file = DB.source_file_empty; nLOC = 0 } + +(** Kind of prune instruction *) +type if_kind = + | Ik_bexp (* boolean expressions, and exp ? exp : exp *) + | Ik_dowhile + | Ik_for + | Ik_if + | Ik_land_lor (* obtained from translation of && or || *) + | Ik_while + | Ik_switch + +(** Stack operation for symbolic execution on propsets *) +type stackop = + | Push (* copy the curreny propset to the stack *) + | Swap (* swap the current propset and the top of the stack *) + | Pop (* pop the stack and combine with the current propset *) + +(** An instruction. *) +type instr = + | Letderef of Ident.t * exp * typ * location (** declaration [let x = *lexp:typ] where [typ] is the root type of [lexp] *) + | Set of exp * typ * exp * location (** assignment [*lexp1:typ = exp2] where [typ] is the root type of [lexp1] *) + | Prune of exp * location * bool * if_kind (** prune the state based on [exp=1], the boolean indicates whether true branch *) + | Call of Ident.t list * exp * (exp * typ) list * location * call_flags + (** [Call (ret_id1..ret_idn, e_fun, arg_ts, loc, call_flags)] represents an instructions + [ret_id1..ret_idn = e_fun(arg_ts);] where n = 0 for void return and n > 1 for struct return *) + | Nullify of pvar * location * bool (** nullify stack variable, the bool parameter indicates whether to deallocate the variable *) + | Abstract of location (** apply abstraction *) + | Remove_temps of Ident.t list * location (** remove temporaries *) + | Stackop of stackop * location (** operation on the stack of propsets *) + | Declare_locals of (pvar * typ) list * location (** declare local variables *) + | Goto_node of exp * location (** jump to a specific cfg node, assuming all the possible target nodes are successors of the current node *) + +(** Check if an instruction is auxiliary, or if it comes from source instructions. *) +let instr_is_auxiliary = function + | Letderef _ | Set _ | Prune _ | Call _ | Goto_node _ -> + false + | Nullify _ | Abstract _ | Remove_temps _ | Stackop _ | Declare_locals _ -> + true + +(** offset for an lvalue *) +type offset = Off_fld of Ident.fieldname * typ | Off_index of exp + +(** {2 Components of Propositions} *) + +(** an atom is a pure atomic formula *) +type atom = + | Aeq of exp * exp (** equality *) + | Aneq of exp * exp (** disequality*) + +(** kind of lseg or dllseg predicates *) +type lseg_kind = + | Lseg_NE (** nonempty (possibly circular) listseg *) + | Lseg_PE (** possibly empty (possibly circular) listseg *) + +(** The boolean is true when the pointer was dereferenced without testing for zero. *) +type zero_flag = bool option + +(** True when the value was obtained by doing case analysis on null in a procedure call. *) +type null_case_flag = bool + +(** instrumentation of heap values *) +type inst = + | Iabstraction + | Iactual_precondition + | Ialloc + | Iformal of zero_flag * null_case_flag + | Iinitial + | Ilookup + | Inone + | Inullify + | Irearrange of zero_flag * null_case_flag * int * path_pos + | Itaint + | Iupdate of zero_flag * null_case_flag * int * path_pos + | Ireturn_from_call of int + +(** structured expressions represent a value of structured type, such as an array or a struct. *) +type strexp = + | Eexp of exp * inst (** Base case: expression with instrumentation *) + | Estruct of (Ident.fieldname * strexp) list * inst (** C structure *) + | Earray of exp * (exp * strexp) list * inst (** Array of given size. *) +(** There are two conditions imposed / used in the array case. +First, if some index and value pair appears inside an array +in a strexp, then the index is less than the size of the array. +For instance, x |->[10 | e1: v1] implies that e1 <= 9. +Second, if two indices appear in an array, they should be different. +For instance, x |->[10 | e1: v1, e2: v2] implies that e1 != e2. *) + +(** an atomic heap predicate *) +and hpred = + | Hpointsto of exp * strexp * exp + (** represents [exp|->strexp:typexp] where [typexp] + is an expression representing a type, e.h. [sizeof(t)]. *) + | Hlseg of lseg_kind * hpara * exp * exp * exp list + (** higher - order predicate for singly - linked lists. + Should ensure that exp1!= exp2 implies that exp1 is allocated. + This assumption is used in the rearrangement. The last [exp list] parameter + is used to denote the shared links by all the nodes in the list. *) + | Hdllseg of lseg_kind * hpara_dll * exp * exp * exp * exp * exp list +(** higher-order predicate for doubly-linked lists. *) + +(** parameter for the higher-order singly-linked list predicate. +Means "lambda (root,next,svars). Exists evars. body". +Assume that root, next, svars, evars are disjoint sets of +primed identifiers, and include all the free primed identifiers in body. +body should not contain any non - primed identifiers or program +variables (i.e. pvars). *) +and hpara = + { root: Ident.t; + next: Ident.t; + svars: Ident.t list; + evars: Ident.t list; + body: hpred list } + +(** parameter for the higher-order doubly-linked list predicates. +Assume that all the free identifiers in body_dll should belong to +cell, blink, flink, svars_dll, evars_dll. *) +and hpara_dll = + { cell: Ident.t; (** address cell *) + blink: Ident.t; (** backward link *) + flink: Ident.t; (** forward link *) + svars_dll: Ident.t list; + evars_dll: Ident.t list; + body_dll: hpred list } + +(** Return the lhs expression of a hpred *) +let hpred_get_lhs h = + match h with + | Hpointsto (e, _, _) + | Hlseg(_, _, e, _, _) + | Hdllseg(_, _, e, _, _, _, _) -> e + +let objc_ref_counter_annot = + [({ class_name = "ref_counter"; parameters = []}, false)] + +(** Field used for objective-c reference counting *) +let objc_ref_counter_field = + (Ident.fieldname_hidden, Tint IInt, objc_ref_counter_annot) + +(** {2 Comparision and Inspection Functions} *) + +let is_objc_ref_counter_field (fld, t, a) = + Ident.fieldname_is_hidden fld && (item_annotation_compare a objc_ref_counter_annot = 0) + +let has_objc_ref_counter hpred = + match hpred with + | Hpointsto(_, _, Sizeof(Tstruct(fl, _, _, _, _, _, _), _)) -> + list_exists is_objc_ref_counter_field fl + | _ -> false + +let pvar_get_name pv = pv.pv_name + +let pvar_to_string pv = Mangled.to_string pv.pv_name + +let pvar_get_simplified_name pv = + let s = Mangled.to_string pv.pv_name in + match string_split_character s '.' with + | Some s1, s2 -> + (match string_split_character s1 '.' with + | Some s3, s4 -> s4 ^ "." ^ s2 + | _ -> s) + | _ -> s + +(** Turn a pvar into a seed pvar (which stored the initial value) *) +let pvar_to_seed pv = { pv with pv_kind = Seed_var } + +(** Check if the pvar is a local var *) +let pvar_is_local pv = + match pv.pv_kind with + | Local_var _ -> true + | _ -> false + +(** Check if the pvar is a callee var *) +let pvar_is_callee pv = + match pv.pv_kind with + | Callee_var _ -> true + | _ -> false + +(** Check if the pvar is an abucted return var *) +let pvar_is_abducted_retvar pv = + match pv.pv_kind with + | Abducted_retvar _ -> true + | _ -> false + +(** Check if the pvar is a seed var *) +let pvar_is_seed pv = + match pv.pv_kind with + | Seed_var -> true + | _ -> false + +(** Check if the pvar is a global var *) +let pvar_is_global pv = + pv.pv_kind = Global_var + +(** Check if a pvar is the special "this" var *) +let pvar_is_this pvar = + Mangled.equal (pvar_get_name pvar) (Mangled.from_string "this") + +(** Check if the pvar is a return var *) +let pvar_is_return pv = + pvar_get_name pv = Ident.name_return + +(** Make a static local name in objc *) +let mk_static_local_name pname vname = + pname^"_"^vname + +(** Check if a pvar is a local static in objc *) +let is_static_local_name pname pvar = (* local static name is of the form procname_varname *) + let var_name = Mangled.to_string(pvar_get_name pvar) in + match Str.split_delim (Str.regexp_string pname) var_name with + | [s1; s2] -> true + | _ -> false + +let loc_compare loc1 loc2 = + let n = int_compare loc1.line loc2.line in + if n <> 0 then n else DB.source_file_compare loc1.file loc2.file + +let loc_equal loc1 loc2 = + loc_compare loc1 loc2 = 0 + +let pv_kind_compare k1 k2 = match k1, k2 with + | Local_var n1, Local_var n2 -> Procname.compare n1 n2 + | Local_var _, _ -> - 1 + | _, Local_var _ -> 1 + | Callee_var n1, Callee_var n2 -> Procname.compare n1 n2 + | Callee_var _, _ -> - 1 + | Abducted_retvar (p1, l1), Abducted_retvar (p2, l2) -> + let n = Procname.compare p1 p2 in + if n <> 0 then n else loc_compare l1 l2 + | Abducted_retvar _, _ -> - 1 + | _, Abducted_retvar _ -> 1 + | _, Callee_var _ -> 1 + | Global_var, Global_var -> 0 + | Global_var, _ -> - 1 + | _, Global_var -> 1 + | Seed_var, Seed_var -> 0 + +let pvar_compare pv1 pv2 = + let n = Mangled.compare pv1.pv_name pv2.pv_name in + if n <> 0 then n else pv_kind_compare pv1.pv_kind pv2.pv_kind + +let pvar_equal pvar1 pvar2 = + pvar_compare pvar1 pvar2 = 0 + +let fld_compare (fld1 : Ident.fieldname) fld2 = Ident.fieldname_compare fld1 fld2 + +let fld_equal fld1 fld2 = fld_compare fld1 fld2 = 0 + +let exp_is_null_literal = function + | Const (Cint n) -> Int.isnull n + | _ -> false + +let exp_is_this = function + | Lvar pvar -> pvar_is_this pvar + | _ -> false + +let ikind_is_char = function + | IChar | ISChar | IUChar -> true + | _ -> false + +let ikind_is_unsigned = function + | IUChar | IUInt | IUShort | IULong | IULongLong -> true + | _ -> false + +let int_of_int64_kind i ik = + Int.of_int64_unsigned i (ikind_is_unsigned ik) + +let unop_compare o1 o2 = match o1, o2 with + | Neg, Neg -> 0 + | Neg, _ -> - 1 + | _, Neg -> 1 + | BNot, BNot -> 0 + | BNot, _ -> - 1 + | _, BNot -> 1 + | LNot, LNot -> 0 + +let unop_equal o1 o2 = unop_compare o1 o2 = 0 + +let binop_compare o1 o2 = match o1, o2 with + | PlusA, PlusA -> 0 + | PlusA, _ -> - 1 + | _, PlusA -> 1 + | PlusPI, PlusPI -> 0 + | PlusPI, _ -> - 1 + | _, PlusPI -> 1 + | MinusA, MinusA -> 0 + | MinusA, _ -> - 1 + | _, MinusA -> 1 + | MinusPI, MinusPI -> 0 + | MinusPI, _ -> - 1 + | _, MinusPI -> 1 + | MinusPP, MinusPP -> 0 + | MinusPP, _ -> - 1 + | _, MinusPP -> 1 + | Mult, Mult -> 0 + | Mult, _ -> - 1 + | _, Mult -> 1 + | Div, Div -> 0 + | Div, _ -> - 1 + | _, Div -> 1 + | Mod, Mod -> 0 + | Mod, _ -> - 1 + | _, Mod -> 1 + | Shiftlt, Shiftlt -> 0 + | Shiftlt, _ -> - 1 + | _, Shiftlt -> 1 + | Shiftrt, Shiftrt -> 0 + | Shiftrt, _ -> - 1 + | _, Shiftrt -> 1 + | Lt, Lt -> 0 + | Lt, _ -> - 1 + | _, Lt -> 1 + | Gt, Gt -> 0 + | Gt, _ -> - 1 + | _, Gt -> 1 + | Le, Le -> 0 + | Le, _ -> - 1 + | _, Le -> 1 + | Ge, Ge -> 0 + | Ge, _ -> - 1 + | _, Ge -> 1 + | Eq, Eq -> 0 + | Eq, _ -> - 1 + | _, Eq -> 1 + | Ne, Ne -> 0 + | Ne, _ -> - 1 + | _, Ne -> 1 + | BAnd, BAnd -> 0 + | BAnd, _ -> - 1 + | _, BAnd -> 1 + | BXor, BXor -> 0 + | BXor, _ -> - 1 + | _, BXor -> 1 + | BOr, BOr -> 0 + | BOr, _ -> - 1 + | _, BOr -> 1 + | LAnd, LAnd -> 0 + | LAnd, _ -> - 1 + | _, LAnd -> 1 + | LOr, LOr -> 0 + | LOr, _ -> -1 + | _, LOr -> 1 + | PtrFld, PtrFld -> 0 + +let binop_equal o1 o2 = binop_compare o1 o2 = 0 + +(** This function returns true if the operation is injective +wrt. each argument: op(e,-) and op(-, e) is injective for all e. +The return value false means "don't know". *) +let binop_injective = function + | PlusA | PlusPI | MinusA | MinusPI | MinusPP -> true + | _ -> false + +(** This function returns true if the operation can be inverted. *) +let binop_invertible = function + | PlusA | PlusPI | MinusA | MinusPI -> true + | _ -> false + +(** This function inverts an injective binary operator +with respect to the first argument. It returns an expression [e'] such that +BinOp([binop], [e'], [exp1]) = [exp2]. If the [binop] operation is not invertible, +the function raises an exception by calling "assert false". *) +let binop_invert bop e1 e2 = + let inverted_bop = match bop with + | PlusA -> MinusA + | PlusPI -> MinusPI + | MinusA -> PlusA + | MinusPI -> PlusPI + | _ -> assert false in + BinOp(inverted_bop, e2, e1) + +(** This function returns true if 0 is the right unit of [binop]. +The return value false means "don't know". *) +let binop_is_zero_runit = function + | PlusA | PlusPI | MinusA | MinusPI | MinusPP -> true + | _ -> false + +let path_pos_compare (pn1, nid1) (pn2, nid2) = + let n = Procname.compare pn1 pn2 in + if n <> 0 then n else int_compare nid1 nid2 + +let path_pos_equal pp1 pp2 = + path_pos_compare pp1 pp2 = 0 + +let mem_kind_to_num = function + | Mmalloc -> 0 + | Mnew -> 1 + | Mnew_array -> 2 + | Mobjc -> 3 + +(** name of the allocation function for the given memory kind *) +let mem_alloc_pname = function + | Mmalloc -> Procname.from_string "malloc" + | Mnew -> Procname.from_string "new" + | Mnew_array -> Procname.from_string "new[]" + | Mobjc -> Procname.from_string "alloc" + +(** name of the deallocation function for the given memory kind *) +let mem_dealloc_pname = function + | Mmalloc -> Procname.from_string "free" + | Mnew -> Procname.from_string "delete" + | Mnew_array -> Procname.from_string "delete[]" + | Mobjc -> Procname.from_string "dealloc" + +let mem_kind_compare mk1 mk2 = + int_compare (mem_kind_to_num mk1) (mem_kind_to_num mk2) + +let resource_compare r1 r2 = + let res_to_num = function + | Rmemory mk -> mem_kind_to_num mk + | Rfile -> 100 + | Rignore -> 200 + | Rlock -> 300 in + int_compare (res_to_num r1) (res_to_num r2) + +let res_act_kind_compare rak1 rak2 = match rak1, rak2 with + | Racquire, Racquire -> 0 + | Racquire, Rrelease -> - 1 + | Rrelease, Racquire -> 1 + | Rrelease, Rrelease -> 0 + +let dangling_kind_compare dk1 dk2 = match dk1, dk2 with + | DAuninit, DAuninit -> 0 + | DAuninit, _ -> - 1 + | _, DAuninit -> 1 + | DAaddr_stack_var, DAaddr_stack_var -> 0 + | DAaddr_stack_var, _ -> - 1 + | _, DAaddr_stack_var -> 1 + | DAminusone, DAminusone -> 0 + +let attribute_category_compare (ac1 : attribute_category) (ac2 : attribute_category) : int = + Pervasives.compare ac1 ac2 + +let attribute_category_equal att1 att2 = + attribute_category_compare att1 att2 = 0 + +let attribute_to_category att = + match att with + | Aresource _ + | Adangling _ + | Aretval _ + | Aundef _ -> ACresource + | Ataint + | Auntaint -> ACtaint + | Aautorelease -> ACautorelease + | Adiv0 _ -> ACdiv0 + | Aobjc_null _ -> ACobjc_null + | Avariadic_function_argument _ -> ACvariadic_function_argument + +let cname_opt_compare nameo1 nameo2 = match nameo1, nameo2 with + | None, None -> 0 + | None, _ -> - 1 + | _, None -> 1 + | Some n1, Some n2 -> Mangled.compare n1 n2 + +(** comparison for ikind *) +let ikind_compare k1 k2 = match k1, k2 with + | IChar, IChar -> 0 + | IChar, _ -> - 1 + | _, IChar -> 1 + | ISChar, ISChar -> 0 + | ISChar, _ -> - 1 + | _, ISChar -> 1 + | IUChar, IUChar -> 0 + | IUChar, _ -> - 1 + | _, IUChar -> 1 + | IBool, IBool -> 0 + | IBool, _ -> - 1 + | _, IBool -> 1 + | IInt, IInt -> 0 + | IInt, _ -> - 1 + | _, IInt -> 1 + | IUInt, IUInt -> 0 + | IUInt, _ -> - 1 + | _, IUInt -> 1 + | IShort, IShort -> 0 + | IShort, _ -> - 1 + | _, IShort -> 1 + | IUShort, IUShort -> 0 + | IUShort, _ -> - 1 + | _, IUShort -> 1 + | ILong, ILong -> 0 + | ILong, _ -> - 1 + | _, ILong -> 1 + | IULong, IULong -> 0 + | IULong, _ -> - 1 + | _, IULong -> 1 + | ILongLong, ILongLong -> 0 + | ILongLong, _ -> - 1 + | _, ILongLong -> 1 + | IULongLong, IULongLong -> 0 + | IULongLong, _ -> -1 + | _, IULongLong -> 1 + | I128, I128 -> 0 + | I128, _ -> -1 + | _, I128 -> 1 + | IU128, IU128 -> 0 + +(** comparison for fkind *) +let fkind_compare k1 k2 = match k1, k2 with + | FFloat, FFloat -> 0 + | FFloat, _ -> - 1 + | _, FFloat -> 1 + | FDouble, FDouble -> 0 + | FDouble, _ -> - 1 + | _, FDouble -> 1 + | FLongDouble, FLongDouble -> 0 + +let csu_compare csu1 csu2 = match csu1, csu2 with + | Class, Class -> 0 + | Class, _ -> -1 + | _, Class -> 1 + | Struct, Struct -> 0 + | Struct, _ -> -1 + | _, Struct -> 1 + | Union, Union -> 0 + | Union, _ -> -1 + | _, Union -> 1 + | Protocol, Protocol -> 0 + +let typename_compare tn1 tn2 = match tn1, tn2 with + | TN_typedef n1, TN_typedef n2 -> Mangled.compare n1 n2 + | TN_typedef _, _ -> - 1 + | _, TN_typedef _ -> 1 + | TN_enum n1, TN_enum n2 -> Mangled.compare n1 n2 + | TN_enum _, _ -> -1 + | _, TN_enum _ -> 1 + | TN_csu (csu1, n1), TN_csu (csu2, n2) -> + let n = csu_compare csu1 csu2 in + if n <> 0 then n else Mangled.compare n1 n2 + +let csu_name_compare tn1 tn2 = match tn1, tn2 with + | (csu1, n1), (csu2, n2) -> + let n = csu_compare csu1 csu2 in + if n <> 0 then n else Mangled.compare n1 n2 + +let csu_name_equal tn1 tn2 = + csu_name_compare tn1 tn2 = 0 + +let typename_equal tn1 tn2 = + typename_compare tn1 tn2 = 0 + +let ptr_kind_compare pk1 pk2 = match pk1, pk2 with + | Pk_pointer, Pk_pointer -> 0 + | Pk_pointer, _ -> -1 + | _, Pk_pointer -> 1 + | Pk_reference, Pk_reference -> 0 + | _ , Pk_reference -> -1 + | Pk_reference, _ -> 1 + | Pk_objc_weak, Pk_objc_weak -> 0 + | Pk_objc_weak, _ -> -1 + | _, Pk_objc_weak -> 1 + | Pk_objc_unsafe_unretained, Pk_objc_unsafe_unretained -> 0 + | Pk_objc_unsafe_unretained, _ -> -1 + | _, Pk_objc_unsafe_unretained -> 1 + | Pk_objc_autoreleasing, Pk_objc_autoreleasing -> 0 + +let const_kind_equal c1 c2 = + let const_kind_number = function + | Cint _ -> 1 + | Cfun _ -> 2 + | Cstr _ -> 3 + | Cfloat _ -> 4 + | Cattribute _ -> 5 + | Cexn _ -> 6 + | Cclass _ -> 7 + | Cptr_to_fld _ -> 8 + | Ctuple _ -> 9 in + const_kind_number c1 = const_kind_number c2 + +let rec const_compare (c1 : const) (c2 : const) : int = + match (c1, c2) with + | Cint i1, Cint i2 -> Int.compare i1 i2 + | Cint _, _ -> - 1 + | _, Cint _ -> 1 + | Cfun fn1, Cfun fn2 -> Procname.compare fn1 fn2 + | Cfun _, _ -> - 1 + | _, Cfun _ -> 1 + | Cstr s1, Cstr s2 -> string_compare s1 s2 + | Cstr _, _ -> - 1 + | _, Cstr _ -> 1 + | Cfloat f1, Cfloat f2 -> float_compare f1 f2 + | Cfloat _, _ -> - 1 + | _, Cfloat _ -> 1 + | Cattribute att1, Cattribute att2 -> attribute_compare att1 att2 + | Cattribute _, _ -> -1 + | _, Cattribute _ -> 1 + | Cexn e1, Cexn e2 -> exp_compare e1 e2 + | Cexn _, _ -> -1 + | _, Cexn _ -> 1 + | Cclass c1, Cclass c2 -> Ident.name_compare c1 c2 + | Cclass _, _ -> -1 + | _, Cclass _ -> 1 + | Cptr_to_fld (fn1, t1), Cptr_to_fld (fn2, t2) -> + let n = fld_compare fn1 fn2 in + if n <> 0 then n else typ_compare t1 t2 + | Cptr_to_fld _, _ -> -1 + | _, Cptr_to_fld _ -> 1 + | Ctuple el1, Ctuple el2 -> list_compare exp_compare el1 el2 + +(** Comparision for types. *) +and typ_compare t1 t2 = + if t1 == t2 then 0 else match t1, t2 with + | Tvar tn1, Tvar tn2 -> typename_compare tn1 tn2 + | Tvar _, _ -> - 1 + | _, Tvar _ -> 1 + | Tint ik1, Tint ik2 -> ikind_compare ik1 ik2 + | Tint _, _ -> - 1 + | _, Tint _ -> 1 + | Tfloat fk1, Tfloat fk2 -> fkind_compare fk1 fk2 + | Tfloat _, _ -> - 1 + | _, Tfloat _ -> 1 + | Tvoid, Tvoid -> 0 + | Tvoid, _ -> - 1 + | _, Tvoid -> 1 + | Tfun noreturn1, Tfun noreturn2 -> bool_compare noreturn1 noreturn2 + | Tfun _, _ -> - 1 + | _, Tfun _ -> 1 + | Tptr (t1', pk1), Tptr (t2', pk2) -> + let n = typ_compare t1' t2' in + if n <> 0 then n else ptr_kind_compare pk1 pk2 + | Tptr _, _ -> - 1 + | _, Tptr _ -> 1 + | Tstruct (ntal1, sntal1, csu1, nameo1, _, _, _), Tstruct (ntal2, sntal2, csu2, nameo2, _, _, _) -> + let n = fld_typ_ann_list_compare ntal1 ntal2 in + if n <> 0 then n else let n = fld_typ_ann_list_compare sntal1 sntal2 in + if n <> 0 then n else let n = csu_compare csu1 csu2 in + if n <> 0 then n else cname_opt_compare nameo1 nameo2 + | Tstruct _, _ -> - 1 + | _, Tstruct _ -> 1 + | Tarray (t1, _), Tarray (t2, _) -> typ_compare t1 t2 + | Tarray _, _ -> -1 + | _, Tarray _ -> 1 + | Tenum l1, Tenum l2 -> + (* Here we take as result the first non-zero result when comparing their (constant,value) pair.*) + let compare_pair (n1, e1) (n2, e2) = + let n = Mangled.compare n1 n2 in + if n <> 0 then n else const_compare e1 e2 in + list_compare compare_pair l1 l2 + +and typ_opt_compare to1 to2 = match to1, to2 with + | None, None -> 0 + | None, Some _ -> - 1 + | Some _, None -> 1 + | Some t1, Some t2 -> typ_compare t1 t2 + +and fld_typ_ann_compare fta1 fta2 = + triple_compare fld_compare typ_compare item_annotation_compare fta1 fta2 + +and fld_typ_ann_list_compare ftal1 ftal2 = + list_compare fld_typ_ann_compare ftal1 ftal2 + +and attribute_compare (att1 : attribute) (att2 : attribute) : int = + match att1, att2 with + | Aresource ra1, Aresource ra2 -> + let n = res_act_kind_compare ra1.ra_kind ra2.ra_kind in + if n <> 0 then n else resource_compare ra1.ra_res ra2.ra_res (* ignore other values beside resources: arbitrary merging into one *) + | Aresource _, _ -> - 1 + | _, Aresource _ -> 1 + | Aautorelease, Aautorelease -> 0 + | Aautorelease, _ -> -1 + | _, Aautorelease -> 1 + | Adangling dk1, Adangling dk2 -> dangling_kind_compare dk1 dk2 + | Adangling _, _ -> - 1 + | _, Adangling _ -> 1 + | Aundef (pn1, _, _), Aundef (pn2, _, _) -> Procname.compare pn1 pn2 + | Ataint, Ataint -> 0 + | Ataint, _ -> -1 + | _, Ataint -> 1 + | Auntaint, Auntaint -> 0 + | Auntaint, _ -> -1 + | _, Auntaint -> 1 + | Adiv0 pp1, Adiv0 pp2 -> + path_pos_compare pp1 pp2 + | Adiv0 _, _ -> -1 + | _, Adiv0 _ -> 1 + | Aobjc_null exp1, Aobjc_null exp2 -> + exp_compare exp1 exp2 + | Aobjc_null _, _ -> -1 + | _, Aobjc_null _ -> 1 + | Avariadic_function_argument (pn1, n1, i1), Avariadic_function_argument (pn2, n2, i2) -> + Procname.compare pn1 pn2 + |> next int_compare n1 n2 + |> next int_compare i1 i2 + | Avariadic_function_argument _, _ -> -1 + | _, Avariadic_function_argument _ -> 1 + | Aretval pn1, Aretval pn2 -> Procname.compare pn1 pn2 + | Aretval _, _ -> -1 + | _, Aretval _ -> 1 + +(** Compare epressions. Variables come before other expressions. *) +and exp_compare (e1 : exp) (e2 : exp) : int = + match (e1, e2) with + | Var id1, Var id2 -> + Ident.compare id2 id1 + | Var _, _ -> - 1 + | _, Var _ -> 1 + | UnOp (o1, e1, to1), UnOp (o2, e2, to2) -> + let n = unop_compare o1 o2 in + if n <> 0 then n else + let n = exp_compare e1 e2 in + if n <> 0 then n else typ_opt_compare to1 to2 + | UnOp _, _ -> - 1 + | _, UnOp _ -> 1 + | BinOp (o1, e1, f1), BinOp (o2, e2, f2) -> + let n = binop_compare o1 o2 in + if n <> 0 then n + else + let n = exp_compare e1 e2 in + if n <> 0 then n else exp_compare f1 f2 + | BinOp _, _ -> - 1 + | _, BinOp _ -> 1 + | Const c1, Const c2 -> + const_compare c1 c2 + | Const _, _ -> - 1 + | _, Const _ -> 1 + | Cast (t1, e1), Cast(t2, e2) -> + let n = exp_compare e1 e2 in + if n <> 0 then n else typ_compare t1 t2 + | Cast _, _ -> - 1 + | _, Cast _ -> 1 + | Lvar i1, Lvar i2 -> + pvar_compare i1 i2 + | Lvar _, _ -> - 1 + | _, Lvar _ -> 1 + | Lfield (e1, f1, t1), Lfield (e2, f2, t2) -> + let n = exp_compare e1 e2 in + if n <> 0 then n else + let n = fld_compare f1 f2 in + if n <> 0 then n else typ_compare t1 t2 + | Lfield _, _ -> - 1 + | _, Lfield _ -> 1 + | Lindex (e1, f1), Lindex (e2, f2) -> + let n = exp_compare e1 e2 in + if n <> 0 then n else exp_compare f1 f2 + | Lindex _, _ -> - 1 + | _, Lindex _ -> 1 + | Sizeof (t1, s1), Sizeof (t2, s2) -> + let n = typ_compare t1 t2 in + if n <> 0 then n else Subtype.compare s1 s2 + +let const_equal c1 c2 = + const_compare c1 c2 = 0 + +let typ_equal t1 t2 = + (typ_compare t1 t2 = 0) + +let exp_equal e1 e2 = + exp_compare e1 e2 = 0 + +let ident_exp_compare = + pair_compare Ident.compare exp_compare + +let ident_exp_equal ide1 ide2 = + ident_exp_compare ide1 ide2 = 0 + +let exp_list_compare = + list_compare exp_compare + +let exp_list_equal el1 el2 = + exp_list_compare el1 el2 = 0 + +let attribute_equal att1 att2 = + attribute_compare att1 att2 = 0 + +(** Compare atoms. Equalities come before disequalities *) +let atom_compare a b = + if a == b then 0 else + match (a, b) with + | Aeq (e1, e2), Aeq(f1, f2) -> + let n = exp_compare e1 f1 in + if n <> 0 then n else exp_compare e2 f2 + | Aeq _, Aneq _ -> - 1 + | Aneq _, Aeq _ -> 1 + | Aneq (e1, e2), Aneq (f1, f2) -> + let n = exp_compare e1 f1 in + if n <> 0 then n else exp_compare e2 f2 + +let atom_equal x y = + atom_compare x y = 0 + +let atom_list_compare l1 l2 = + list_compare atom_compare l1 l2 + +let lseg_kind_compare k1 k2 = match k1, k2 with + | Lseg_NE, Lseg_NE -> 0 + | Lseg_NE, Lseg_PE -> - 1 + | Lseg_PE, Lseg_NE -> 1 + | Lseg_PE, Lseg_PE -> 0 + +let lseg_kind_equal k1 k2 = + lseg_kind_compare k1 k2 = 0 + +(* Comparison for strexps *) +let rec strexp_compare se1 se2 = + if se1 == se2 then 0 + else match se1, se2 with + | Eexp (e1, inst1), Eexp (e2, inst2) -> exp_compare e1 e2 + | Eexp _, _ -> - 1 + | _, Eexp _ -> 1 + | Estruct (fel1, inst1), Estruct (fel2, inst2) -> fld_strexp_list_compare fel1 fel2 + | Estruct _, _ -> - 1 + | _, Estruct _ -> 1 + | Earray (e1, esel1, inst1), Earray (e2, esel2, inst2) -> + let n = exp_compare e1 e2 in + if n <> 0 then n else exp_strexp_list_compare esel1 esel2 + +and fld_strexp_compare fse1 fse2 = + pair_compare fld_compare strexp_compare fse1 fse2 + +and fld_strexp_list_compare fsel1 fsel2 = + list_compare fld_strexp_compare fsel1 fsel2 + +and exp_strexp_compare ese1 ese2 = + pair_compare exp_compare strexp_compare ese1 ese2 + +and exp_strexp_list_compare esel1 esel2 = + list_compare exp_strexp_compare esel1 esel2 + +(** Comparsion between heap predicates. Hpointsto comes before others. *) +and hpred_compare hpred1 hpred2 = + if hpred1 == hpred2 then 0 else + match (hpred1, hpred2) with + | Hpointsto (e1, _, _), Hlseg(_, _, e2, _, _) when exp_compare e2 e1 <> 0 -> + exp_compare e2 e1 + | Hpointsto (e1, _, _), Hdllseg(_, _, e2, _, _, _, _) when exp_compare e2 e1 <> 0 -> + exp_compare e2 e1 + | Hlseg(_, _, e1, _, _), Hpointsto (e2, _, _) when exp_compare e2 e1 <> 0 -> + exp_compare e2 e1 + | Hlseg(_, _, e1, _, _), Hdllseg(_, _, e2, _, _, _, _) when exp_compare e2 e1 <> 0 -> + exp_compare e2 e1 + | Hdllseg(_, _, e1, _, _, _, _), Hpointsto (e2, _, _) when exp_compare e2 e1 <> 0 -> + exp_compare e2 e1 + | Hdllseg(_, _, e1, _, _, _, _), Hlseg(_, _, e2, _, _) when exp_compare e2 e1 <> 0 -> + exp_compare e2 e1 + | Hpointsto (e1, se1, te1), Hpointsto (e2, se2, te2) -> + let n = exp_compare e2 e1 in + if n <> 0 then n else + let n = strexp_compare se2 se1 in + if n <> 0 then n else exp_compare te2 te1 + | Hpointsto _, _ -> - 1 + | _, Hpointsto _ -> 1 + | Hlseg (k1, hpar1, e1, f1, el1), Hlseg (k2, hpar2, e2, f2, el2) -> + let n = exp_compare e2 e1 in + if n <> 0 then n + else let n = lseg_kind_compare k2 k1 in + if n <> 0 then n + else let n = hpara_compare hpar2 hpar1 in + if n <> 0 then n + else let n = exp_compare f2 f1 in + if n <> 0 then n + else exp_list_compare el2 el1 + | Hlseg _, Hdllseg _ -> - 1 + | Hdllseg _, Hlseg _ -> 1 + | Hdllseg (k1, hpar1, e1, f1, g1, h1, el1), Hdllseg (k2, hpar2, e2, f2, g2, h2, el2) -> + let n = exp_compare e2 e1 in + if n <> 0 then n + else let n = lseg_kind_compare k2 k1 in + if n <> 0 then n + else let n = hpara_dll_compare hpar2 hpar1 in + if n <> 0 then n + else let n = exp_compare f2 f1 in + if n <> 0 then n + else let n = exp_compare g2 g1 in + if n <> 0 then n + else let n = exp_compare h2 h1 in + if n <> 0 then n + else exp_list_compare el2 el1 + +and hpred_list_compare l1 l2 = + list_compare hpred_compare l1 l2 + +and hpara_compare hp1 hp2 = + let n = Ident.compare hp1.root hp2.root in + if n <> 0 then n + else let n = Ident.compare hp1.next hp2.next in + if n <> 0 then n + else let n = Ident.ident_list_compare hp1.svars hp2.svars in + if n <> 0 then n + else let n = Ident.ident_list_compare hp1.evars hp2.evars in + if n <> 0 then n + else hpred_list_compare hp1.body hp2.body + +and hpara_dll_compare hp1 hp2 = + let n = Ident.compare hp1.cell hp2.cell in + if n <> 0 then n + else let n = Ident.compare hp1.blink hp2.blink in + if n <> 0 then n + else let n = Ident.compare hp1.flink hp2.flink in + if n <> 0 then n + else let n = Ident.ident_list_compare hp1.svars_dll hp2.svars_dll in + if n <> 0 then n + else let n = Ident.ident_list_compare hp1.evars_dll hp2.evars_dll in + if n <> 0 then n + else hpred_list_compare hp1.body_dll hp2.body_dll + +let strexp_equal se1 se2 = + (strexp_compare se1 se2 = 0) + +let fld_strexp_equal fld_sexp1 fld_sexp2 = + (fld_strexp_compare fld_sexp1 fld_sexp2 = 0) + +let exp_strexp_equal ese1 ese2 = + (exp_strexp_compare ese1 ese2 = 0) + +let hpred_equal hpred1 hpred2 = + (hpred_compare hpred1 hpred2 = 0) + +let hpara_equal hpara1 hpara2 = + (hpara_compare hpara1 hpara2 = 0) + +let hpara_dll_equal hpara1 hpara2 = + (hpara_dll_compare hpara1 hpara2 = 0) + +(** {2 Sets and maps of types} *) +module TypSet = Set.Make(struct + type t = typ + let compare = typ_compare + end) + +module TypMap = Map.Make(struct + type t = typ + let compare = typ_compare + end) + +(** {2 Sets of expressions} *) + +module ExpSet = Set.Make + (struct + type t = exp + let compare = exp_compare + end) + +let elist_to_eset es = + list_fold_left (fun set e -> ExpSet.add e set) ExpSet.empty es + +(** {2 Sets of heap predicates} *) + +module HpredSet = Set.Make + (struct + type t = hpred + let compare = hpred_compare + end) + +(** {2 Pretty Printing} *) + +(** Begin change color if using diff printing, return updated printenv and change status *) +let color_pre_wrapper pe f x = + if !Config.print_using_diff && pe.pe_kind != PP_TEXT then begin + let color = pe.pe_cmap_norm (Obj.repr x) in + if color != pe.pe_color then begin + (if pe.pe_kind == PP_HTML then Io_infer.Html.pp_start_color else Latex.pp_color) f color; + if color == Red then ({ pe with pe_cmap_norm = colormap_red; pe_color = Red }, true) (** Al subexpressiona red *) + else ({ pe with pe_color = color }, true) end + else (pe, false) end + else (pe, false) + +(** Close color annotation if changed *) +let color_post_wrapper changed pe f = + if changed then (if pe.pe_kind == PP_HTML then Io_infer.Html.pp_end_color f () else Latex.pp_color f pe.pe_color) + +(** Print a sequence with difference mode if enabled. *) +let pp_seq_diff pp pe0 f = + if not !Config.print_using_diff + then pp_comma_seq pp f + else + let rec doit = function + | [] -> () + | [x] -> + let pe, changed = color_pre_wrapper pe0 f x in + F.fprintf f "%a" pp x; + color_post_wrapper changed pe0 f + | x :: l -> + let pe, changed = color_pre_wrapper pe0 f x in + F.fprintf f "%a" pp x; + color_post_wrapper changed pe0 f; + F.fprintf f ", "; + doit l in + doit + +let text_binop = function + | PlusA -> "+" + | PlusPI -> "+" + | MinusA | MinusPP -> "-" + | MinusPI -> "-" + | Mult -> "*" + | Div -> "/" + | Mod -> "%" + | Shiftlt -> "<<" + | Shiftrt -> ">>" + | Lt -> "<" + | Gt -> ">" + | Le -> "<=" + | Ge -> ">=" + | Eq -> "==" + | Ne -> "!=" + | BAnd -> "&" + | BXor -> "^" + | BOr -> "|" + | LAnd -> "&&" + | LOr -> "||" + | PtrFld -> "_ptrfld_" + +(** String representation of unary operator. *) +let str_unop = function + | Neg -> "-" + | BNot -> "~" + | LNot -> "!" + +(** Pretty print a binary operator. *) +let str_binop pe binop = + match pe.pe_kind with + | PP_HTML -> + begin + match binop with + | Ge -> " >= " + | Le -> " <= " + | Gt -> " > " + | Lt -> " < " + | Shiftlt -> " << " + | Shiftrt -> " >> " + | _ -> text_binop binop + end + | PP_LATEX -> + begin + match binop with + | Ge -> " \\geq " + | Le -> " \\leq " + | _ -> text_binop binop + end + | _ -> + text_binop binop + +(** Pretty print a location *) +let pp_loc f (loc: location) = + F.fprintf f "[line %d]" loc.line + +let loc_to_string loc = + let s = (string_of_int loc.line) in + if (loc.col != -1) then + s ^":"^(string_of_int loc.col) + else s + +(** Dump a location *) +let d_loc (loc: location) = L.add_print_action (L.PTloc, Obj.repr loc) + +let _pp_pvar f pv = + let name = pv.pv_name in + match pv.pv_kind with + | Local_var n -> + if !Config.pp_simple then F.fprintf f "%a" Mangled.pp name + else F.fprintf f "%a$%a" Procname.pp n Mangled.pp name + | Callee_var n -> + if !Config.pp_simple then F.fprintf f "%a|callee" Mangled.pp name + else F.fprintf f "%a$%a|callee" Procname.pp n Mangled.pp name + | Abducted_retvar (n, l) -> + if !Config.pp_simple then F.fprintf f "%a|abductedRetvar" Mangled.pp name + else F.fprintf f "%a$%a%a|abductedRetvar" Procname.pp n pp_loc l Mangled.pp name + | Global_var -> F.fprintf f "#GB$%a" Mangled.pp name + | Seed_var -> F.fprintf f "old_%a" Mangled.pp name + +(** Pretty print a program variable in latex. *) +let pp_pvar_latex f pv = + let name = pv.pv_name in + match pv.pv_kind with + | Local_var n -> + Latex.pp_string Latex.Roman f (Mangled.to_string name) + | Callee_var n -> + F.fprintf f "%a_{%a}" (Latex.pp_string Latex.Roman) (Mangled.to_string name) + (Latex.pp_string Latex.Roman) "callee" + | Abducted_retvar (n, l) -> + F.fprintf f "%a_{%a}" (Latex.pp_string Latex.Roman) (Mangled.to_string name) + (Latex.pp_string Latex.Roman) "abductedRetvar" + | Global_var -> + Latex.pp_string Latex.Boldface f (Mangled.to_string name) + | Seed_var -> + F.fprintf f "%a^{%a}" (Latex.pp_string Latex.Roman) (Mangled.to_string name) + (Latex.pp_string Latex.Roman) "old" + +(** Pretty print a pvar which denotes a value, not an address *) +let pp_pvar_value pe f pv = + match pe.pe_kind with + | PP_TEXT -> _pp_pvar f pv + | PP_HTML -> _pp_pvar f pv + | PP_LATEX -> pp_pvar_latex f pv + +(** Pretty print a program variable. *) +let pp_pvar pe f pv = + let ampersand = match pe.pe_kind with + | PP_TEXT -> "&" + | PP_HTML -> "&" + | PP_LATEX -> "\\&" in + F.fprintf f "%s%a" ampersand (pp_pvar_value pe) pv + +(** Dump a program variable. *) +let d_pvar (pvar: pvar) = L.add_print_action (L.PTpvar, Obj.repr pvar) + +(** Pretty print a list of program variables. *) +let pp_pvar_list pe f pvl = + F.fprintf f "%a" (pp_seq (fun f e -> F.fprintf f "%a" (pp_pvar pe) e)) pvl + +(** Dump a list of program variables. *) +let d_pvar_list pvl = + list_iter (fun pv -> d_pvar pv; L.d_str " ") pvl + +let ikind_to_string = function + | IChar -> "char" + | ISChar -> "signed char" + | IUChar -> "unsigned char" + | IBool -> "_Bool" + | IInt -> "int" + | IUInt -> "unsigned int" + | IShort -> "short" + | IUShort -> "unsigned short" + | ILong -> "long" + | IULong -> "unsigned long" + | ILongLong -> "long long" + | IULongLong -> "unsigned long long" + | I128 -> "__int128_t" + | IU128 -> "__uint128_t" + +let fkind_to_string = function + | FFloat -> "float" + | FDouble -> "double" + | FLongDouble -> "long double" + +let csu_name = function + | Class -> "class" + | Struct -> "struct" + | Union -> "union" + | Protocol -> "protocol" + +let typename_to_string = function + | TN_enum name + | TN_typedef name -> Mangled.to_string name + | TN_csu (csu, name) -> csu_name csu ^ " " ^ Mangled.to_string name + +let typename_name = function + | TN_enum name + | TN_typedef name + | TN_csu (_, name) -> Mangled.to_string name + +let ptr_kind_string = function + | Pk_reference -> "&" + | Pk_pointer -> "*" + | Pk_objc_weak -> "__weak *" + | Pk_objc_unsafe_unretained -> "__unsafe_unretained *" + | Pk_objc_autoreleasing -> "__autoreleasing *" + +let java () = !curr_language = Java +let eradicate_java () = !Config.eradicate && java () + +(** convert a dexp to a string *) +let rec dexp_to_string = function + | Darray (de1, de2) -> dexp_to_string de1 ^ "[" ^ dexp_to_string de2 ^ "]" + | Dbinop (op, de1, de2) -> + "(" ^ dexp_to_string de1 ^ (str_binop pe_text op) ^ dexp_to_string de2 ^ ")" + | Dconst (Cfun pn) -> + Procname.to_simplified_string pn + | Dconst c -> exp_to_string (Const c) + | Dderef de -> "*" ^ dexp_to_string de + | Dfcall (fun_dexp, args, loc, { cf_virtual = isvirtual }) -> + let pp_arg fmt de = F.fprintf fmt "%s" (dexp_to_string de) in + let pp_args fmt des = + if eradicate_java () + then (if des <> [] then F.fprintf fmt "...") + else (pp_comma_seq) pp_arg fmt des in + let pp_fun fmt = function + | Dconst (Cfun pname) -> + let s = (if Procname.is_java pname then Procname.java_get_method else Procname.to_string) pname in + F.fprintf fmt "%s" s + | de -> F.fprintf fmt "%s" (dexp_to_string de) in + let receiver, args' = match args with + | (Dpvar pv):: args' when isvirtual && pvar_is_this pv -> + (None, args') + | a:: args' when isvirtual -> + (Some a, args') + | _ -> + (None, args) in + let pp fmt () = + let pp_receiver fmt = function + | None -> () + | Some arg -> F.fprintf fmt "%a." pp_arg arg in + F.fprintf fmt "%a%a(%a)" pp_receiver receiver pp_fun fun_dexp pp_args args' in + pp_to_string pp () + | Darrow ((Dpvar pv), f) when pvar_is_this pv -> (* this->fieldname *) + Ident.fieldname_to_simplified_string f + | Darrow (de, f) -> + if Ident.fieldname_is_hidden f then dexp_to_string de + else if java() then dexp_to_string de ^ "." ^ Ident.fieldname_to_flat_string f + else dexp_to_string de ^ "->" ^ Ident.fieldname_to_string f + | Ddot (Dpvar pv, fe) when eradicate_java () -> (* static field access *) + Ident.fieldname_to_simplified_string fe + | Ddot (de, f) -> + if Ident.fieldname_is_hidden f then "&" ^ dexp_to_string de + else if java() then dexp_to_string de ^ "." ^ Ident.fieldname_to_flat_string f + else dexp_to_string de ^ "." ^ Ident.fieldname_to_string f + | Dpvar pv -> Mangled.to_string (pvar_get_name pv) + | Dpvaraddr pv -> + let s = + if eradicate_java () then pvar_get_simplified_name pv + else Mangled.to_string (pvar_get_name pv) in + let ampersand = + if eradicate_java () then "" + else "&" in + ampersand ^ s + | Dunop (op, de) -> str_unop op ^ dexp_to_string de + | Dsizeof (typ, sub) -> pp_to_string (pp_typ_full pe_text) typ + | Dunknown -> "unknown" + | Dretcall (de, _, _, _) -> + "returned by " ^ (dexp_to_string de) + +(** Pretty print a dexp. *) +and pp_dexp pe fmt de = F.fprintf fmt "%s" (dexp_to_string de) + +(** Pretty print a value path *) +and pp_vpath pe fmt vpath = + let pp fmt = function + | Some de -> pp_dexp pe fmt de + | None -> () in + if pe.pe_kind == PP_HTML then + F.fprintf fmt " %a{vpath: %a}%a" Io_infer.Html.pp_start_color Orange pp vpath Io_infer.Html.pp_end_color () + else + F.fprintf fmt "%a" pp vpath + +(** convert the attribute to a string *) +and attribute_to_string pe = function + | Aresource ra -> + let mk_name = function + | Mmalloc -> "ma" + | Mnew -> "ne" + | Mnew_array -> "na" + | Mobjc -> "oc" in + let name = match ra.ra_kind, ra.ra_res with + | Racquire, Rmemory mk -> "MEM" ^ mk_name mk + | Racquire, Rfile -> "FILE" + | Rrelease, Rmemory mk -> "FREED" ^ mk_name mk + | Rrelease, Rfile -> "CLOSED" + | _, Rignore -> "IGNORE" + | Racquire, Rlock -> "LOCKED" + | Rrelease, Rlock -> "UNLOCKED" in + let str_vpath = + if !Config.trace_error + then pp_to_string (pp_vpath pe) ra.ra_vpath + else "" in + name ^ (str_binop pe Lt) ^ Procname.to_string ra.ra_pname ^ ":" ^ (string_of_int ra.ra_loc.line) ^ (str_binop pe Gt) ^ str_vpath + | Aautorelease -> "AUTORELEASE" + | Adangling dk -> + let dks = match dk with + | DAuninit -> "UNINIT" + | DAaddr_stack_var -> "ADDR_STACK" + | DAminusone -> "MINUS1" in + "DANGL" ^ (str_binop pe Lt) ^ dks ^ (str_binop pe Gt) + | Aundef (pn, loc, _) -> "UND" ^ (str_binop pe Lt) ^ Procname.to_string pn ^ (str_binop pe Gt) ^ ":" ^ (string_of_int loc.line) + | Ataint -> "TAINTED" + | Auntaint -> "UNTAINTED" + | Adiv0 (pn, nd_id) -> "DIV0" + | Aobjc_null exp -> + let info_s = + match exp with + | Lvar var -> "FORMAL "^(pvar_to_string var) + | Lfield _ -> "FIELD "^(exp_to_string exp) + | _ -> "" in + "OBJC_NULL["^ info_s ^"]" + | Avariadic_function_argument (pn, n, i) -> + Printf.sprintf "VA_ARG" ^ (str_binop pe Lt) + ^ Procname.to_string pn ^ "," ^ (string_of_int n) ^ "," ^ (string_of_int i) + ^ (str_binop pe Gt) + | Aretval pn -> "RET" ^ str_binop pe Lt ^ Procname.to_string pn ^ str_binop pe Gt + +and pp_const pe f = function + | Cint i -> Int.pp f i + | Cfun fn -> + (match pe.pe_kind with + | PP_HTML -> F.fprintf f "_fun_%s" (Escape.escape_xml (Procname.to_string fn)) + | _ -> F.fprintf f "_fun_%s" (Procname.to_string fn)) + | Cstr s -> F.fprintf f "\"%s\"" (String.escaped s) + | Cfloat v -> F.fprintf f "%f" v + | Cattribute att -> F.fprintf f "%s" (attribute_to_string pe att) + | Cexn e -> F.fprintf f "EXN %a" (pp_exp pe) e + | Cclass c -> F.fprintf f "%a" Ident.pp_name c + | Cptr_to_fld (fn, typ) -> F.fprintf f "__fld_%a" Ident.pp_fieldname fn + | Ctuple el -> F.fprintf f "(%a)" (pp_comma_seq (pp_exp pe)) el + +(** Pretty print a type. Do nothing by default. *) +and pp_typ pe f te = + if !Config.print_types then pp_typ_full pe f te else () + +(** Pretty print a type declaration. +pp_base prints the variable for a declaration, or can be skip to print only the type +pp_size prints the expression for the array size *) +and pp_type_decl pe pp_base pp_size f = function + | Tvar tname -> F.fprintf f "%s %a" (typename_to_string tname) pp_base () + | Tint ik -> F.fprintf f "%s %a" (ikind_to_string ik) pp_base () + | Tfloat fk -> F.fprintf f "%s %a" (fkind_to_string fk) pp_base () + | Tvoid -> F.fprintf f "void %a" pp_base () + | Tfun false -> F.fprintf f "_fn_ %a" pp_base () + | Tfun true -> F.fprintf f "_fn_noreturn_ %a" pp_base () + | Tptr ((Tarray _ | Tfun _) as typ, pk) -> + let pp_base' fmt () = F.fprintf fmt "(%s%a)" (ptr_kind_string pk) pp_base () in + pp_type_decl pe pp_base' pp_size f typ + | Tptr (typ, pk) -> + let pp_base' fmt () = F.fprintf fmt "%s%a" (ptr_kind_string pk) pp_base () in + pp_type_decl pe pp_base' pp_size f typ + | Tstruct (ftal, sftal, csu, Some name, _, _, _) when false -> (* remove "when false" to print the details of struct *) + F.fprintf f "%s %a {%a} %a" (csu_name csu) Mangled.pp name + (pp_seq (fun f (fld, t, ann) -> + F.fprintf f "%a %a" (pp_typ_full pe) t Ident.pp_fieldname fld)) + ftal pp_base () + | Tstruct (ftal, sftal, csu, Some name, _, _, _) -> + F.fprintf f "%s %a %a" (csu_name csu) Mangled.pp name pp_base () + | Tstruct (ftal, sftal, csu, None, _, _, _) -> + F.fprintf f "%s {%a} %a" (csu_name csu) + (pp_seq (fun f (fld, t, ann) -> F.fprintf f "%a %a" (pp_typ_full pe) t Ident.pp_fieldname fld)) ftal pp_base () + | Tarray (typ, size) -> + let pp_base' fmt () = F.fprintf fmt "%a[%a]" pp_base () (pp_size pe) size in + pp_type_decl pe pp_base' pp_size f typ + | Tenum econsts -> + F.fprintf f "enum { %a }" + (pp_seq (fun f (n, e) -> F.fprintf f " (%a, %a) " Mangled.pp n (pp_const pe) e)) econsts + +(** Pretty print a type with all the details, using the C syntax. *) +and pp_typ_full pe = pp_type_decl pe (fun fmt () -> ()) pp_exp_full + +(** Pretty print an expression. *) +and _pp_exp pe0 pp_t f e0 = + let pe, changed = color_pre_wrapper pe0 f e0 in + let e = match pe.pe_obj_sub with + | Some sub -> Obj.obj (sub (Obj.repr e0)) (* apply object substitution to expression *) + | None -> e0 in + (if not (exp_equal e0 e) + then + match e with + | Lvar pvar -> pp_pvar_value pe f pvar + | _ -> assert false + else + let pp_exp = _pp_exp pe pp_t in + let print_binop_stm_output e1 op e2 = + match op with + | Eq | Ne | PlusA | Mult -> F.fprintf f "(%a %s %a)" pp_exp e2 (str_binop pe op) pp_exp e1 + | Lt -> F.fprintf f "(%a %s %a)" pp_exp e2 (str_binop pe Gt) pp_exp e1 + | Gt -> F.fprintf f "(%a %s %a)" pp_exp e2 (str_binop pe Lt) pp_exp e1 + | Le -> F.fprintf f "(%a %s %a)" pp_exp e2 (str_binop pe Ge) pp_exp e1 + | Ge -> F.fprintf f "(%a %s %a)" pp_exp e2 (str_binop pe Le) pp_exp e1 + | _ -> F.fprintf f "(%a %s %a)" pp_exp e1 (str_binop pe op) pp_exp e2 in + begin match e with + | Var id -> (Ident.pp pe) f id + | Const c -> F.fprintf f "%a" (pp_const pe) c + | Cast (typ, e) -> F.fprintf f "(%a)%a" pp_t typ pp_exp e + | UnOp (op, e, _) -> F.fprintf f "%s%a" (str_unop op) pp_exp e + | BinOp (op, Const c, e2) when !Config.smt_output -> print_binop_stm_output (Const c) op e2 + | BinOp (op, e1, e2) -> F.fprintf f "(%a %s %a)" pp_exp e1 (str_binop pe op) pp_exp e2 + | Lvar pv -> pp_pvar pe f pv + | Lfield (e, fld, typ) -> F.fprintf f "%a.%a" pp_exp e Ident.pp_fieldname fld + | Lindex (e1, e2) -> F.fprintf f "%a[%a]" pp_exp e1 pp_exp e2 + | Sizeof (t, s) -> F.fprintf f "sizeof(%a%a)" pp_t t Subtype.pp s + end); + color_post_wrapper changed pe0 f + +and pp_exp pe f e = + _pp_exp pe (pp_typ pe) f e +and pp_exp_full pe f e = + _pp_exp pe (pp_typ_full pe) f e + +(** Convert an expression to a string *) +and exp_to_string e = pp_to_string (pp_exp pe_text) e + +let typ_to_string typ = + let pp fmt () = pp_typ_full Utils.pe_text fmt typ in + Utils.pp_to_string pp () + +(** dump a type with all the details. *) +let d_typ_full (t: typ) = L.add_print_action (L.PTtyp_full, Obj.repr t) + +(** dump a list of types. *) +let d_typ_list (tl: typ list) = L.add_print_action (L.PTtyp_list, Obj.repr tl) + +let pp_pair pe f ((fld: Ident.fieldname), (t: typ)) = + F.fprintf f "%a %a" (pp_typ pe) t Ident.pp_fieldname fld + +(** dump an expression. *) +let d_exp (e: exp) = L.add_print_action (L.PTexp, Obj.repr e) + +(** Pretty print a list of expressions. *) +let pp_exp_list pe f expl = + (pp_seq (pp_exp pe)) f expl + +(** dump a list of expressions. *) +let d_exp_list (el: exp list) = L.add_print_action (L.PTexp_list, Obj.repr el) + +let pp_texp pe f = function + | Sizeof (t, s) -> F.fprintf f "%a%a" (pp_typ pe) t Subtype.pp s + | e -> (pp_exp pe) f e + +(** Pretty print a type with all the details. *) +let pp_texp_full pe f = function + | Sizeof (t, s) -> F.fprintf f "%a%a" (pp_typ_full pe) t Subtype.pp s + | e -> (_pp_exp pe) (pp_typ_full pe) f e + +(** Dump a type expression with all the details. *) +let d_texp_full (te: exp) = L.add_print_action (L.PTtexp_full, Obj.repr te) + +(** Pretty print an offset *) +let pp_offset pe f = function + | Off_fld (fld, typ) -> F.fprintf f "%a" Ident.pp_fieldname fld + | Off_index exp -> F.fprintf f "%a" (pp_exp pe) exp + +(** dump an offset. *) +let d_offset (off: offset) = L.add_print_action (L.PToff, Obj.repr off) + +(** Pretty print a list of offsets *) +let rec pp_offset_list pe f = function + | [] -> () + | [off1; off2] -> F.fprintf f "%a.%a" (pp_offset pe) off1 (pp_offset pe) off2 + | off:: off_list -> F.fprintf f "%a.%a" (pp_offset pe) off (pp_offset_list pe) off_list + +(** Dump a list of offsets *) +let d_offset_list (offl: offset list) = L.add_print_action (L.PToff_list, Obj.repr offl) + +let pp_exp_typ pe f (e, t) = + F.fprintf f "%a:%a" (pp_exp pe) e (pp_typ pe) t + +(** Get the location of the instruction *) +let instr_get_loc = function + | Letderef (_, _, _, loc) + | Set (_, _, _, loc) + | Prune (_, loc, _, _) + | Call (_, _, _, loc, _) + | Nullify (_, loc, _) + | Abstract loc + | Remove_temps (_, loc) + | Stackop (_, loc) + | Declare_locals (_, loc) + | Goto_node (_, loc) -> + loc + +(** get the expressions occurring in the instruction *) +let rec instr_get_exps = function + | Letderef (id, e, _, _) -> + [Var id; e] + | Set (e1, _, e2, _) -> + [e1; e2] + | Prune (cond, _, _, _) -> + [cond] + | Call (ret_ids, e, _, _, _) -> + e :: (list_map (fun id -> Var id)) ret_ids + | Nullify (pvar, _, _) -> + [Lvar pvar] + | Abstract _ -> + [] + | Remove_temps (temps, _) -> + list_map (fun id -> Var id) temps + | Stackop _ -> + [] + | Declare_locals _ -> + [] + | Goto_node (e, _) -> + [e] + +(** Pretty print call flags *) +let pp_call_flags f cf = + if cf.cf_virtual then F.fprintf f " virtual"; + if cf.cf_noreturn then F.fprintf f " noreturn" + +(** Pretty print an instruction. *) +let rec pp_instr pe0 f instr = + let pe, changed = color_pre_wrapper pe0 f instr in + (match instr with + | Letderef (id, e, t, loc) -> F.fprintf f "%a=*%a:%a %a" (Ident.pp pe) id (pp_exp pe) e (pp_typ pe) t pp_loc loc + | Set (e1, t, e2, loc) -> F.fprintf f "*%a:%a=%a %a" (pp_exp pe) e1 (pp_typ pe) t (pp_exp pe) e2 pp_loc loc + | Prune (cond, loc, true_branch, ik) -> + F.fprintf f "PRUNE(%a, %b); %a" (pp_exp pe) cond true_branch pp_loc loc + | Call (ret_ids, e, arg_ts, loc, cf) -> + (match ret_ids with + | [] -> () + | _ -> F.fprintf f "%a=" (pp_comma_seq (Ident.pp pe)) ret_ids); + F.fprintf f "%a(%a)%a %a" (pp_exp pe) e (pp_comma_seq (pp_exp_typ pe)) (arg_ts) pp_call_flags cf pp_loc loc + | Nullify (pvar, loc, deallocate) -> + F.fprintf f "NULLIFY(%a,%b); %a" (pp_pvar pe) pvar deallocate pp_loc loc + | Abstract loc -> + F.fprintf f "APPLY_ABSTRACTION; %a" pp_loc loc + | Remove_temps (temps, loc) -> + F.fprintf f "REMOVE_TEMPS(%a); %a" (Ident.pp_list pe) temps pp_loc loc + | Stackop (stackop, loc) -> + let s = match stackop with + | Push -> "Push" + | Swap -> "Swap" + | Pop -> "Pop" in + F.fprintf f "STACKOP.%s; %a" s pp_loc loc + | Declare_locals (ptl, loc) -> + (* let pp_pvar_typ fmt (pvar, typ) = F.fprintf fmt "%a:%a" (pp_pvar pe) pvar (pp_typ_full pe) typ in *) + let pp_pvar_typ fmt (pvar, typ) = F.fprintf fmt "%a" (pp_pvar pe) pvar in + F.fprintf f "DECLARE_LOCALS(%a); %a" (pp_comma_seq pp_pvar_typ) ptl pp_loc loc + | Goto_node (e, loc) -> + F.fprintf f "Goto_node %a %a" (pp_exp pe) e pp_loc loc + ); + color_post_wrapper changed pe0 f + +let has_block_prefix s = + match Str.split_delim (Str.regexp_string Config.anonymous_block_prefix) s with + | s1:: s2:: _ -> true + | _ -> false + +(** Check if a pvar is a local pointing to a block in objc *) +let is_block_pvar pvar = + has_block_prefix (Mangled.to_string (pvar_get_name pvar)) + +(** Check if type is a type for a block in objc *) +let is_block_type typ = + has_block_prefix (typ_to_string typ) + +(** Iterate over all the subtypes in the type (including the type itself) *) +let rec typ_iter_types (f : typ -> unit) typ = + f typ; + match typ with + | Tvar _ + | Tint _ + | Tfloat _ + | Tvoid + | Tfun _ -> + () + | Tptr (t', pk) -> + typ_iter_types f t' + | Tstruct (ftal, sftal, csu, nameo, supers, def_mthds, iann) -> + list_iter (fun (_, t, _) -> typ_iter_types f t) ftal + | Tarray (t, e) -> + typ_iter_types f t; + exp_iter_types f e + | Tenum econsts -> + () + +(** Iterate over all the subtypes in the type (including the type itself) *) +and exp_iter_types f e = + match e with + | Var id -> () + | Const (Cexn e1) -> + exp_iter_types f e1 + | Const (Ctuple el) -> + list_iter (exp_iter_types f) el + | Const _ -> + () + | Cast (t, e1) -> + typ_iter_types f t; + exp_iter_types f e1 + | UnOp (op, e1, typo) -> + exp_iter_types f e1; + (match typo with + | Some t -> typ_iter_types f t + | None -> ()) + | BinOp (op, e1, e2) -> + exp_iter_types f e1; + exp_iter_types f e2 + | Lvar id -> + () + | Lfield (e1, fld, typ) -> + exp_iter_types f e1; + typ_iter_types f typ + | Lindex (e1, e2) -> + exp_iter_types f e1; + exp_iter_types f e2 + | Sizeof (t, s) -> + typ_iter_types f t + +(** Iterate over all the types (and subtypes) in the instruction *) +let rec instr_iter_types f instr = match instr with + | Letderef (id, e, t, loc) -> + exp_iter_types f e; + typ_iter_types f t + | Set (e1, t, e2, loc) -> + exp_iter_types f e1; + typ_iter_types f t; + exp_iter_types f e2 + | Prune (cond, loc, true_branch, ik) -> + exp_iter_types f cond + | Call (ret_ids, e, arg_ts, loc, cf) -> + exp_iter_types f e; + list_iter (fun (e, t) -> exp_iter_types f e; typ_iter_types f t) arg_ts + | Nullify (pvar, loc, deallocate) -> + () + | Abstract loc -> + () + | Remove_temps (temps, loc) -> + () + | Stackop (stackop, loc) -> + () + | Declare_locals (ptl, loc) -> + list_iter (fun (_, t) -> typ_iter_types f t) ptl + | Goto_node _ -> + () + +(** Dump an instruction. *) +let d_instr (i: instr) = L.add_print_action (L.PTinstr, Obj.repr i) + +let rec pp_instr_list pe f = function + | [] -> F.fprintf f "" + | i:: is -> F.fprintf f "%a;@\n%a" (pp_instr pe) i (pp_instr_list pe) is + +(** Dump a list of instructions. *) +let d_instr_list (il: instr list) = L.add_print_action (L.PTinstr_list, Obj.repr il) + +let pp_atom pe0 f a = + let pe, changed = color_pre_wrapper pe0 f a in + begin match a with + | Aeq (BinOp(op, e1, e2), Const (Cint i)) when Int.isone i -> + (match pe.pe_kind with + | PP_TEXT | PP_HTML -> + F.fprintf f "%a" (pp_exp pe) (BinOp(op, e1, e2)) + | PP_LATEX -> + F.fprintf f "%a" (pp_exp pe) (BinOp(op, e1, e2)) + ) + | Aeq (e1, e2) -> + (match pe.pe_kind with + | PP_TEXT | PP_HTML -> + F.fprintf f "%a = %a" (pp_exp pe) e1 (pp_exp pe) e2 + | PP_LATEX -> + F.fprintf f "%a{=}%a" (pp_exp pe) e1 (pp_exp pe) e2) + | Aneq ((Const (Cattribute a) as ea), e) + | Aneq (e, (Const (Cattribute a) as ea)) -> + F.fprintf f "%a(%a)" (pp_exp pe) ea (pp_exp pe) e + | Aneq (e1, e2) -> + (match pe.pe_kind with + | PP_TEXT | PP_HTML -> + F.fprintf f "%a != %a" (pp_exp pe) e1 (pp_exp pe) e2 + | PP_LATEX -> + F.fprintf f "%a{\\neq}%a" (pp_exp pe) e1 (pp_exp pe) e2) + end; + color_post_wrapper changed pe0 f + +(** dump an atom *) +let d_atom (a: atom) = L.add_print_action (L.PTatom, Obj.repr a) + +let pp_lseg_kind f = function + | Lseg_NE -> F.fprintf f "ne" + | Lseg_PE -> F.fprintf f "" + +(** Print a *-separated sequence. *) +let rec pp_star_seq pp f = function + | [] -> () + | [x] -> F.fprintf f "%a" pp x + | x:: l -> F.fprintf f "%a * %a" pp x (pp_star_seq pp) l + +(********* START OF MODULE Predicates **********) +(** Module Predicates records the occurrences of predicates as parameters +of (doubly -)linked lists and Epara. Provides unique numbering for predicates and an iterator. *) +module Predicates : sig +(** predicate environment *) + type env + (** create an empty predicate environment *) + val empty_env : unit -> env + (** return true if the environment is empty *) + val is_empty : env -> bool + (** return the id of the hpara *) + val get_hpara_id : env -> hpara -> int + (** return the id of the hpara_dll *) + val get_hpara_dll_id : env -> hpara_dll -> int + (** [iter env f f_dll] iterates [f] and [f_dll] on all the hpara and hpara_dll, + passing the unique id to the functions. The iterator can only be used once. *) + val iter : env -> (int -> hpara -> unit) -> (int -> hpara_dll -> unit) -> unit + (** Process one hpred, updating the predicate environment *) + val process_hpred : env -> hpred -> unit +end = struct + + (** hash tables for hpara *) + module HparaHash = Hashtbl.Make (struct + type t = hpara + let equal = hpara_equal + let hash = Hashtbl.hash + end) + + (** hash tables for hpara_dll *) + module HparaDllHash = Hashtbl.Make (struct + type t = hpara_dll + let equal = hpara_dll_equal + let hash = Hashtbl.hash + end) + + (** Map each visited hpara to a unique number and a boolean denoting whether it has been emitted, + also keep a list of hparas still to be emitted. Same for hpara_dll. *) + type env = + { + mutable num: int; + hash: (int * bool) HparaHash.t; + mutable todo: hpara list; + hash_dll: (int * bool) HparaDllHash.t; + mutable todo_dll: hpara_dll list; + } + + (** return true if the environment is empty *) + let is_empty env = env.num = 0 + + (** return the id of the hpara *) + let get_hpara_id env hpara = + fst (HparaHash.find env.hash hpara) + + (** return the id of the hpara_dll *) + let get_hpara_dll_id env hpara_dll = + fst (HparaDllHash.find env.hash_dll hpara_dll) + + (** Process one hpara, updating the map from hparas to numbers, and the todo list *) + let process_hpara env hpara = + if not (HparaHash.mem env.hash hpara) then + (HparaHash.add env.hash hpara (env.num, false); + env.num <- env.num + 1; + env.todo <- env.todo @ [hpara]) + + (** Process one hpara_dll, updating the map from hparas to numbers, and the todo list *) + let process_hpara_dll env hpara_dll = + if not (HparaDllHash.mem env.hash_dll hpara_dll) + then + (HparaDllHash.add env.hash_dll hpara_dll (env.num, false); + env.num <- env.num + 1; + env.todo_dll <- env.todo_dll @ [hpara_dll]) + + (** Process a sexp, updating env *) + let rec process_sexp env = function + | Eexp _ -> () + | Earray (_, esel, _) -> + list_iter (fun (e, se) -> process_sexp env se) esel + | Estruct (fsel, _) -> + list_iter (fun (f, se) -> process_sexp env se) fsel + + (** Process one hpred, updating env *) + let rec process_hpred env = function + | Hpointsto (_, se, _) -> + process_sexp env se + | Hlseg (_, hpara, _, _, _) -> + list_iter (process_hpred env) hpara.body; + process_hpara env hpara + | Hdllseg(_, hpara_dll, _, _, _, _, _) -> + list_iter (process_hpred env) hpara_dll.body_dll; + process_hpara_dll env hpara_dll + + (** create an empty predicate environment *) + let empty_env () = + { + num = 0; + hash = HparaHash.create 3; + todo =[]; + hash_dll = HparaDllHash.create 3; + todo_dll =[]; + } + + (** iterator for predicates which are marked as todo in env, unless they have been visited already. + This can in turn extend the todo list for the nested predicates, which are then visited as well. + Can be applied only once, as it destroys the todo list *) + let iter (env: env) f f_dll = + while env.todo != [] || env.todo_dll != [] do + if env.todo != [] then + begin + let hpara = list_hd env.todo in + let () = env.todo <- list_tl env.todo in + let (n, emitted) = HparaHash.find env.hash hpara in + if not emitted then f n hpara + end + else if env.todo_dll != [] then + begin + let hpara_dll = list_hd env.todo_dll in + let () = env.todo_dll <- list_tl env.todo_dll in + let (n, emitted) = HparaDllHash.find env.hash_dll hpara_dll in + if not emitted then f_dll n hpara_dll + end + done +end +(********* END OF MODULE Predicates **********) + +let pp_texp_simple pe = match pe.pe_opt with + | PP_SIM_DEFAULT -> pp_texp pe + | PP_SIM_WITH_TYP -> pp_texp_full pe + +let inst_abstraction = Iabstraction +let inst_actual_precondition = Iactual_precondition +let inst_alloc = Ialloc +let inst_formal = Iformal (None, false) (** for formal parameters *) +let inst_initial = Iinitial (** for initial values *) +let inst_lookup = Ilookup +let inst_none = Inone +let inst_nullify = Inullify +let inst_rearrange b loc pos = Irearrange (Some b, false, loc.line, pos) +let inst_taint = Itaint +let inst_update loc pos = Iupdate (None, false, loc.line, pos) + +(** update the location of the instrumentation *) +let inst_new_loc loc inst = match inst with + | Iabstraction -> inst + | Iactual_precondition -> inst + | Ialloc -> inst + | Iformal (zf, ncf) -> inst + | Iinitial -> inst + | Ilookup -> inst + | Inone -> inst + | Inullify -> inst + | Irearrange (zf, ncf, n, pos) -> Irearrange (zf, ncf, loc.line, pos) + | Itaint -> inst + | Iupdate (zf, ncf, n, pos) -> Iupdate (zf, ncf, loc.line, pos) + | Ireturn_from_call n -> Ireturn_from_call loc.line + +(** return a string representing the inst *) +let inst_to_string inst = + let zero_flag_to_string = function + | Some true -> "(z)" + | _ -> "" in + let null_case_flag_to_string ncf = + if ncf then "(ncf)" else "" in + match inst with + | Iabstraction -> "abstraction" + | Iactual_precondition -> "actual_precondition" + | Ialloc -> "alloc" + | Iformal (zf, ncf) -> + "formal" ^ zero_flag_to_string zf ^ null_case_flag_to_string ncf + | Iinitial -> "initial" + | Ilookup -> "lookup" + | Inone -> "none" + | Inullify -> "nullify" + | Irearrange (zf, ncf, n, _) -> + "rearrange:" ^ zero_flag_to_string zf ^ null_case_flag_to_string ncf ^ string_of_int n + | Itaint -> "taint" + | Iupdate (zf, ncf, n, _) -> + "update:" ^ zero_flag_to_string zf ^ null_case_flag_to_string ncf ^ string_of_int n + | Ireturn_from_call n -> "return_from_call: " ^ string_of_int n + +(** join of instrumentations *) +let inst_partial_join inst1 inst2 = + let fail () = + L.d_strln ("inst_partial_join failed on " ^ inst_to_string inst1 ^ " " ^ inst_to_string inst2); + raise Fail in + if inst1 = inst2 then inst1 + else match inst1, inst2 with + | _, Inone | Inone, _ -> inst_none + | _, Ialloc | Ialloc, _ -> fail () + | _, Iinitial | Iinitial, _ -> fail () + | _, Iupdate _ | Iupdate _, _ -> fail () + | _ -> inst_none + +(** meet of instrumentations *) +let inst_partial_meet inst1 inst2 = + if inst1 = inst2 then inst1 else inst_none + +(** Return the zero flag of the inst *) +let inst_zero_flag = function + | Iabstraction -> None + | Iactual_precondition -> None + | Ialloc -> None + | Iformal (zf, ncf) -> zf + | Iinitial -> None + | Ilookup -> None + | Inone -> None + | Inullify -> None + | Irearrange (zf, ncf, n, _) -> zf + | Itaint -> None + | Iupdate (zf, ncf, n, _) -> zf + | Ireturn_from_call _ -> None + +(** Set the null case flag of the inst. *) +let inst_set_null_case_flag = function + | Iformal (zf, false) -> + Iformal (zf, true) + | Irearrange (zf, false, n, pos) -> + Irearrange (zf, true, n, pos) + | Iupdate (zf, false, n, pos) -> + Iupdate (zf, true, n, pos) + | inst -> inst + +(** Get the null case flag of the inst. *) +let inst_get_null_case_flag = function + | Iupdate (_, ncf, _, _) -> Some ncf + | _ -> None + +(** Update [inst_old] to [inst_new] preserving the zero flag *) +let update_inst inst_old inst_new = + let combine_zero_flags z1 z2 = match z1, z2 with + | Some b1, Some b2 -> Some (b1 || b2) + | Some b, None -> Some b + | None, Some b -> Some b + | None, None -> None in + match inst_new with + | Iabstraction -> inst_new + | Iactual_precondition -> inst_new + | Ialloc -> inst_new + | Iformal (zf, ncf) -> + let zf' = combine_zero_flags (inst_zero_flag inst_old) zf in + Iformal (zf', ncf) + | Iinitial -> inst_new + | Ilookup -> inst_new + | Inone -> inst_new + | Inullify -> inst_new + | Irearrange (zf, ncf, n, pos) -> + let zf' = combine_zero_flags (inst_zero_flag inst_old) zf in + Irearrange (zf', ncf, n, pos) + | Itaint -> inst_new + | Iupdate (zf, ncf, n, pos) -> + let zf' = combine_zero_flags (inst_zero_flag inst_old) zf in + Iupdate (zf', ncf, n, pos) + | Ireturn_from_call _ -> inst_new + +let string_of_language = function + | Java -> "Java" + | C_CPP -> "C_CPP" + +(** describe an instrumentation with a string *) +let pp_inst pe f inst = + let str = inst_to_string inst in + if pe.pe_kind == PP_HTML then + F.fprintf f " %a%s%a" Io_infer.Html.pp_start_color Orange str Io_infer.Html.pp_end_color () + else + F.fprintf f "%s%s%s" (str_binop pe Lt) str (str_binop pe Gt) + +let pp_inst_if_trace pe f inst = + if !Config.trace_error then pp_inst pe f inst + +(** pretty print a strexp with an optional predicate env *) +let rec pp_sexp_env pe0 envo f se = + let pe, changed = color_pre_wrapper pe0 f se in + begin + match se with + | Eexp (e, inst) -> + F.fprintf f "%a%a" (pp_exp pe) e (pp_inst_if_trace pe) inst + | Estruct (fel, inst) -> + begin + match pe.pe_kind with + | PP_TEXT | PP_HTML -> + let pp_diff f (n, se) = F.fprintf f "%a:%a" Ident.pp_fieldname n (pp_sexp_env pe envo) se in + F.fprintf f "{%a}%a" (pp_seq_diff pp_diff pe) fel (pp_inst_if_trace pe) inst + | PP_LATEX -> + let pp_diff f (n, se) = F.fprintf f "%a:%a" (Ident.pp_fieldname_latex Latex.Boldface) n (pp_sexp_env pe envo) se in + F.fprintf f "\\{%a\\}%a" (pp_seq_diff pp_diff pe) fel (pp_inst_if_trace pe) inst + end + | Earray (size, nel, inst) -> + let pp_diff f (i, se) = F.fprintf f "%a:%a" (pp_exp pe) i (pp_sexp_env pe envo) se in + F.fprintf f "[%a|%a]%a" (pp_exp pe) size (pp_seq_diff pp_diff pe) nel (pp_inst_if_trace pe) inst + end; + color_post_wrapper changed pe0 f + +(** Pretty print an hpred with an optional predicate env *) +and pp_hpred_env pe0 envo f hpred = + let pe, changed = color_pre_wrapper pe0 f hpred in + begin match hpred with + | Hpointsto (e, se, te) -> + let pe' = match (e, se) with + | Lvar pvar, Eexp (Var id, inst) when not (pvar_is_global pvar) -> + { pe with pe_obj_sub = None } (* dont use obj sub on the var defining it *) + | _ -> pe in + (match pe'.pe_kind with + | PP_TEXT | PP_HTML -> + F.fprintf f "%a|->%a:%a" (pp_exp pe') e (pp_sexp_env pe' envo) se (pp_texp_simple pe') te + | PP_LATEX -> + F.fprintf f "%a\\mapsto %a" (pp_exp pe') e (pp_sexp_env pe' envo) se) + | Hlseg (k, hpara, e1, e2, elist) -> + (match pe.pe_kind with + | PP_TEXT | PP_HTML -> + F.fprintf f "lseg%a(%a,%a,[%a],%a)" + pp_lseg_kind k (pp_exp pe) e1 (pp_exp pe) e2 (pp_comma_seq (pp_exp pe)) elist (pp_hpara_env pe envo) hpara + | PP_LATEX -> + F.fprintf f "\\textsf{lseg}_{%a}(%a,%a,[%a],%a)" + pp_lseg_kind k (pp_exp pe) e1 (pp_exp pe) e2 (pp_comma_seq (pp_exp pe)) elist (pp_hpara_env pe envo) hpara) + | Hdllseg (k, hpara_dll, iF, oB, oF, iB, elist) -> + (match pe.pe_kind with + | PP_TEXT | PP_HTML -> + F.fprintf f "dllseg%a(%a,%a,%a,%a,[%a],%a)" + pp_lseg_kind k (pp_exp pe) iF (pp_exp pe) oB (pp_exp pe) oF (pp_exp pe) iB (pp_comma_seq (pp_exp pe)) elist (pp_hpara_dll_env pe envo) hpara_dll + | PP_LATEX -> + F.fprintf f "\\textsf{dllseg}_{%a}(%a,%a,%a,%a,[%a],%a)" + pp_lseg_kind k (pp_exp pe) iF (pp_exp pe) oB (pp_exp pe) oF (pp_exp pe) iB (pp_comma_seq (pp_exp pe)) elist (pp_hpara_dll_env pe envo) hpara_dll) + end; + color_post_wrapper changed pe0 f + +and pp_hpara_env pe envo f hpara = match envo with + | None -> + let (r, n, svars, evars, b) = (hpara.root, hpara.next, hpara.svars, hpara.evars, hpara.body) in + F.fprintf f "lam [%a,%a,%a]. exists [%a]. %a" + (Ident.pp pe) r + (Ident.pp pe) n + (pp_seq (Ident.pp pe)) svars + (pp_seq (Ident.pp pe)) evars + (pp_star_seq (pp_hpred_env pe envo)) b + | Some env -> + F.fprintf f "P%d" (Predicates.get_hpara_id env hpara) + +and pp_hpara_dll_env pe envo f hpara_dll = match envo with + | None -> + let (iF, oB, oF, svars, evars, b) = (hpara_dll.cell, hpara_dll.blink, hpara_dll.flink, hpara_dll.svars_dll, hpara_dll.evars_dll, hpara_dll.body_dll) in + F.fprintf f "lam [%a,%a,%a,%a]. exists [%a]. %a" + (Ident.pp pe) iF + (Ident.pp pe) oB + (Ident.pp pe) oF + (pp_seq (Ident.pp pe)) svars + (pp_seq (Ident.pp pe)) evars + (pp_star_seq (pp_hpred_env pe envo)) b + | Some env -> + F.fprintf f "P%d" (Predicates.get_hpara_dll_id env hpara_dll) + +(** pretty print a strexp *) +let pp_sexp pe f = pp_sexp_env pe None f + +(** pretty print a hpara *) +let pp_hpara pe f = pp_hpara_env pe None f + +(** pretty print a hpara_dll *) +let pp_hpara_dll pe f = pp_hpara_dll_env pe None f + +(** pretty print a hpred *) +let pp_hpred pe f = pp_hpred_env pe None f + +(** dump a strexp. *) +let d_sexp (se: strexp) = L.add_print_action (L.PTsexp, Obj.repr se) + +(** Pretty print a list of expressions. *) +let pp_sexp_list pe f sel = + F.fprintf f "%a" (pp_seq (fun f se -> F.fprintf f "%a" (pp_sexp pe) se)) sel + +(** dump a list of expressions. *) +let d_sexp_list (sel: strexp list) = L.add_print_action (L.PTsexp_list, Obj.repr sel) + +let rec pp_hpara_list pe f = function + | [] -> () + | [para] -> + F.fprintf f "PRED: %a" (pp_hpara pe) para + | para:: paras -> + F.fprintf f "PRED: %a@\n@\n%a" (pp_hpara pe) para (pp_hpara_list pe) paras + +let rec pp_hpara_dll_list pe f = function + | [] -> () + | [para] -> + F.fprintf f "PRED: %a" (pp_hpara_dll pe) para + | para:: paras -> + F.fprintf f "PRED: %a@\n@\n%a" (pp_hpara_dll pe) para (pp_hpara_dll_list pe) paras + +(** dump a hpred. *) +let d_hpred (hpred: hpred) = L.add_print_action (L.PThpred, Obj.repr hpred) + +(** {2 Functions for traversing SIL data types} *) + +let rec strexp_expmap (f: exp * inst option -> exp * inst option) = + let fe e = fst (f (e, None)) in + let fei (e, inst) = match f (e, Some inst) with + | e', None -> (e', inst) + | e', Some inst' -> (e', inst') in + function + | Eexp (e, inst) -> + let e', inst' = fei (e, inst) in + Eexp (e', inst') + | Estruct (fld_se_list, inst) -> + let f_fld_se (fld, se) = (fld, strexp_expmap f se) in + Estruct (list_map f_fld_se fld_se_list, inst) + | Earray (size, idx_se_list, inst) -> + let size' = fe size in + let f_idx_se (idx, se) = + let idx' = fe idx in + (idx', strexp_expmap f se) in + Earray (size', list_map f_idx_se idx_se_list, inst) + +let hpred_expmap (f: exp * inst option -> exp * inst option) = + let fe e = fst (f (e, None)) in + function + | Hpointsto (e, se, te) -> + let e' = fe e in + let se' = strexp_expmap f se in + let te' = fe te in + Hpointsto(e', se', te') + | Hlseg (k, hpara, root, next, shared) -> + let root' = fe root in + let next' = fe next in + let shared' = list_map fe shared in + Hlseg (k, hpara, root', next', shared') + | Hdllseg (k, hpara, iF, oB, oF, iB, shared) -> + let iF' = fe iF in + let oB' = fe oB in + let oF' = fe oF in + let iB' = fe iB in + let shared' = list_map fe shared in + Hdllseg (k, hpara, iF', oB', oF', iB', shared') + +let rec strexp_instmap (f: inst -> inst) strexp = match strexp with + | Eexp (e, inst) -> + Eexp (e, f inst) + | Estruct (fld_se_list, inst) -> + let f_fld_se (fld, se) = (fld, strexp_instmap f se) in + Estruct (list_map f_fld_se fld_se_list, f inst) + | Earray (size, idx_se_list, inst) -> + let f_idx_se (idx, se) = + (idx, strexp_instmap f se) in + Earray (size, list_map f_idx_se idx_se_list, f inst) + +and hpara_instmap (f: inst -> inst) hpara = + { hpara with body = list_map (hpred_instmap f) hpara.body } + +and hpara_dll_instmap (f: inst -> inst) hpara_dll = + { hpara_dll with body_dll = list_map (hpred_instmap f) hpara_dll.body_dll } + +and hpred_instmap (fn: inst -> inst) (hpred: hpred) : hpred = match hpred with + | Hpointsto (e, se, te) -> + let se' = strexp_instmap fn se in + Hpointsto(e, se', te) + | Hlseg (k, hpara, e, f, el) -> + Hlseg (k, hpara_instmap fn hpara, e, f, el) + | Hdllseg (k, hpar_dll, e, f, g, h, el) -> + Hdllseg (k, hpara_dll_instmap fn hpar_dll, e, f, g, h, el) + +let hpred_list_expmap (f: exp * inst option -> exp * inst option) (hlist: hpred list) = + list_map (hpred_expmap f) hlist + +let atom_expmap (f: exp -> exp) = function + | Aeq (e1, e2) -> Aeq (f e1, f e2) + | Aneq (e1, e2) -> Aneq (f e1, f e2) + +let atom_list_expmap (f: exp -> exp) (alist: atom list) = + list_map (atom_expmap f) alist + +(** {2 Function for computing lexps in sigma} *) + +let hpred_get_lexp acc = function + | Hpointsto(e, _, _) -> e:: acc + | Hlseg(_, _, e, _, _) -> e:: acc + | Hdllseg(_, _, e1, _, _, e2, _) -> e1:: e2:: acc + +let hpred_list_get_lexps (filter: exp -> bool) (hlist: hpred list) : exp list = + let lexps = list_fold_left hpred_get_lexp [] hlist in + list_filter filter lexps + +(** {2 Utility Functions for Expressions} *) + +let unsome_typ s = function + | Some default_typ -> default_typ + | None -> + L.err "No default typ in %s@." s; + assert false + +(** Turn an expression representing a type into the type it represents +If not a sizeof, return the default type if given, otherwise raise an exception *) +let texp_to_typ default_opt = function + | Sizeof (t, _) -> t + | t -> + unsome_typ "texp_to_typ" default_opt + +(** If a struct type with field f, return the type of f. +If not, return the default type if given, otherwise raise an exception *) +let struct_typ_fld default_opt f = + let def () = unsome_typ "struct_typ_fld" default_opt in + function + | Tstruct (ftal, sftal, _, _, _, _, _) -> + (try (fun (x, y, z) -> y) (list_find (fun (_f, t, ann) -> Ident.fieldname_equal _f f) ftal) + with Not_found -> def ()) + | _ -> def () + +(** If an array type, return the type of the element. +If not, return the default type if given, otherwise raise an exception *) +let array_typ_elem default_opt = function + | Tarray (t_el, _) -> t_el + | t -> + unsome_typ "array_typ_elem" default_opt + +(** Return the root of [lexp]. *) +let rec root_of_lexp lexp = match lexp with + | Var _ -> lexp + | Const _ -> lexp + | Cast (t, e) -> root_of_lexp e + | UnOp _ | BinOp _ -> lexp + | Lvar _ -> lexp + | Lfield(e, _, _) -> root_of_lexp e + | Lindex(e, _) -> root_of_lexp e + | Sizeof _ -> lexp + +(** Checks whether an expression denotes a location by pointer arithmetic. +Currently, catches array - indexing expressions such as a[i] only. *) +let rec exp_pointer_arith = function + | Lfield (e, _, _) -> exp_pointer_arith e + | Lindex _ -> true + | _ -> false + +let exp_get_undefined footprint = + Var (Ident.create_fresh (if footprint then Ident.kfootprint else Ident.kprimed)) + +(** Create integer constant *) +let exp_int i = Const (Cint i) + +(** Create float constant *) +let exp_float v = Const (Cfloat v) + +(** Integer constant 0 *) +let exp_zero = exp_int Int.zero + +(** Null constant *) +let exp_null = exp_int Int.null + +(** Integer constant 1 *) +let exp_one = exp_int Int.one + +(** Integer constant -1 *) +let exp_minus_one = exp_int Int.minus_one + +(** Create integer constant corresponding to the boolean value *) +let exp_bool b = + if b then exp_one else exp_zero + +(** Create expresstion [e1 == e2] *) +let exp_eq e1 e2 = + BinOp (Eq, e1, e2) + +(** Create expresstion [e1 != e2] *) +let exp_ne e1 e2 = + BinOp (Ne, e1, e2) + +(** Create expression [e1 <= e2] *) +let exp_le e1 e2 = + BinOp (Le, e1, e2) + +(** Create expression [e1 < e2] *) +let exp_lt e1 e2 = + BinOp (Lt, e1, e2) + +(** {2 Functions for computing program variables} *) + +let rec exp_fpv = function + | Var id -> [] + | Const (Cexn e) -> exp_fpv e + | Const (Ctuple el) -> exp_list_fpv el + | Const _ -> [] + | Cast (_, e) | UnOp (_, e, _) -> exp_fpv e + | BinOp (_, e1, e2) -> exp_fpv e1 @ exp_fpv e2 + | Lvar name -> [name] + | Lfield (e, _, _) -> exp_fpv e + | Lindex (e1, e2) -> exp_fpv e1 @ exp_fpv e2 + | Sizeof _ -> [] + +and exp_list_fpv el = list_flatten (list_map exp_fpv el) + +let atom_fpv = function + | Aeq (e1, e2) -> exp_fpv e1 @ exp_fpv e2 + | Aneq (e1, e2) -> exp_fpv e1 @ exp_fpv e2 + +let rec strexp_fpv = function + | Eexp (e, inst) -> exp_fpv e + | Estruct (fld_se_list, inst) -> + let f (_, se) = strexp_fpv se in + list_flatten (list_map f fld_se_list) + | Earray (size, idx_se_list, inst) -> + let fpv_in_size = exp_fpv size in + let f (idx, se) = exp_fpv idx @ strexp_fpv se in + fpv_in_size @ list_flatten (list_map f idx_se_list) + +and hpred_fpv = function + | Hpointsto (base, se, te) -> + exp_fpv base @ strexp_fpv se @ exp_fpv te + | Hlseg (_, para, e1, e2, elist) -> + let fpvars_in_elist = exp_list_fpv elist in + hpara_fpv para (* This set has to be empty. *) + @ exp_fpv e1 + @ exp_fpv e2 + @ fpvars_in_elist + | Hdllseg (_, para, e1, e2, e3, e4, elist) -> + let fpvars_in_elist = exp_list_fpv elist in + hpara_dll_fpv para (* This set has to be empty. *) + @ exp_fpv e1 + @ exp_fpv e2 + @ exp_fpv e3 + @ exp_fpv e4 + @ fpvars_in_elist + +(** hpara should not contain any program variables. +This is because it might cause problems when we do interprocedural +analysis. In interprocedural analysis, we should consider the issue +of scopes of program variables. *) +and hpara_fpv para = + let fpvars_in_body = list_flatten (list_map hpred_fpv para.body) in + match fpvars_in_body with + | [] -> [] + | _ -> assert false + +(** hpara_dll should not contain any program variables. +This is because it might cause problems when we do interprocedural +analysis. In interprocedural analysis, we should consider the issue +of scopes of program variables. *) +and hpara_dll_fpv para = + let fpvars_in_body = list_flatten (list_map hpred_fpv para.body_dll) in + match fpvars_in_body with + | [] -> [] + | _ -> assert false + +(** {2 Functions for computing free non-program variables} *) + +(** Type of free variables. These include primed, normal and footprint variables. We keep a count of how many types the variables appear. *) +type fav = Ident.t list ref + +let fav_new () = + ref [] + +(** Emptyness check. *) +let fav_is_empty fav = match !fav with + | [] -> true + | _ -> false + +(** Check whether a predicate holds for all elements. *) +let fav_for_all fav predicate = + list_for_all predicate !fav + +(** Check whether a predicate holds for some elements. *) +let fav_exists fav predicate = + list_exists predicate !fav + +(** flag to indicate whether fav's are stored in duplicate form -- only to be used with fav_to_list *) +let fav_duplicates = ref false + +(** extend [fav] with a [id] *) +let (++) fav id = + if !fav_duplicates || not (list_exists (Ident.equal id) !fav) then fav := id::!fav + +(** extend [fav] with ident list [idl] *) +let (+++) fav idl = + list_iter (fun id -> fav ++ id) idl + +(** add identity lists to fav *) +let ident_list_fav_add idl fav = + fav +++ idl + +(** Convert a list to a fav. *) +let fav_from_list l = + let fav = fav_new () in + let _ = list_iter (fun id -> fav ++ id) l in + fav + +let rec remove_duplicates_from_sorted special_equal = function + | [] -> [] + | [x] -> [x] + | x:: y:: l -> + if (special_equal x y) + then remove_duplicates_from_sorted special_equal (y:: l) + else x:: (remove_duplicates_from_sorted special_equal (y:: l)) + +(** Convert a [fav] to a list of identifiers while preserving the order +that the identifiers were added to [fav]. *) +let fav_to_list fav = + list_rev !fav + +(** Pretty print a fav. *) +let pp_fav pe f fav = + (pp_seq (Ident.pp pe)) f (fav_to_list fav) + +(** Copy a [fav]. *) +let fav_copy fav = + ref (list_map (fun x -> x) !fav) + +(** Turn a xxx_fav_add function into a xxx_fav function *) +let fav_imperative_to_functional f x = + let fav = fav_new () in + let _ = f fav x in + fav + +(** [fav_filter_ident fav f] only keeps [id] if [f id] is true. *) +let fav_filter_ident fav filter = + fav := list_filter filter !fav + +(** Like [fav_filter_ident] but return a copy. *) +let fav_copy_filter_ident fav filter = + ref (list_filter filter !fav) + +(** checks whether every element in l1 appears l2 **) +let rec ident_sorted_list_subset l1 l2 = + match l1, l2 with + | [], _ -> true + | _:: _,[] -> false + | id1:: l1, id2:: l2 -> + let n = Ident.compare id1 id2 in + if n = 0 then ident_sorted_list_subset l1 (id2:: l2) + else if n > 0 then ident_sorted_list_subset (id1:: l1) l2 + else false + +(** [fav_subset_ident fav1 fav2] returns true if every ident in [fav1] +is in [fav2].*) +let fav_subset_ident fav1 fav2 = + ident_sorted_list_subset (fav_to_list fav1) (fav_to_list fav2) + +let fav_mem fav id = + list_exists (Ident.equal id) !fav + +let rec exp_fav_add fav = function + | Var id -> fav ++ id + | Const (Cexn e) -> exp_fav_add fav e + | Const (Ctuple el) -> list_iter (exp_fav_add fav) el + | Const _ -> () + | Cast (_, e) | UnOp (_, e, _) -> exp_fav_add fav e + | BinOp (_, e1, e2) -> exp_fav_add fav e1; exp_fav_add fav e2 + | Lvar id -> () (* do nothing since we only count non-program variables *) + | Lfield (e, _, _) -> exp_fav_add fav e + | Lindex (e1, e2) -> exp_fav_add fav e1; exp_fav_add fav e2 + | Sizeof _ -> () + +let exp_fav = + fav_imperative_to_functional exp_fav_add + +let exp_fav_list e = + fav_to_list (exp_fav e) + +let rec ident_in_exp id e = + let fav = fav_new () in + exp_fav_add fav e; + fav_mem fav id + +let atom_fav_add fav = function + | Aeq (e1, e2) | Aneq(e1, e2) -> exp_fav_add fav e1; exp_fav_add fav e2 + +let atom_fav = + fav_imperative_to_functional atom_fav_add + +(** Atoms do not contain binders *) +let atom_av_add = atom_fav_add + +let hpara_fav_add fav para = () (* Global invariant: hpara is closed *) +let hpara_dll_fav_add fav para = () (* Global invariant: hpara_dll is closed *) + +let rec strexp_fav_add fav = function + | Eexp (e, inst) -> exp_fav_add fav e + | Estruct (fld_se_list, inst) -> + list_iter (fun (_, se) -> strexp_fav_add fav se) fld_se_list + | Earray (size, idx_se_list, inst) -> + exp_fav_add fav size; + list_iter (fun (e, se) -> exp_fav_add fav e; strexp_fav_add fav se) idx_se_list + +let rec hpred_fav_add fav = function + | Hpointsto (base, sexp, te) -> exp_fav_add fav base; strexp_fav_add fav sexp; exp_fav_add fav te + | Hlseg (_, para, e1, e2, elist) -> + hpara_fav_add fav para; + exp_fav_add fav e1; exp_fav_add fav e2; + list_iter (exp_fav_add fav) elist + | Hdllseg (_, para, e1, e2, e3, e4, elist) -> + hpara_dll_fav_add fav para; + exp_fav_add fav e1; exp_fav_add fav e2; + exp_fav_add fav e3; exp_fav_add fav e4; + list_iter (exp_fav_add fav) elist + +let hpred_fav = + fav_imperative_to_functional hpred_fav_add + +(** This function should be used before adding a new +index to Earray. The [exp] is the newly created +index. This function "cleans" [exp] according to whether it is the footprint or current part of the prop. +The function faults in the re - execution mode, as an internal check of the tool. *) +let array_clean_new_index footprint_part new_idx = + if footprint_part && not !Config.footprint then assert false; + let fav = exp_fav new_idx in + if footprint_part && fav_exists fav (fun id -> not (Ident.is_footprint id)) then + begin + L.d_warning ("Array index " ^ (exp_to_string new_idx) ^ + " has non-footprint vars: replaced by fresh footprint var"); + L.d_ln (); + let id = Ident.create_fresh Ident.kfootprint in + Var id + end + else new_idx + +(** {2 Functions for computing all free or bound non-program variables} *) + +let exp_av_add = exp_fav_add (** Expressions do not bind variables *) + +let strexp_av_add = strexp_fav_add (** Structured expressions do not bind variables *) + +let rec hpara_av_add fav para = + list_iter (hpred_av_add fav) para.body; + fav ++ para.root; fav ++ para.next; + fav +++ para.svars; fav +++ para.evars + +and hpara_dll_av_add fav para = + list_iter (hpred_av_add fav) para.body_dll; + fav ++ para.cell; fav ++ para.blink; fav ++ para.flink; + fav +++ para.svars_dll; fav +++ para.evars_dll + +and hpred_av_add fav = function + | Hpointsto (base, se, te) -> + exp_av_add fav base; strexp_av_add fav se; exp_av_add fav te + | Hlseg (_, para, e1, e2, elist) -> + hpara_av_add fav para; + exp_av_add fav e1; exp_av_add fav e2; + list_iter (exp_av_add fav) elist + | Hdllseg (_, para, e1, e2, e3, e4, elist) -> + hpara_dll_av_add fav para; + exp_av_add fav e1; exp_av_add fav e2; + exp_av_add fav e3; exp_av_add fav e4; + list_iter (exp_av_add fav) elist + +let hpara_shallow_av_add fav para = + list_iter (hpred_fav_add fav) para.body; + fav ++ para.root; fav ++ para.next; + fav +++ para.svars; fav +++ para.evars + +let hpara_dll_shallow_av_add fav para = + list_iter (hpred_fav_add fav) para.body_dll; + fav ++ para.cell; fav ++ para.blink; fav ++ para.flink; + fav +++ para.svars_dll; fav +++ para.evars_dll + +(** Variables in hpara, excluding bound vars in the body *) +let hpara_shallow_av = fav_imperative_to_functional hpara_shallow_av_add + +(** Variables in hpara_dll, excluding bound vars in the body *) +let hpara_dll_shallow_av = fav_imperative_to_functional hpara_dll_shallow_av_add + +(** {2 Functions for Substitution} *) + +let rec reverse_with_base base = function + | [] -> base + | x:: l -> reverse_with_base (x:: base) l + +let sorted_list_merge compare l1_in l2_in = + let rec merge acc l1 l2 = + match l1, l2 with + | [], l2 -> reverse_with_base l2 acc + | l1, [] -> reverse_with_base l1 acc + | x1 :: l1', x2 :: l2' -> + if compare x1 x2 <= 0 then merge (x1:: acc) l1' l2 + else merge (x2 :: acc) l1 l2' in + merge [] l1_in l2_in + +let rec sorted_list_check_consecutives f = function + | [] | [_] -> false + | x1:: ((x2:: _) as l) -> + if f x1 x2 then true else sorted_list_check_consecutives f l + +(** substitution *) +type subst = (Ident.t * exp) list + +(** Comparison between substitutions. *) +let rec sub_compare (sub1: subst) (sub2: subst) = + if sub1 == sub2 then 0 + else match sub1, sub2 with + | [],[] -> 0 + | [], _ :: _ -> - 1 + | (i1, e1) :: sub1', (i2, e2):: sub2' -> + let n = Ident.compare i1 i2 in + if n <> 0 then n + else let n = exp_compare e1 e2 in + if n <> 0 then n + else sub_compare sub1' sub2' + | _ :: _, [] -> 1 + +(** Equality for substitutions. *) +let sub_equal sub1 sub2 = + sub_compare sub1 sub2 = 0 + +let sub_check_duplicated_ids sub = + let f (id1, _) (id2, _) = Ident.equal id1 id2 in + sorted_list_check_consecutives f sub + +let sub_check_sortedness sub = + let sub' = list_sort ident_exp_compare sub in + sub_equal sub sub' + +let sub_check_inv sub = + (sub_check_sortedness sub) && not (sub_check_duplicated_ids sub) + +(** Create a substitution from a list of pairs. +For all (id1, e1), (id2, e2) in the input list, +if id1 = id2, then e1 = e2. *) +let sub_of_list sub = + let sub' = list_sort ident_exp_compare sub in + let sub'' = remove_duplicates_from_sorted ident_exp_equal sub' in + (if sub_check_duplicated_ids sub'' then assert false); + sub' + +(** like sub_of_list, but allow duplicate ids and only keep the first occurrence *) +let sub_of_list_duplicates sub = + let sub' = list_sort ident_exp_compare sub in + let rec remove_duplicate_ids = function + | (id1, e1) :: (id2, e2) :: l -> + if Ident.equal id1 id2 + then remove_duplicate_ids ((id1, e1) :: l) + else (id1, e1) :: remove_duplicate_ids ((id2, e2) :: l) + | l -> l in + remove_duplicate_ids sub' + +(** Convert a subst to a list of pairs. *) +let sub_to_list sub = + sub + +(** The empty substitution. *) +let sub_empty = sub_of_list [] + +(** Join two substitutions into one. +For all id in dom(sub1) cap dom(sub2), sub1(id) = sub2(id). *) +let sub_join sub1 sub2 = + let sub = sorted_list_merge ident_exp_compare sub1 sub2 in + let sub' = remove_duplicates_from_sorted ident_exp_equal sub in + (if sub_check_duplicated_ids sub' then assert false); + sub + +(** Compute the common id-exp part of two inputs [subst1] and [subst2]. +The first component of the output is this common part. +The second and third components are the remainder of [subst1] +and [subst2], respectively. *) +let sub_symmetric_difference sub1_in sub2_in = + let rec diff sub_common sub1_only sub2_only sub1 sub2 = + match sub1, sub2 with + | [], _ | _, [] -> + let sub1_only' = reverse_with_base sub1 sub1_only in + let sub2_only' = reverse_with_base sub2 sub2_only in + let sub_common = reverse_with_base [] sub_common in + (sub_common, sub1_only', sub2_only') + | id_e1 :: sub1', id_e2 :: sub2' -> + let n = ident_exp_compare id_e1 id_e2 in + if n = 0 then + diff (id_e1:: sub_common) sub1_only sub2_only sub1' sub2' + else if n < 0 then + diff sub_common (id_e1:: sub1_only) sub2_only sub1' sub2 + else + diff sub_common sub1_only (id_e2:: sub2_only) sub1 sub2' in + diff [] [] [] sub1_in sub2_in + +module Typtbl = Hashtbl.Make (struct type t = typ let equal = typ_equal let hash = Hashtbl.hash end) + +let typ_update_memo = Typtbl.create 17 + +(** [sub_find filter sub] returns the expression associated to the first identifier that satisfies [filter]. Raise [Not_found] if there isn't one. *) +let sub_find filter (sub: subst) = + snd (list_find (fun (i, _) -> filter i) sub) + +(** [sub_filter filter sub] restricts the domain of [sub] to the +identifiers satisfying [filter]. *) +let sub_filter filter (sub: subst) = + list_filter (fun (i, _) -> filter i) sub + +(** [sub_filter_pair filter sub] restricts the domain of [sub] to the +identifiers satisfying [filter(id, sub(id))]. *) +let sub_filter_pair = list_filter + +(** [sub_range_partition filter sub] partitions [sub] according to +whether range expressions satisfy [filter]. *) +let sub_range_partition filter (sub: subst) = + list_partition (fun (_, e) -> filter e) sub + +(** [sub_domain_partition filter sub] partitions [sub] according to +whether domain identifiers satisfy [filter]. *) +let sub_domain_partition filter (sub: subst) = + list_partition (fun (i, _) -> filter i) sub + +(** Return the list of identifiers in the domain of the substitution. *) +let sub_domain sub = + list_map fst sub + +(** Return the list of expressions in the range of the substitution. *) +let sub_range sub = + list_map snd sub + +(** [sub_range_map f sub] applies [f] to the expressions in the range of [sub]. *) +let sub_range_map f sub = + sub_of_list (list_map (fun (i, e) -> (i, f e)) sub) + +(** [sub_map f g sub] applies the renaming [f] to identifiers in the domain +of [sub] and the substitution [g] to the expressions in the range of [sub]. *) +let sub_map f g sub = + sub_of_list (list_map (fun (i, e) -> (f i, g e)) sub) + +let mem_sub id sub = + list_exists (fun (id1, _) -> Ident.equal id id1) sub + +(** Extend substitution and return [None] if not possible. *) +let extend_sub sub id exp : subst option = + let compare (id1, _) (id2, _) = Ident.compare id1 id2 in + if mem_sub id sub then None + else Some (sorted_list_merge compare sub [(id, exp)]) + +(** Free auxilary variables in the domain and range of the +substitution. *) +let sub_fav_add fav (sub: subst) = + list_iter (fun (id, e) -> fav ++ id; exp_fav_add fav e) sub + +let sub_fpv (sub: subst) = + list_flatten (list_map (fun (_, e) -> exp_fpv e) sub) + +(** Substitutions do not contain binders *) +let sub_av_add = sub_fav_add + +let rec typ_sub (subst: subst) typ = + match typ with + | Tvar _ + | Tint _ + | Tfloat _ + | Tvoid + | Tstruct _ + | Tfun _ -> + typ + | Tptr (t', pk) -> + Tptr (typ_sub subst t', pk) + | Tarray (t, e) -> + Tarray (typ_sub subst t, exp_sub subst e) + | Tenum econsts -> + typ + +and exp_sub (subst: subst) e = + match e with + | Var id -> + let rec apply_sub = function + | [] -> e + | (i, e):: l -> if Ident.equal i id then e else apply_sub l in + apply_sub subst + | Const (Cexn e1) -> + let e1' = exp_sub subst e1 in + Const (Cexn e1') + | Const (Ctuple el) -> + let el' = list_map (exp_sub subst) el in + Const (Ctuple el') + | Const _ -> + e + | Cast (t, e1) -> + let e1' = exp_sub subst e1 in + Cast (t, e1') + | UnOp (op, e1, typo) -> + let e1' = exp_sub subst e1 in + let typo' = match typo with + | None -> None + | Some typ -> Some (typ_sub subst typ) in + UnOp(op, e1', typo') + | BinOp (op, e1, e2) -> + let e1' = exp_sub subst e1 in + let e2' = exp_sub subst e2 in + BinOp (op, e1', e2') + | Lvar id -> + e + | Lfield (e1, fld, typ) -> + let e1' = exp_sub subst e1 in + let typ' = typ_sub subst typ in + Lfield (e1', fld, typ') + | Lindex (e1, e2) -> + let e1' = exp_sub subst e1 in + let e2' = exp_sub subst e2 in + Lindex (e1', e2') + | Sizeof (t, s) -> + Sizeof (typ_sub subst t, s) + +let instr_sub (subst: subst) instr = + let id_s id = match exp_sub subst (Var id) with + | Var id' -> id' + | _ -> id in + let exp_s = exp_sub subst in + let typ_s = typ_sub subst in + match instr with + | Letderef (id, e, t, loc) -> + Letderef (id_s id, exp_s e, typ_s t, loc) + | Set (e1, t, e2, loc) -> + Set (exp_s e1, typ_s t, exp_s e2, loc) + | Prune (cond, loc, true_branch, ik) -> + Prune (exp_s cond, loc, true_branch, ik) + | Call (ret_ids, e, arg_ts, loc, cf) -> + let arg_s (e, t) = (exp_s e, typ_s t) in + Call (list_map id_s ret_ids, exp_s e, list_map arg_s arg_ts, loc, cf) + | Nullify (pvar, loc, deallocate) -> + instr + | Abstract loc -> + instr + | Remove_temps (temps, loc) -> + Remove_temps (list_map id_s temps, loc) + | Stackop (stackop, loc) -> + instr + | Declare_locals (ptl, loc) -> + let pt_s (pv, t) = (pv, typ_s t) in + Declare_locals (list_map pt_s ptl, loc) + | Goto_node (e, loc) -> + Goto_node (exp_s e, loc) + +let call_flags_compare cflag1 cflag2 = + let n = bool_compare cflag1.cf_virtual cflag2.cf_virtual in + if n <> 0 then n else bool_compare cflag1.cf_noreturn cflag2.cf_noreturn + +let exp_typ_compare (exp1, typ1) (exp2, typ2) = + let n = exp_compare exp1 exp2 in + if n <> 0 then n else typ_compare typ1 typ2 + +let instr_compare instr1 instr2 = match instr1, instr2 with + | Letderef (id1, e1, t1, loc1), Letderef (id2, e2, t2, loc2) -> + let n = Ident.compare id1 id2 in + if n <> 0 then n else let n = exp_compare e1 e2 in + if n <> 0 then n else let n = typ_compare t1 t2 in + if n <> 0 then n else loc_compare loc1 loc2 + | Letderef _, _ -> -1 + | _, Letderef _ -> 1 + | Set (e11, t1, e21, loc1), Set (e12, t2, e22, loc2) -> + let n = exp_compare e11 e12 in + if n <> 0 then n else let n = typ_compare t1 t2 in + if n <> 0 then n else let n = exp_compare e21 e22 in + if n <> 0 then n else loc_compare loc1 loc2 + | Set _, _ -> -1 + | _, Set _ -> 1 + | Prune (cond1, loc1, true_branch1, ik1), Prune (cond2, loc2, true_branch2, ik2) -> + let n = exp_compare cond1 cond2 in + if n <> 0 then n else let n = loc_compare loc1 loc2 in + if n <> 0 then n else let n = bool_compare true_branch1 true_branch2 in + if n <> 0 then n else Pervasives.compare ik1 ik2 + | Prune _, _ -> -1 + | _, Prune _ -> 1 + | Call (ret_ids1, e1, arg_ts1, loc1, cf1), Call (ret_ids2, e2, arg_ts2, loc2, cf2) -> + let n = list_compare Ident.compare ret_ids1 ret_ids2 in + if n <> 0 then n else let n = exp_compare e1 e2 in + if n <> 0 then n else let n = list_compare exp_typ_compare arg_ts1 arg_ts2 in + if n <> 0 then n else let n = loc_compare loc1 loc2 in + if n <> 0 then n else call_flags_compare cf1 cf2 + | Call _, _ -> -1 + | _, Call _ -> 1 + | Nullify (pvar1, loc1, deallocate1), Nullify (pvar2, loc2, deallocate2) -> + let n = pvar_compare pvar1 pvar2 in + if n <> 0 then n else let n = loc_compare loc1 loc2 in + if n <> 0 then n else bool_compare deallocate1 deallocate2 + | Nullify _, _ -> -1 + | _, Nullify _ -> 1 + | Abstract loc1, Abstract loc2 -> + loc_compare loc1 loc2 + | Abstract _, _ -> -1 + | _, Abstract _ -> 1 + | Remove_temps (temps1, loc1), Remove_temps (temps2, loc2) -> + let n = list_compare Ident.compare temps1 temps2 in + if n <> 0 then n else loc_compare loc1 loc2 + | Remove_temps _, _ -> -1 + | _, Remove_temps _ -> 1 + | Stackop (stackop1, loc1), Stackop (stackop2, loc2) -> + let n = Pervasives.compare stackop1 stackop2 in + if n <> 0 then n else loc_compare loc1 loc2 + | Stackop _, _ -> -1 + | _, Stackop _ -> 1 + | Declare_locals (ptl1, loc1), Declare_locals (ptl2, loc2) -> + let pt_compare (pv1, t1) (pv2, t2) = + let n = pvar_compare pv1 pv2 in + if n <> 0 then n else typ_compare t1 t2 in + + let n = list_compare pt_compare ptl1 ptl2 in + if n <> 0 then n else loc_compare loc1 loc2 + | Declare_locals _, _ -> -1 + | _, Declare_locals _ -> 1 + | Goto_node (e1, loc1), Goto_node (e2, loc2) -> + let n = exp_compare e1 e2 in + if n <> 0 then n else loc_compare loc1 loc2 + +let atom_sub subst = + atom_expmap (exp_sub subst) + +let range_sub subst range = + let lower, upper = range in + let lower' = exp_sub subst lower in + let upper' = exp_sub subst upper in + (lower', upper') + +let hpred_sub subst = + let f (e, inst_opt) = (exp_sub subst e, inst_opt) in + hpred_expmap f + +let hpara_sub subst para = para + +let hpara_dll_sub subst para = para + +(** {2 Functions for replacing occurrences of expressions.} *) + +let exp_replace_exp epairs e = + try + let (_, e') = list_find (fun (e1, _) -> exp_equal e e1) epairs in + e' + with Not_found -> e + +let exp_list_replace_exp epairs l = + list_map (exp_replace_exp epairs) l + +let atom_replace_exp epairs = function + | Aeq (e1, e2) -> + let e1' = exp_replace_exp epairs e1 in + let e2' = exp_replace_exp epairs e2 in + Aeq (e1', e2') + | Aneq (e1, e2) -> + let e1' = exp_replace_exp epairs e1 in + let e2' = exp_replace_exp epairs e2 in + Aneq (e1', e2') + +let rec strexp_replace_exp epairs = function + | Eexp (e, inst) -> + Eexp (exp_replace_exp epairs e, inst) + | Estruct (fsel, inst) -> + let f (fld, se) = (fld, strexp_replace_exp epairs se) in + Estruct (list_map f fsel, inst) + | Earray (size, isel, inst) -> + let size' = exp_replace_exp epairs size in + let f (idx, se) = + let idx' = exp_replace_exp epairs idx in + (idx', strexp_replace_exp epairs se) in + Earray (size', list_map f isel, inst) + +let rec hpred_replace_exp epairs = function + | Hpointsto (root, se, te) -> + let root_repl = exp_replace_exp epairs root in + let strexp_repl = strexp_replace_exp epairs se in + let te_repl = exp_replace_exp epairs te in + Hpointsto (root_repl, strexp_repl, te_repl) + | Hlseg (k, para, root, next, shared) -> + let root_repl = exp_replace_exp epairs root in + let next_repl = exp_replace_exp epairs next in + let shared_repl = list_map (exp_replace_exp epairs) shared in + Hlseg (k, para, root_repl, next_repl, shared_repl) + | Hdllseg (k, para, e1, e2, e3, e4, shared) -> + let e1' = exp_replace_exp epairs e1 in + let e2' = exp_replace_exp epairs e2 in + let e3' = exp_replace_exp epairs e3 in + let e4' = exp_replace_exp epairs e4 in + let shared_repl = list_map (exp_replace_exp epairs) shared in + Hdllseg (k, para, e1', e2', e3', e4', shared_repl) + +(** {2 Compaction} *) +module ExpHash = Hashtbl.Make (struct + type t = exp + let equal = exp_equal + let hash = Hashtbl.hash end) + +module HpredHash = Hashtbl.Make (struct + type t = hpred + let equal = hpred_equal + let hash = Hashtbl.hash end) + +type sharing_env = + { exph : exp ExpHash.t; + hpredh : hpred HpredHash.t } + +(** Create a sharing env to store canonical representations *) +let create_sharing_env () = + { exph = ExpHash.create 3; + hpredh = HpredHash.create 3 } + +(** Return a canonical representation of the exp *) +let exp_compact sh e = + try ExpHash.find sh.exph e with + | Not_found -> + ExpHash.add sh.exph e e; + e + +let rec sexp_compact sh se = + match se with + | Eexp (e, inst) -> + Eexp (exp_compact sh e, inst) + | Estruct (fsel, inst) -> + Estruct (list_map (fun (f, se) -> (f, sexp_compact sh se)) fsel, inst) + | Earray _ -> + se + +(** Return a compact representation of the hpred *) +let _hpred_compact sh hpred = match hpred with + | Hpointsto (e1, se, e2) -> + let e1' = exp_compact sh e1 in + let e2' = exp_compact sh e2 in + let se' = sexp_compact sh se in + Hpointsto (e1', se', e2') + | Hlseg _ -> hpred + | Hdllseg _ -> hpred + +let hpred_compact sh hpred = + try HpredHash.find sh.hpredh hpred with + | Not_found -> + let hpred' = _hpred_compact sh hpred in + HpredHash.add sh.hpredh hpred' hpred'; + hpred' + +(** {2 Type Environment} *) +(** hash tables on strings *) + +module TypenameHash = + Hashtbl.Make(struct + type t = typename + let equal tn1 tn2 = typename_equal tn1 tn2 + let hash = Hashtbl.hash + end) + +(** Type for type environment. *) +type tenv = typ TypenameHash.t + +(** Create a new type environment. *) +let create_tenv () = TypenameHash.create 1000 + +(** Check if typename is found in tenv *) +let tenv_mem tenv name = + TypenameHash.mem tenv name + +(** Look up a name in the global type environment. *) +let tenv_lookup tenv name = + try Some (TypenameHash.find tenv name) + with Not_found -> None + +(** Add a (name,type) pair to the global type environment. *) +let tenv_add tenv name typ = + TypenameHash.replace tenv name typ + +(** look up the type for a mangled name in the current type environment *) +let get_typ name csu_option tenv = + let csu = match csu_option with + | Some t -> t + | None -> Class in + tenv_lookup tenv (TN_csu (csu, name)) + +(** expand a type if it is a typename by looking it up in the type environment *) +let rec expand_type tenv typ = + match typ with + | Tvar tname -> + begin + match tenv_lookup tenv tname with + | None -> assert false + | Some typ' -> expand_type tenv typ' + end + | _ -> typ + +(** type environment used for parsing, to be set by the client of the parser module *) +let tenv_for_parsing = ref (create_tenv ()) + +(** Serializer for type environments *) +let tenv_serializer : tenv Serialization.serializer = Serialization.create_serializer Serialization.tenv_key + +let global_tenv: (tenv option) Lazy.t = + lazy (Serialization.from_file tenv_serializer (DB.global_tenv_fname ())) + +(** Load a type environment from a file *) +let load_tenv_from_file (filename : DB.filename) : tenv option = + if filename = DB.global_tenv_fname () then + Lazy.force global_tenv + else + Serialization.from_file tenv_serializer filename + +(** Save a type environment into a file *) +let store_tenv_to_file (filename : DB.filename) (tenv : tenv) = + Serialization.to_file tenv_serializer filename tenv + +let tenv_iter f tenv = + TypenameHash.iter f tenv + +let tenv_fold f tenv = + TypenameHash.fold f tenv + +let pp_tenv f (tenv : tenv) = + TypenameHash.iter + (fun name typ -> + Format.fprintf f "@[<6>NAME: %s@." (typename_to_string name); + Format.fprintf f "@[<6>TYPE: %a@." (pp_typ_full pe_text) typ) + tenv + +(** {2 Functions for constructing or destructing entities in this module} *) + +(** [mk_pvar name proc_name] creates a program var with the given function name *) +let mk_pvar (name: Mangled.t) (proc_name: Procname.t) : pvar = + { pv_name = name; pv_kind = Local_var proc_name } + +(** [mk_pvar_callee name proc_name] creates a program var for a callee function with the given function name *) +let mk_pvar_callee (name: Mangled.t) (proc_name: Procname.t) : pvar = + { pv_name = name; pv_kind = Callee_var proc_name } + +(** create a global variable with the given name *) +let mk_pvar_global (name: Mangled.t) : pvar = + { pv_name = name; pv_kind = Global_var } + +(** create an abducted return variable for a call to [proc_name] at [loc] *) +let mk_pvar_abducted_ret (proc_name : Procname.t) (loc : location) : pvar = + let name = Mangled.from_string ("$RET_" ^ (Procname.to_unique_id proc_name)) in + { pv_name = name; pv_kind = Abducted_retvar (proc_name, loc) } + +(** Turn an ordinary program variable into a callee program variable *) +let pvar_to_callee pname pvar = match pvar.pv_kind with + | Local_var _ -> + { pvar with pv_kind = Callee_var pname } + | Global_var -> pvar + | Callee_var _ | Abducted_retvar _ | Seed_var -> + L.d_str "Cannot convert pvar to callee: "; + d_pvar pvar; L.d_ln (); + assert false + +(** Compute the offset list of an expression *) +let exp_get_offsets exp = + let rec f offlist_past e = match e with + | Var _ | Const _ | UnOp _ | BinOp _ | Lvar _ | Sizeof _ -> offlist_past + | Cast(t, sub_exp) -> f offlist_past sub_exp + | Lfield(sub_exp, fldname, typ) -> f (Off_fld (fldname, typ):: offlist_past) sub_exp + | Lindex(sub_exp, e) -> f (Off_index e :: offlist_past) sub_exp in + f [] exp + +let exp_add_offsets exp offsets = + let rec f acc = function + | [] -> acc + | Off_fld (fld, typ) :: offs' -> f (Lfield(acc, fld, typ)) offs' + | Off_index e :: offs' -> f (Lindex(acc, e)) offs' in + f exp offsets + +(** Convert all the lseg's in sigma to nonempty lsegs. *) +let sigma_to_sigma_ne sigma : (atom list * hpred list) list = + if !Config.nelseg then + let f eqs_sigma_list hpred = match hpred with + | Hpointsto _ | Hlseg(Lseg_NE, _, _, _, _) | Hdllseg(Lseg_NE, _, _, _, _, _, _) -> + let g (eqs, sigma) = (eqs, hpred:: sigma) in + list_map g eqs_sigma_list + | Hlseg(Lseg_PE, para, e1, e2, el) -> + let g (eqs, sigma) = [(Aeq(e1, e2):: eqs, sigma); (eqs, Hlseg(Lseg_NE, para, e1, e2, el):: sigma)] in + list_flatten (list_map g eqs_sigma_list) + | Hdllseg(Lseg_PE, para_dll, e1, e2, e3, e4, el) -> + let g (eqs, sigma) = [(Aeq(e1, e3):: Aeq(e2, e4):: eqs, sigma); (eqs, Hdllseg(Lseg_NE, para_dll, e1, e2, e3, e4, el):: sigma)] in + list_flatten (list_map g eqs_sigma_list) in + list_fold_left f [([],[])] sigma + else + [([], sigma)] + +(** [hpara_instantiate para e1 e2 elist] instantiates [para] with [e1], +[e2] and [elist]. If [para = lambda (x, y, xs). exists zs. b], +then the result of the instantiation is [b\[e1 / x, e2 / y, elist / xs, _zs'/ zs\]] +for some fresh [_zs'].*) +let hpara_instantiate para e1 e2 elist = + let subst_for_svars = + let g id e = (id, e) in + try (list_map2 g para.svars elist) + with Invalid_argument _ -> assert false in + let ids_evars = + let g id = Ident.create_fresh Ident.kprimed in + list_map g para.evars in + let subst_for_evars = + let g id id' = (id, Var id') in + try (list_map2 g para.evars ids_evars) + with Invalid_argument _ -> assert false in + let subst = sub_of_list ((para.root, e1):: (para.next, e2):: subst_for_svars@subst_for_evars) in + (ids_evars, list_map (hpred_sub subst) para.body) + +(** [hpara_dll_instantiate para cell blink flink elist] instantiates [para] with [cell], +[blink], [flink], and [elist]. If [para = lambda (x, y, z, xs). exists zs. b], +then the result of the instantiation is [b\[cell / x, blink / y, flink / z, elist / xs, _zs'/ zs\]] +for some fresh [_zs'].*) +let hpara_dll_instantiate (para: hpara_dll) cell blink flink elist = + let subst_for_svars = + let g id e = (id, e) in + try (list_map2 g para.svars_dll elist) + with Invalid_argument _ -> assert false in + let ids_evars = + let g id = Ident.create_fresh Ident.kprimed in + list_map g para.evars_dll in + let subst_for_evars = + let g id id' = (id, Var id') in + try (list_map2 g para.evars_dll ids_evars) + with Invalid_argument _ -> assert false in + let subst = sub_of_list ((para.cell, cell):: (para.blink, blink):: (para.flink, flink):: subst_for_svars@subst_for_evars) in + (ids_evars, list_map (hpred_sub subst) para.body_dll) + +(** Return the list of expressions that could be understood as outgoing arrows from the strexp *) +let rec strexp_get_target_exps = function + | Eexp (e, inst) -> [e] + | Estruct (fsel, inst) -> list_flatten (list_map (fun (_, se) -> strexp_get_target_exps se) fsel) + | Earray (_, esel, _) -> + (* We ignore size and indices since they are not quite outgoing arrows. *) + list_flatten (list_map (fun (_, se) -> strexp_get_target_exps se) esel) + +let global_error = + mk_pvar_global (Mangled.from_string "INFER_ERROR") + +(* A block pvar used to explain retain cycles *) +let block_pvar = + mk_pvar (Mangled.from_string "block") (Procname.from_string "") diff --git a/infer/src/backend/sil.mli b/infer/src/backend/sil.mli new file mode 100644 index 000000000..c589d1800 --- /dev/null +++ b/infer/src/backend/sil.mli @@ -0,0 +1,1353 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** The Smallfoot Intermediate Language *) + +open Utils + +(** Programming language. *) +type language = C_CPP | Java + +(** current language *) +val curr_language : language ref (* TODO: move to Config. It is good to have all global variables in the same place *) + +val string_of_language : language -> string + +(** Class, struct, union, (Obj C) protocol *) +type csu = + | Class + | Struct + | Union + | Protocol + +(** Named types. *) +type typename = + | TN_typedef of Mangled.t + | TN_enum of Mangled.t + | TN_csu of csu * Mangled.t + +(** {2 Programs and Types} *) + +(** Type to represent one @Annotation. *) +type annotation = + { class_name: string; (* name of the annotation *) + parameters: string list; (* currently only one string parameter *) } + +(** Annotation for one item: a list of annotations with visibility. *) +type item_annotation = (annotation * bool) list + +(** Annotation for a method: return value and list of parameters. *) +type method_annotation = + item_annotation * item_annotation list + +(** Visibility modifiers. *) +type access = Default | Public | Private | Protected + +(** Attributes of a procedure. *) +type proc_attributes = + { + access : access; (** visibility access *) + exceptions : string list; (** exceptions thrown by the procedure *) + is_abstract : bool; (** the procedure is abstract *) + mutable is_bridge_method : bool; (** the procedure is a bridge method *) + is_objc_instance_method : bool; (** the procedure is an objective-C instance method *) + mutable is_synthetic_method : bool; (** the procedure is a synthetic method *) + language : language; + method_annotation : method_annotation; + } + +(** Create a copy of a proc_attributes *) +val copy_proc_attributes : proc_attributes -> proc_attributes + +(** Type for program variables. There are 4 kinds of variables: +1) local variables, used for local variables and formal parameters +2) callee program variables, used to handle recursion ([x | callee] is distinguished from [x]) +3) global variables +4) seed variables, used to store the initial value of formal parameters +*) +type pvar + +(** Location in the original source file *) +type location = { + line: int; (** The line number. -1 means "do not know" *) + col: int; (** The column number. -1 means "do not know" *) + file: DB.source_file; (** The name of the source file *) + nLOC : int; (** Lines of code in the source file *) +} + +val dummy_location : location + +(** Unary operations *) +type unop = + | Neg (** Unary minus *) + | BNot (** Bitwise complement (~) *) + | LNot (** Logical Not (!) *) + +(** Binary operations *) +type binop = + | PlusA (** arithmetic + *) + | PlusPI (** pointer + integer *) + | MinusA (** arithmetic - *) + | MinusPI (** pointer - integer *) + | MinusPP (** pointer - pointer *) + | Mult (** * *) + | Div (** / *) + | Mod (** % *) + | Shiftlt (** shift left *) + | Shiftrt (** shift right *) + + | Lt (** < (arithmetic comparison) *) + | Gt (** > (arithmetic comparison) *) + | Le (** <= (arithmetic comparison) *) + | Ge (** >= (arithmetic comparison) *) + | Eq (** == (arithmetic comparison) *) + | Ne (** != (arithmetic comparison) *) + | BAnd (** bitwise and *) + | BXor (** exclusive-or *) + | BOr (** inclusive-or *) + + | LAnd (** logical and. Does not always evaluate both operands. *) + | LOr (** logical or. Does not always evaluate both operands. *) + | PtrFld (** field offset via pointer to field: takes the address of a csu and a Cptr_to_fld constant to form an Lfield expression (see prop.ml) *) + +(** Kinds of integers *) +type ikind = + IChar (** [char] *) + | ISChar (** [signed char] *) + | IUChar (** [unsigned char] *) + | IBool (** [bool] *) + | IInt (** [int] *) + | IUInt (** [unsigned int] *) + | IShort (** [short] *) + | IUShort (** [unsigned short] *) + | ILong (** [long] *) + | IULong (** [unsigned long] *) + | ILongLong (** [long long] (or [_int64] on Microsoft Visual C) *) + | IULongLong (** [unsigned long long] (or [unsigned _int64] on Microsoft Visual C) *) + | I128 (** [__int128_t] *) + | IU128 (** [__uint128_t] *) + +(** Kinds of floating-point numbers*) +type fkind = + | FFloat (** [float] *) + | FDouble (** [double] *) + | FLongDouble (** [long double] *) + +type mem_kind = + | Mmalloc (** memory allocated with malloc *) + | Mnew (** memory allocated with new *) + | Mnew_array (** memory allocated with new[] *) + | Mobjc (** memory allocated with objective-c alloc *) + +(** resource that can be allocated *) +type resource = + | Rmemory of mem_kind + | Rfile + | Rignore + | Rlock + +(** kind of resource action *) +type res_act_kind = + | Racquire + | Rrelease + +(** kind of dangling pointers *) +type dangling_kind = + | DAuninit (** pointer is dangling because it is uninitialized *) + | DAaddr_stack_var (** pointer is dangling because it is the address of a stack variable which went out of scope *) + | DAminusone (** pointer is -1 *) + +(** kind of pointer *) +type ptr_kind = + | Pk_pointer (* C/C++, Java, Objc standard/__strong pointer*) + | Pk_reference (* C++ reference *) + | Pk_objc_weak (* Obj-C __weak pointer*) + | Pk_objc_unsafe_unretained (* Obj-C __unsafe_unretained pointer *) + | Pk_objc_autoreleasing (* Obj-C __autoreleasing pointer *) + +(** position in a path: proc name, node id *) +type path_pos = Procname.t * int + +(** module for subtypes, to be used with Sizeof info *) +module Subtype : sig + type t + val exact : t (** denotes the current type only *) + val subtypes : t (** denotes the current type and any subtypes *) + val subtypes_cast : t + val subtypes_instof : t + val join : t -> t -> t + (** [case_analysis (c1, st1) (c2,st2) f] performs case analysis on [c1 <: c2] according to [st1] and [st2] + where f c1 c2 is true if c1 is a subtype of c2. + get_subtypes returning a pair: + - whether [st1] and [st2] admit [c1 <: c2], and in case return the updated subtype [st1] + - whether [st1] and [st2] admit [not(c1 <: c2)], and in case return the updated subtype [st1] *) + val case_analysis : (Mangled.t * t) -> (Mangled.t * t) -> (Mangled.t -> Mangled.t -> bool) -> (Mangled.t -> bool) -> t option * t option + val check_subtype : (Mangled.t -> Mangled.t -> bool) -> Mangled.t -> Mangled.t -> bool + val subtypes_to_string : t -> string + val is_cast : t -> bool + val is_instof : t -> bool + (** equality ignoring flags in the subtype *) + val equal_modulo_flag : t -> t -> bool +end + +(** module for signed and unsigned integers *) +module Int : sig + type t + val add : t -> t -> t + + (** compare the value of the integers, notice this is different from const compare, which distinguished between signed and unsigned +1 *) + val compare_value : t -> t -> int + val div : t -> t -> t + val eq : t -> t -> bool + val of_int : int -> t + val of_int32 : int32 -> t + val of_int64 : int64 -> t + val geq : t -> t -> bool + val gt : t -> t -> bool + val isminusone : t -> bool + val isnegative : t -> bool + val isnull : t -> bool + val isone : t -> bool + val iszero : t -> bool + val leq : t -> t -> bool + val logand : t -> t -> t + val lognot : t -> t + val logor : t -> t -> t + val logxor : t -> t -> t + val lt : t -> t -> bool + val minus_one : t + val mul : t -> t -> t + val neg : t -> t + val neq : t -> t -> bool + val null : t (** null behaves like zero except for the function isnull *) + val one : t + val pp : Format.formatter -> t -> unit + val rem : t -> t -> t + val sub : t -> t -> t + val to_int : t -> int + val to_signed : t -> t option (** convert to signed if the value is representable *) + val to_string : t -> string + val two : t + val zero : t +end + +(** Flags for a procedure call *) +type call_flags = { + cf_virtual: bool; + cf_noreturn : bool; + cf_is_objc_block : bool; +} + +(** Default value for call_flags where all fields are set to false *) +val cf_default : call_flags + +(** expression representing the result of decompilation *) +type dexp = + | Darray of dexp * dexp + | Dbinop of binop * dexp * dexp + | Dconst of const + | Dsizeof of typ * Subtype.t + | Dderef of dexp + | Dfcall of dexp * dexp list * location * call_flags + | Darrow of dexp * Ident.fieldname + | Ddot of dexp * Ident.fieldname + | Dpvar of pvar + | Dpvaraddr of pvar + | Dunop of unop * dexp + | Dunknown + | Dretcall of dexp * dexp list * location * call_flags + +(** Value paths: identify an occurrence of a value in a symbolic heap +each expression represents a path, with Dpvar being the simplest one *) +and vpath = + dexp option + +(** acquire/release action on a resource *) +and res_action = + { ra_kind : res_act_kind; (** kind of action *) + ra_res : resource; (** kind of resource *) + ra_pname : Procname.t; (** name of the procedure used to acquire/release the resource *) + ra_loc : location; (** location of the acquire/release *) + ra_vpath: vpath; (** vpath of the resource value *) + } + +(** Attributes *) +and attribute = + | Aresource of res_action (** resource acquire/release *) + | Aautorelease + | Adangling of dangling_kind (** dangling pointer *) + | Aundef of Procname.t * location * path_pos (** undefined value obtained by calling the given procedure *) + | Ataint + | Auntaint + | Adiv0 of path_pos (** value appeared in second argument of division at given path position *) + | Aobjc_null of exp (** the exp. is null because of a call to a method with exp as a null receiver *) + | Avariadic_function_argument of Procname.t * int * int (** (pn, n, i) the exp. is used as [i]th + argument of a call to the variadic + function [pn] that has [n] arguments *) + | Aretval of Procname.t (** value was returned from a call to the given procedure *) + +(** Categories of attributes *) +and attribute_category = + | ACresource + | ACautorelease + | ACtaint + | ACdiv0 + | ACobjc_null + | ACvariadic_function_argument + +(** Constants *) +and const = + | Cint of Int.t (** integer constants *) + | Cfun of Procname.t (** function names *) + | Cstr of string (** string constants *) + | Cfloat of float (** float constants *) + | Cattribute of attribute (** attribute used in disequalities to annotate a value *) + | Cexn of exp (** exception *) + | Cclass of Ident.name (** class constant *) + | Cptr_to_fld of Ident.fieldname * typ (** pointer to field constant, and type of the surrounding csu type *) + | Ctuple of exp list (** tuple of values *) + +and struct_fields = (Ident.fieldname * typ * item_annotation) list + +(** Types for sil (structured) expressions. *) +and typ = + | Tvar of typename (** named type *) + | Tint of ikind (** integer type *) + | Tfloat of fkind (** float type *) + | Tvoid (** void type *) + | Tfun of bool (** function type with noreturn attribute *) + | Tptr of typ * ptr_kind (** pointer type *) + | Tstruct of struct_fields * struct_fields * csu * Mangled.t option * (csu * Mangled.t) list * Procname.t list * item_annotation + (** Structure type with nonstatic and static fields, class/struct/union flag, name, list of superclasses, + methods defined, and annotations. + The fld - typ pairs are always sorted. This means that we don't support programs that exploit specific layouts + of C structs. *) + | Tarray of typ * exp (** array type with fixed size *) + | Tenum of (Mangled.t * const) list + +(** Program expressions. *) +and exp = + | Var of Ident.t (** pure variable: it is not an lvalue *) + | UnOp of unop * exp * typ option (** unary operator with type of the result if known *) + | BinOp of binop * exp * exp (** binary operator *) + | Const of const (** constants *) + | Cast of typ * exp (** type cast *) + | Lvar of pvar (** the address of a program variable *) + | Lfield of exp * Ident.fieldname * typ (** a field offset, the type is the surrounding struct type *) + | Lindex of exp * exp (** an array index offset: [exp1\[exp2\]] *) + | Sizeof of typ * Subtype.t (** a sizeof expression *) + +(** Sets of types. *) +module TypSet : Set.S with type elt = typ + +(** Maps with type keys. *) +module TypMap : Map.S with type key = typ + +(** Sets of expressions. *) +module ExpSet : Set.S with type elt = exp + +(** Hashtable with expressions as keys. *) +module ExpHash : Hashtbl.S with type key = exp + +(** Convert expression lists to expression sets. *) +val elist_to_eset : exp list -> ExpSet.t + +(** Kind of prune instruction *) +type if_kind = + | Ik_bexp (* boolean expressions, and exp ? exp : exp *) + | Ik_dowhile + | Ik_for + | Ik_if + | Ik_land_lor (* obtained from translation of && or || *) + | Ik_while + | Ik_switch + +(** Stack operation for symbolic execution on propsets *) +type stackop = + | Push (* copy the curreny propset to the stack *) + | Swap (* swap the current propset and the top of the stack *) + | Pop (* pop the stack and combine with the current propset *) + +(** An instruction. *) +type instr = + | Letderef of Ident.t * exp * typ * location (** declaration [let x = *lexp:typ] where [typ] is the root type of [lexp] *) + | Set of exp * typ * exp * location (** assignment [*lexp1:typ = exp2] where [typ] is the root type of [lexp1] *) + | Prune of exp * location * bool * if_kind (** prune the state based on [exp=1], the boolean indicates whether true branch *) + | Call of Ident.t list * exp * (exp * typ) list * location * call_flags + (** [Call (ret_id1..ret_idn, e_fun, arg_ts, loc, call_flags)] represents an instructions + [ret_id1..ret_idn = e_fun(arg_ts);] where n = 0 for void return and n > 1 for struct return *) + | Nullify of pvar * location * bool (** nullify stack variable, the bool parameter indicates whether to deallocate the variable *) + | Abstract of location (** apply abstraction *) + | Remove_temps of Ident.t list * location (** remove temporaries *) + | Stackop of stackop * location (** operation on the stack of propsets *) + | Declare_locals of (pvar * typ) list * location (** declare local variables *) + | Goto_node of exp * location (** jump to a specific cfg node, assuming all the possible target nodes are successors of the current node *) + +(** Check if an instruction is auxiliary, or if it comes from source instructions. *) +val instr_is_auxiliary : instr -> bool + +(** Offset for an lvalue. *) +type offset = Off_fld of Ident.fieldname * typ | Off_index of exp + +(** {2 Components of Propositions} *) + +(** an atom is a pure atomic formula *) +type atom = + | Aeq of exp * exp (** equality *) + | Aneq of exp * exp (** disequality*) + +(** kind of lseg or dllseg predicates *) +type lseg_kind = + | Lseg_NE (** nonempty (possibly circular) listseg *) + | Lseg_PE (** possibly empty (possibly circular) listseg *) + +(** The boolean is true when the pointer was dereferenced without testing for zero. *) +type zero_flag = bool option + +(** True when the value was obtained by doing case analysis on null in a procedure call. *) +type null_case_flag = bool + +(** instrumentation of heap values *) +type inst = + | Iabstraction + | Iactual_precondition + | Ialloc + | Iformal of zero_flag * null_case_flag + | Iinitial + | Ilookup + | Inone + | Inullify + | Irearrange of zero_flag * null_case_flag * int * path_pos + | Itaint + | Iupdate of zero_flag * null_case_flag * int * path_pos + | Ireturn_from_call of int + +val inst_abstraction : inst +val inst_actual_precondition : inst +val inst_alloc : inst +val inst_formal : inst (** for formal parameters and heap values at the beginning of the function *) +val inst_initial : inst (** for initial values *) +val inst_lookup : inst +val inst_none : inst +val inst_nullify : inst +val inst_rearrange : bool -> location -> path_pos -> inst (** the boolean indicates whether the pointer is known nonzero *) +val inst_taint : inst +val inst_update : location -> path_pos -> inst + +(** Get the null case flag of the inst. *) +val inst_get_null_case_flag : inst -> bool option + +(** Set the null case flag of the inst. *) +val inst_set_null_case_flag : inst -> inst + +(** update the location of the instrumentation *) +val inst_new_loc : location -> inst -> inst + +(** Update [inst_old] to [inst_new] preserving the zero flag *) +val update_inst : inst -> inst -> inst + +(** join of instrumentations *) +val inst_partial_join : inst -> inst -> inst + +(** meet of instrumentations *) +val inst_partial_meet : inst -> inst -> inst + +(** structured expressions represent a value of structured type, such as an array or a struct. *) +type strexp = + | Eexp of exp * inst (** Base case: expression with instrumentation *) + | Estruct of (Ident.fieldname * strexp) list * inst (** C structure *) + | Earray of exp * (exp * strexp) list * inst (** Array of given size. *) +(** There are two conditions imposed / used in the array case. +First, if some index and value pair appears inside an array +in a strexp, then the index is less than the size of the array. +For instance, x |->[10 | e1: v1] implies that e1 <= 9. +Second, if two indices appear in an array, they should be different. +For instance, x |->[10 | e1: v1, e2: v2] implies that e1 != e2. *) + +(** an atomic heap predicate *) +and hpred = + | Hpointsto of exp * strexp * exp + (** represents [exp|->strexp:typexp] where [typexp] + is an expression representing a type, e.h. [sizeof(t)]. *) + | Hlseg of lseg_kind * hpara * exp * exp * exp list + (** higher - order predicate for singly - linked lists. + Should ensure that exp1!= exp2 implies that exp1 is allocated. + This assumption is used in the rearrangement. The last [exp list] parameter + is used to denote the shared links by all the nodes in the list.*) + | Hdllseg of lseg_kind * hpara_dll * exp * exp * exp * exp * exp list +(** higher-order predicate for doubly-linked lists. *) + +(** parameter for the higher-order singly-linked list predicate. +Means "lambda (root,next,svars). Exists evars. body". +Assume that root, next, svars, evars are disjoint sets of +primed identifiers, and include all the free primed identifiers in body. +body should not contain any non - primed identifiers or program +variables (i.e. pvars). *) +and hpara = + { root: Ident.t; + next: Ident.t; + svars: Ident.t list; + evars: Ident.t list; + body: hpred list } + +(** parameter for the higher-order doubly-linked list predicates. +Assume that all the free identifiers in body_dll should belong to +cell, blink, flink, svars_dll, evars_dll. *) +and hpara_dll = + { cell: Ident.t; (** address cell *) + blink: Ident.t; (** backward link *) + flink: Ident.t; (** forward link *) + svars_dll: Ident.t list; + evars_dll: Ident.t list; + body_dll: hpred list } + +(** Sets of heap predicates *) +module HpredSet : Set.S with type elt = hpred + +(** {2 Compaction} *) + +type sharing_env + +(** Create a sharing env to store canonical representations *) +val create_sharing_env : unit -> sharing_env + +(** Return a canonical representation of the exp *) +val exp_compact : sharing_env -> exp -> exp + +(** Return a compact representation of the exp *) +val hpred_compact : sharing_env -> hpred -> hpred + +(** {2 Type Environment} *) + +type tenv (** Type for type environment. *) + +(** Create a new type environment. *) +val create_tenv : unit -> tenv + +(** Check if typename is found in tenv *) +val tenv_mem : tenv -> typename -> bool + +(** Look up a name in the global type environment. *) +val tenv_lookup : tenv -> typename -> typ option + +(** Add a (name,typ) pair to the global type environment. *) +val tenv_add : tenv -> typename -> typ -> unit + +(** look up the type for a mangled name in the current type environment *) +val get_typ : Mangled.t -> csu option -> tenv -> typ option + +(** expand a type if it is a typename by looking it up in the type environment *) +val expand_type : tenv -> typ -> typ + +(** type environment used for parsing, to be set by the client of the parser module *) +val tenv_for_parsing : tenv ref + +(** Load a type environment from a file *) +val load_tenv_from_file : DB.filename -> tenv option + +(** Save a type environment into a file *) +val store_tenv_to_file : DB.filename -> tenv -> unit + +(** convert the typename to a string *) +val typename_to_string : typename -> string + +(** name of the typename without qualifier *) +val typename_name : typename -> string + +(** iterate over a type environment *) +val tenv_iter : (typename -> typ -> unit) -> tenv -> unit + +val tenv_fold : (typename -> typ -> 'a -> 'a) -> tenv -> 'a -> 'a + +(** print a type environment *) +val pp_tenv : Format.formatter -> tenv -> unit + +(** Return the lhs expression of a hpred *) +val hpred_get_lhs : hpred -> exp + +(** Field used for objective-c reference counting *) +val objc_ref_counter_field : (Ident.fieldname * typ * item_annotation) + +(** {2 Comparision And Inspection Functions} *) + +val is_objc_ref_counter_field : (Ident.fieldname * typ * item_annotation) -> bool + +val has_objc_ref_counter : hpred -> bool + +val exp_is_null_literal : exp -> bool + +(** return true if [exp] is the special this/self expression *) +val exp_is_this : exp -> bool + +val loc_compare : location -> location -> int + +val loc_equal : location -> location -> bool + +val path_pos_equal : path_pos -> path_pos -> bool + +val pvar_get_name : pvar -> Mangled.t + +val pvar_to_string : pvar -> string + +(** Turn a pvar into a seed pvar (which stores the initial value of a stack var) *) +val pvar_to_seed : pvar -> pvar + +(** Check if the pvar is a callee var *) +val pvar_is_callee : pvar -> bool + +(** Check if the pvar is an abducted return var *) +val pvar_is_abducted_retvar : pvar -> bool + +(** Check if the pvar is a local var *) +val pvar_is_local : pvar -> bool + +(** Check if the pvar is a seed var *) +val pvar_is_seed : pvar -> bool + +(** Check if the pvar is a global var *) +val pvar_is_global : pvar -> bool + +(** Check if a pvar is the special "this" var *) +val pvar_is_this : pvar -> bool + +(** Check if the pvar is a return var *) +val pvar_is_return : pvar -> bool + +(** Make a static local name in objc *) +val mk_static_local_name : string -> string -> string + +(** Check if a pvar is a local static in objc *) +val is_static_local_name : string -> pvar -> bool + +(* A block pvar used to explain retain cycles *) +val block_pvar : pvar + +(** Check if a pvar is a local pointing to a block in objc *) +val is_block_pvar : pvar -> bool + +(** Check if type is a type for a block in objc *) +val is_block_type : typ -> bool + +(** Compare two pvar's *) +val pvar_compare : pvar -> pvar -> int + +(** Equality for pvar's *) +val pvar_equal : pvar -> pvar -> bool + +(** Comparision for fieldnames. *) +val fld_compare : Ident.fieldname -> Ident.fieldname -> int + +(** Equality for fieldnames. *) +val fld_equal : Ident.fieldname -> Ident.fieldname -> bool + +(** Check wheter the integer kind is a char *) +val ikind_is_char : ikind -> bool + +(** Check wheter the integer kind is unsigned *) +val ikind_is_unsigned : ikind -> bool + +(** Convert an int64 into an Int.t given the kind: the int64 is interpreted as unsigned according to the kind *) +val int_of_int64_kind : int64 -> ikind -> Int.t + +(** Comparison for typenames *) +val typename_compare : typename -> typename -> int + +(** Equality for typenames *) +val typename_equal : typename -> typename -> bool + +(** Equality for typenames *) +val csu_name_equal : (csu * Mangled.t) -> (csu * Mangled.t) -> bool + +(** Comparision for ptr_kind *) +val ptr_kind_compare : ptr_kind -> ptr_kind -> int + +(** Equality for consts. *) +val const_equal : const -> const -> bool + +(** Comparision for types. *) +val typ_compare : typ -> typ -> int + +(** Equality for types. *) +val typ_equal : typ -> typ -> bool + +(** Comparision for fieldnames * types * item annotations. *) +val fld_typ_ann_compare : Ident.fieldname * typ * item_annotation -> Ident.fieldname * typ * item_annotation -> int + +val unop_equal : unop -> unop -> bool + +val binop_equal : binop -> binop -> bool + +(** This function returns true if the operation is injective +wrt. each argument: op(e,-) and op(-, e) is injective for all e. +The return value false means "don't know". *) +val binop_injective : binop -> bool + +(** This function returns true if the operation can be inverted. *) +val binop_invertible : binop -> bool + +(** This function inverts an injective binary operator +with respect to the first argument. It returns an expression [e'] such that +BinOp([binop], [e'], [exp1]) = [exp2]. If the [binop] operation is not invertible, +the function raises an exception by calling "assert false". *) +val binop_invert : binop -> exp -> exp -> exp + +(** This function returns true if 0 is the right unit of [binop]. +The return value false means "don't know". *) +val binop_is_zero_runit : binop -> bool + +val mem_kind_compare : mem_kind -> mem_kind -> int + +val attribute_compare : attribute -> attribute -> int + +val attribute_equal : attribute -> attribute -> bool + +val attribute_category_compare : attribute_category -> attribute_category -> int + +val attribute_category_equal : attribute_category -> attribute_category -> bool + +(** Return the category to which the attribute belongs. *) +val attribute_to_category : attribute -> attribute_category + +val const_compare : const -> const -> int + +val const_equal : const -> const -> bool + +(** Return true if the constants have the same kind (both integers, ...) *) +val const_kind_equal : const -> const -> bool + +val exp_compare : exp -> exp -> int + +val exp_equal : exp -> exp -> bool + +val call_flags_compare : call_flags -> call_flags -> int + +val exp_typ_compare : (exp * typ) -> (exp * typ) -> int + +val instr_compare : instr -> instr -> int + +val exp_list_compare : exp list -> exp list -> int + +val exp_list_equal : exp list -> exp list -> bool + +val atom_compare : atom -> atom -> int + +val atom_equal : atom -> atom -> bool + +val strexp_compare : strexp -> strexp -> int + +val strexp_equal : strexp -> strexp -> bool + +val hpara_compare : hpara -> hpara -> int + +val hpara_equal : hpara -> hpara -> bool + +val hpara_dll_compare : hpara_dll -> hpara_dll -> int + +val hpara_dll_equal : hpara_dll -> hpara_dll -> bool + +val lseg_kind_compare : lseg_kind -> lseg_kind -> int + +val lseg_kind_equal : lseg_kind -> lseg_kind -> bool + +val hpred_compare : hpred -> hpred -> int + +val hpred_equal : hpred -> hpred -> bool + +val fld_strexp_compare : Ident.fieldname * strexp -> Ident.fieldname * strexp -> int + +val fld_strexp_list_compare : (Ident.fieldname * strexp) list -> (Ident.fieldname * strexp) list -> int + +val exp_strexp_compare : exp * strexp -> exp * strexp -> int + +(** Compare function for annotations. *) +val annotation_compare : annotation -> annotation -> int + +(** Compare function for annotation items. *) +val item_annotation_compare : item_annotation -> item_annotation -> int + +(** Compare function for Method annotations. *) +val method_annotation_compare : method_annotation -> method_annotation -> int + +(** Empty item annotation. *) +val item_annotation_empty : item_annotation + +(** Empty method annotation. *) +val method_annotation_empty : method_annotation + +(** Check if the item annodation is empty. *) +val item_annotation_is_empty : item_annotation -> bool + +(** Check if the method annodation is empty. *) +val method_annotation_is_empty : method_annotation -> bool + +(** {2 Pretty Printing} *) + +(** Begin change color if using diff printing, return updated printenv and change status *) +val color_pre_wrapper : printenv -> Format.formatter -> 'a -> printenv * bool + +(** Close color annotation if changed *) +val color_post_wrapper : bool -> printenv -> Format.formatter -> unit + +(** String representation of a unary operator. *) +val str_unop : unop -> string + +(** String representation of a binary operator. *) +val str_binop : printenv -> binop -> string + +(** Pretty print a location. *) +val pp_loc : Format.formatter -> location -> unit + +val loc_to_string : location -> string + +(** Dump a location. *) +val d_loc : location -> unit + +(** name of the allocation function for the given memory kind *) +val mem_alloc_pname : mem_kind -> Procname.t + +(** name of the deallocation function for the given memory kind *) +val mem_dealloc_pname : mem_kind -> Procname.t + +(** Pretty print an annotation. *) +val pp_annotation : Format.formatter -> annotation -> unit + +(** Pretty print a const. *) +val pp_const: printenv -> Format.formatter -> const -> unit + +(** Pretty print an item annotation. *) +val pp_item_annotation : Format.formatter -> item_annotation -> unit + +(** Pretty print a method annotation. *) +val pp_method_annotation : string -> Format.formatter -> method_annotation -> unit + +(** Pretty print a type. *) +val pp_typ : printenv -> Format.formatter -> typ -> unit + +(** Pretty print a type with all the details. *) +val pp_typ_full : printenv -> Format.formatter -> typ -> unit + +val typ_to_string : typ -> string + +(** [pp_type_decl pe pp_base pp_size f typ] pretty prints a type declaration. +pp_base prints the variable for a declaration, or can be skip to print only the type +pp_size prints the expression for the array size *) +val pp_type_decl: printenv -> (Format.formatter -> unit -> unit) -> +(printenv -> Format.formatter -> exp -> unit) -> +Format.formatter -> typ -> unit + +(** Dump a type with all the details. *) +val d_typ_full : typ -> unit + +(** Dump a list of types. *) +val d_typ_list : typ list -> unit + +(** Pretty print a program variable. *) +val pp_pvar : printenv -> Format.formatter -> pvar -> unit + +(** Pretty print a pvar which denotes a value, not an address *) +val pp_pvar_value : printenv -> Format.formatter -> pvar -> unit + +(** Dump a program variable. *) +val d_pvar : pvar -> unit + +(** Pretty print a list of program variables. *) +val pp_pvar_list : printenv -> Format.formatter -> pvar list -> unit + +(** Dump a list of program variables. *) +val d_pvar_list : pvar list -> unit + +(** convert the attribute to a string *) +val attribute_to_string : printenv -> attribute -> string + +(** convert a dexp to a string *) +val dexp_to_string : dexp -> string + +(** Pretty print a dexp. *) +val pp_dexp : printenv -> Format.formatter -> dexp -> unit + +(** Pretty print an expression. *) +val pp_exp : printenv -> Format.formatter -> exp -> unit + +(** Pretty print an expression with type. *) +val pp_exp_typ : printenv -> Format.formatter -> exp * typ -> unit + +(** Convert an expression to a string *) +val exp_to_string : exp -> string + +(** dump an expression. *) +val d_exp : exp -> unit + +(** Pretty print a type. *) +val pp_texp : printenv -> Format.formatter -> exp -> unit + +(** Pretty print a type with all the details. *) +val pp_texp_full : printenv -> Format.formatter -> exp -> unit + +(** Dump a type expression with all the details. *) +val d_texp_full : exp -> unit + +(** Pretty print a list of expressions. *) +val pp_exp_list : printenv -> Format.formatter -> exp list -> unit + +(** Dump a list of expressions. *) +val d_exp_list : exp list -> unit + +(** Pretty print an offset *) +val pp_offset : printenv -> Format.formatter -> offset -> unit + +(** Dump an offset *) +val d_offset : offset -> unit + +(** Pretty print a list of offsets *) +val pp_offset_list : printenv -> Format.formatter -> offset list -> unit + +(** Dump a list of offsets *) +val d_offset_list : offset list -> unit + +(** Get the location of the instruction *) +val instr_get_loc : instr -> location + +(** get the expressions occurring in the instruction *) +val instr_get_exps : instr -> exp list + +(** Pretty print an instruction. *) +val pp_instr : printenv -> Format.formatter -> instr -> unit + +(** Dump an instruction. *) +val d_instr : instr -> unit + +(** Pretty print a list of instructions. *) +val pp_instr_list : printenv -> Format.formatter -> instr list -> unit + +(** Dump a list of instructions. *) +val d_instr_list : instr list -> unit + +(** Pretty print a value path *) +val pp_vpath : printenv -> Format.formatter -> vpath -> unit + +(** Pretty print an atom. *) +val pp_atom : printenv -> Format.formatter -> atom -> unit + +(** Dump an atom. *) +val d_atom : atom -> unit + +(** return a string representing the inst *) +val inst_to_string : inst -> string + +(** Pretty print a strexp. *) +val pp_sexp : printenv -> Format.formatter -> strexp -> unit + +(** Dump a strexp. *) +val d_sexp : strexp -> unit + +(** Pretty print a strexp list. *) +val pp_sexp_list : printenv -> Format.formatter -> strexp list -> unit + +(** Dump a strexp. *) +val d_sexp_list : strexp list -> unit + +(** Pretty print a hpred. *) +val pp_hpred : printenv -> Format.formatter -> hpred -> unit + +(** Dump a hpred. *) +val d_hpred : hpred -> unit + +(** Pretty print a hpara. *) +val pp_hpara : printenv -> Format.formatter -> hpara -> unit + +(** Pretty print a list of hparas. *) +val pp_hpara_list : printenv -> Format.formatter -> hpara list -> unit + +(** Pretty print a hpara_dll. *) +val pp_hpara_dll : printenv -> Format.formatter -> hpara_dll -> unit + +(** Pretty print a list of hpara_dlls. *) +val pp_hpara_dll_list : printenv -> Format.formatter -> hpara_dll list -> unit + +(** Module Predicates records the occurrences of predicates as parameters +of (doubly -)linked lists and Epara. Provides unique numbering for predicates and an iterator. *) +module Predicates : sig +(** predicate environment *) + type env + (** create an empty predicate environment *) + val empty_env : unit -> env + (** return true if the environment is empty *) + val is_empty : env -> bool + (** return the id of the hpara *) + val get_hpara_id : env -> hpara -> int + (** return the id of the hpara_dll *) + val get_hpara_dll_id : env -> hpara_dll -> int + (** [iter env f f_dll] iterates [f] and [f_dll] on all the hpara and hpara_dll, + passing the unique id to the functions. The iterator can only be used once. *) + val iter : env -> (int -> hpara -> unit) -> (int -> hpara_dll -> unit) -> unit + (** Process one hpred, updating the predicate environment *) + val process_hpred : env -> hpred -> unit +end + +(** Pretty print a hpred with optional predicate env *) +val pp_hpred_env : printenv -> Predicates.env option -> Format.formatter -> hpred -> unit + +(** {2 Functions for traversing SIL data types} *) + +(** This function should be used before adding a new +index to Earray. The [exp] is the newly created +index. This function "cleans" [exp] according to whether it is the footprint or current part of the prop. +The function faults in the re - execution mode, as an internal check of the tool. *) +val array_clean_new_index : bool -> exp -> exp + +(** Change exps in strexp using [f]. *) +(** WARNING: the result might not be normalized. *) +val strexp_expmap : (exp * inst option -> exp * inst option) -> strexp -> strexp + +(** Change exps in hpred by [f]. *) +(** WARNING: the result might not be normalized. *) +val hpred_expmap : (exp * inst option -> exp * inst option) -> hpred -> hpred + +(** Change instrumentations in hpred using [f]. *) +val hpred_instmap : (inst -> inst) -> hpred -> hpred + +(** Change exps in hpred list by [f]. *) +(** WARNING: the result might not be normalized. *) +val hpred_list_expmap : (exp * inst option -> exp * inst option) -> hpred list -> hpred list + +(** Change exps in atom by [f]. *) +(** WARNING: the result might not be normalized. *) +val atom_expmap : (exp -> exp) -> atom -> atom + +(** Change exps in atom list by [f]. *) +(** WARNING: the result might not be normalized. *) +val atom_list_expmap : (exp -> exp) -> atom list -> atom list + +(** {2 Function for computing lexps in sigma} *) + +val hpred_list_get_lexps : (exp -> bool) -> hpred list -> exp list + +(** {2 Utility Functions for Expressions} *) + +(** Turn an expression representing a type into the type it represents +If not a sizeof, return the default type if given, otherwise raise an exception *) +val texp_to_typ : typ option -> exp -> typ + +(** If a struct type with field f, return the type of f. +If not, return the default type if given, otherwise raise an exception *) +val struct_typ_fld : typ option -> Ident.fieldname -> typ -> typ + +(** If an array type, return the type of the element. +If not, return the default type if given, otherwise raise an exception *) +val array_typ_elem : typ option -> typ -> typ + +(** Return the root of [lexp]. *) +val root_of_lexp : exp -> exp + +(** Get an expression "undefined", the boolean indicates whether the undefined value goest into the footprint *) +val exp_get_undefined : bool -> exp + +(** Checks whether an expression denotes a location using pointer arithmetic. +Currently, catches array - indexing expressions such as a[i] only. *) +val exp_pointer_arith : exp -> bool + +(** Integer constant 0 *) +val exp_zero : exp + +(** Null constant *) +val exp_null : exp + +(** Integer constant 1 *) +val exp_one : exp + +(** Integer constant -1 *) +val exp_minus_one : exp + +(** Create integer constant *) +val exp_int : Int.t -> exp + +(** Create float constant *) +val exp_float : float -> exp + +(** Create integer constant corresponding to the boolean value *) +val exp_bool : bool -> exp + +(** Create expresstion [e1 == e2] *) +val exp_eq : exp -> exp -> exp + +(** Create expresstion [e1 != e2] *) +val exp_ne : exp -> exp -> exp + +(** Create expresstion [e1 <= e2] *) +val exp_le : exp -> exp -> exp + +(** Create expression [e1 < e2] *) +val exp_lt : exp -> exp -> exp + +(** {2 Functions for computing program variables} *) + +val exp_fpv : exp -> pvar list + +val strexp_fpv : strexp -> pvar list + +val atom_fpv : atom -> pvar list + +val hpred_fpv : hpred -> pvar list + +val hpara_fpv : hpara -> pvar list + +(** {2 Functions for computing free non-program variables} *) + +(** Type of free variables. These include primed, normal and footprint variables. We remember the order in which variables are added. *) +type fav + +(** flag to indicate whether fav's are stored in duplicate form -- only to be used with fav_to_list *) +val fav_duplicates : bool ref + +(** Pretty print a fav. *) +val pp_fav : printenv -> Format.formatter -> fav -> unit + +(** Create a new [fav]. *) +val fav_new : unit -> fav + +(** Emptyness check. *) +val fav_is_empty : fav -> bool + +(** Check whether a predicate holds for all elements. *) +val fav_for_all : fav -> (Ident.t -> bool) -> bool + +(** Check whether a predicate holds for some elements. *) +val fav_exists : fav -> (Ident.t -> bool) -> bool + +(** Membership test fot [fav] *) +val fav_mem : fav -> Ident.t -> bool + +(** Convert a list to a fav. *) +val fav_from_list : Ident.t list -> fav + +(** Convert a [fav] to a list of identifiers while preserving the order +that identifiers were added to [fav]. *) +val fav_to_list : fav -> Ident.t list + +(** Copy a [fav]. *) +val fav_copy : fav -> fav + +(** Turn a xxx_fav_add function into a xxx_fav function *) +val fav_imperative_to_functional : (fav -> 'a -> unit) -> 'a -> fav + +(** [fav_filter_ident fav f] only keeps [id] if [f id] is true. *) +val fav_filter_ident : fav -> (Ident.t -> bool) -> unit + +(** Like [fav_filter_ident] but return a copy. *) +val fav_copy_filter_ident : fav -> (Ident.t -> bool) -> fav + +(** [fav_subset_ident fav1 fav2] returns true if every ident in [fav1] +is in [fav2].*) +val fav_subset_ident : fav -> fav -> bool + +(** add identifier list to fav *) +val ident_list_fav_add : Ident.t list -> fav -> unit + +(** [exp_fav_add fav exp] extends [fav] with the free variables of [exp] *) +val exp_fav_add : fav -> exp -> unit + +val exp_fav : exp -> fav + +val exp_fav_list : exp -> Ident.t list + +val ident_in_exp : Ident.t -> exp -> bool + +val strexp_fav_add : fav -> strexp -> unit + +val atom_fav_add : fav -> atom -> unit + +val atom_fav: atom -> fav + +val hpred_fav_add : fav -> hpred -> unit + +val hpred_fav : hpred -> fav + +val hpara_fav_add : fav -> hpara -> unit + +(** Variables in hpara, excluding bound vars in the body *) +val hpara_shallow_av : hpara -> fav + +(** Variables in hpara_dll, excluding bound vars in the body *) +val hpara_dll_shallow_av : hpara_dll -> fav + +(** {2 Functions for computing all free or bound non-program variables} *) + +(** Non-program variables include all of primed, normal and footprint +variables. Thus, the functions essentially compute all the +identifiers occuring in a parameter. Some variables can appear more +than once in the result. *) + +val exp_av_add : fav -> exp -> unit + +val strexp_av_add : fav -> strexp -> unit + +val atom_av_add : fav -> atom -> unit + +val hpred_av_add : fav -> hpred -> unit + +val hpara_av_add : fav -> hpara -> unit + +(** {2 Substitution} *) + +type subst + +(** Create a substitution from a list of pairs. +For all (id1, e1), (id2, e2) in the input list, +if id1 = id2, then e1 = e2. *) +val sub_of_list : (Ident.t * exp) list -> subst + +(** like sub_of_list, but allow duplicate ids and only keep the first occurrence *) +val sub_of_list_duplicates : (Ident.t * exp) list -> subst + +(** Convert a subst to a list of pairs. *) +val sub_to_list : subst -> (Ident.t * exp) list + +(** The empty substitution. *) +val sub_empty : subst + +(** Comparison for substitutions. *) +val sub_compare : subst -> subst -> int + +(** Equality for substitutions. *) +val sub_equal : subst -> subst -> bool + +(** Compute the common id-exp part of two inputs [subst1] and [subst2]. +The first component of the output is this common part. +The second and third components are the remainder of [subst1] +and [subst2], respectively. *) +val sub_join : subst -> subst -> subst + +(** Compute the common id-exp part of two inputs [subst1] and [subst2]. +The first component of the output is this common part. +The second and third components are the remainder of [subst1] +and [subst2], respectively. *) +val sub_symmetric_difference : subst -> subst -> subst * subst * subst + +(** [sub_find filter sub] returns the expression associated to the first identifier that satisfies [filter]. Raise [Not_found] if there isn't one. *) +val sub_find : (Ident.t -> bool) -> subst -> exp + +(** [sub_filter filter sub] restricts the domain of [sub] to the +identifiers satisfying [filter]. *) +val sub_filter : (Ident.t -> bool) -> subst -> subst + +(** [sub_filter_exp filter sub] restricts the domain of [sub] to the +identifiers satisfying [filter(id, sub(id))]. *) +val sub_filter_pair : (Ident.t * exp -> bool) -> subst -> subst + +(** [sub_range_partition filter sub] partitions [sub] according to +whether range expressions satisfy [filter]. *) +val sub_range_partition : (exp -> bool) -> subst -> subst * subst + +(** [sub_domain_partition filter sub] partitions [sub] according to +whether domain identifiers satisfy [filter]. *) +val sub_domain_partition : (Ident.t -> bool) -> subst -> subst * subst + +(** Return the list of identifiers in the domain of the substitution. *) +val sub_domain : subst -> Ident.t list + +(** Return the list of expressions in the range of the substitution. *) +val sub_range : subst -> exp list + +(** [sub_range_map f sub] applies [f] to the expressions in the range of [sub]. *) +val sub_range_map : (exp -> exp) -> subst -> subst + +(** [sub_map f g sub] applies the renaming [f] to identifiers in the domain +of [sub] and the substitution [g] to the expressions in the range of [sub]. *) +val sub_map : (Ident.t -> Ident.t) -> (exp -> exp) -> subst -> subst + +(** Checks whether [id] belongs to the domain of [subst]. *) +val mem_sub : Ident.t -> subst -> bool + +(** Extend substitution and return [None] if not possible. *) +val extend_sub : subst -> Ident.t -> exp -> subst option + +(** Free auxilary variables in the domain and range of the +substitution. *) +val sub_fav_add : fav -> subst -> unit + +(** Free or bound auxilary variables in the domain and range of the +substitution. *) +val sub_av_add : fav -> subst -> unit + +(** Compute free pvars in a sub *) +val sub_fpv : subst -> pvar list + +(** substitution functions *) +(** WARNING: these functions do not ensure that the results are normalized. *) +val exp_sub : subst -> exp -> exp + +val atom_sub : subst -> atom -> atom + +val instr_sub : subst -> instr -> instr + +val hpred_sub : subst -> hpred -> hpred + +val hpara_sub : subst -> hpara -> hpara + +(** {2 Functions for replacing occurrences of expressions.} *) + +(** The first parameter should define a partial function. +No parts of hpara are replaced by these functions. *) + +val exp_replace_exp : (exp * exp) list -> exp -> exp + +val strexp_replace_exp : (exp * exp) list -> strexp -> strexp + +val atom_replace_exp : (exp * exp) list -> atom -> atom + +val hpred_replace_exp : (exp * exp) list -> hpred -> hpred + +(** {2 Functions for constructing or destructing entities in this module} *) + +(** Unknown location *) +val loc_none : location + +(** [mk_pvar name proc_name suffix] creates a program var with the given function name and suffix *) +val mk_pvar : Mangled.t -> Procname.t -> pvar + +(** [mk_pvar_callee name proc_name] creates a program var for a callee function with the given function name *) +val mk_pvar_callee : Mangled.t -> Procname.t -> pvar + +(** create a global variable with the given name *) +val mk_pvar_global : Mangled.t -> pvar + +(** create an abducted return variable for a call to [proc_name] at [loc] *) +val mk_pvar_abducted_ret : Procname.t -> location -> pvar + +(** Turn an ordinary program variable into a callee program variable *) +val pvar_to_callee : Procname.t -> pvar -> pvar + +(** Compute the offset list of an expression *) +val exp_get_offsets : exp -> offset list + +(** Add the offset list to an expression *) +val exp_add_offsets : exp -> offset list -> exp + +val sigma_to_sigma_ne : hpred list -> (atom list * hpred list) list + +(** [hpara_instantiate para e1 e2 elist] instantiates [para] with [e1], +[e2] and [elist]. If [para = lambda (x, y, xs). exists zs. b], +then the result of the instantiation is [b\[e1 / x, e2 / y, elist / xs, _zs'/ zs\]] +for some fresh [_zs'].*) +val hpara_instantiate : hpara -> exp -> exp -> exp list -> Ident.t list * hpred list + +(** [hpara_dll_instantiate para cell blink flink elist] instantiates [para] with [cell], +[blink], [flink], and [elist]. If [para = lambda (x, y, z, xs). exists zs. b], +then the result of the instantiation is [b\[cell / x, blink / y, flink / z, elist / xs, _zs'/ zs\]] +for some fresh [_zs'].*) +val hpara_dll_instantiate : hpara_dll -> exp -> exp -> exp -> exp list -> Ident.t list * hpred list + +(** Return the list of expressions that could be understood as outgoing arrows from the strexp *) +val strexp_get_target_exps : strexp -> exp list + +(** Iterate over all the subtypes in the type (including the type itself) *) +val typ_iter_types : (typ -> unit) -> typ -> unit +(** Iterate over all the types (and subtypes) in the expression *) +val exp_iter_types : (typ -> unit) -> exp -> unit +(** Iterate over all the types (and subtypes) in the instruction *) +val instr_iter_types : (typ -> unit) -> instr -> unit + +val global_error : pvar diff --git a/infer/src/backend/specs.ml b/infer/src/backend/specs.ml new file mode 100644 index 000000000..e491e900c --- /dev/null +++ b/infer/src/backend/specs.ml @@ -0,0 +1,796 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Specifications and spec table *) + +module L = Logging +module F = Format +open Utils + +(* =============== START of support for spec tables =============== *) + +(** Module for joined props *) +module Jprop = struct + + (** Remember when a prop is obtained as the join of two other props; the first parameter is an id *) + type 'a t = + | Prop of int * 'a Prop.t + | Joined of int * 'a Prop.t * 'a t * 'a t + + let to_prop = function + | Prop (_, p) -> p + | Joined (_, p, _, _) -> p + + let to_number = function + | Prop (n, _) -> n + | Joined (n, _, _, _) -> n + + let rec fav_add_dfs fav = function + | Prop (_, p) -> Prop.prop_fav_add_dfs fav p + | Joined (_, p, jp1, jp2) -> + Prop.prop_fav_add_dfs fav p; + fav_add_dfs fav jp1; + fav_add_dfs fav jp2 + + let rec jprop_sub sub = function + | Prop (n, p) -> Prop (n, Prop.prop_sub sub p) + | Joined (n, p, jp1, jp2) -> Joined (n, Prop.prop_sub sub p, jprop_sub sub jp1, jprop_sub sub jp2) + + let rec normalize = function + | Prop (n, p) -> Prop (n, Prop.normalize p) + | Joined (n, p, jp1, jp2) -> Joined (n, Prop.normalize p, normalize jp1, normalize jp2) + + (** Return a compact representation of the jprop *) + let rec compact sh = function + | Prop (n, p) -> + Prop (n, Prop.prop_compact sh p) + | Joined(n, p, jp1, jp2) -> + Joined(n, Prop.prop_compact sh p, compact sh jp1, compact sh jp2) + + (** Print the toplevel prop *) + let pp_short pe f jp = + Prop.pp_prop pe f (to_prop jp) + + (** Dump the toplevel prop *) + let d_shallow (jp: Prop.normal t) = L.add_print_action (L.PTjprop_short, Obj.repr jp) + + (** Get identifies of the jprop *) + let get_id = function + | Prop (n, _) -> n + | Joined (n, _, _, _) -> n + + (** Print a list of joined props, the boolean indicates whether to print subcomponents of joined props *) + let pp_list pe shallow f jplist = + let rec pp_seq_newline f = function + | [] -> () + | [Prop (n, p)] -> F.fprintf f "PROP %d:@\n%a" n (Prop.pp_prop pe) p + | [Joined (n, p, p1, p2)] -> + if not shallow then F.fprintf f "%a@\n" pp_seq_newline [p1]; + if not shallow then F.fprintf f "%a@\n" pp_seq_newline [p2]; + F.fprintf f "PROP %d (join of %d,%d):@\n%a" n (get_id p1) (get_id p2) (Prop.pp_prop pe) p + | jp:: l -> + F.fprintf f "%a@\n" pp_seq_newline [jp]; + pp_seq_newline f l in + pp_seq_newline f jplist + + (** dump a joined prop list, the boolean indicates whether to print toplevel props only *) + let d_list (shallow: bool) (jplist: Prop.normal t list) = L.add_print_action (L.PTjprop_list, Obj.repr (shallow, jplist)) + + (** Comparison for joined_prop *) + let rec compare jp1 jp2 = match jp1, jp2 with + | Prop (_, p1), Prop (_, p2) -> + Prop.prop_compare p1 p2 + | Prop _, _ -> - 1 + | _, Prop _ -> 1 + | Joined (_, p1, jp1, jq1), Joined (_, p2, jp2, jq2) -> + let n = Prop.prop_compare p1 p2 in + if n <> 0 then n + else + let n = compare jp1 jp2 in + if n <> 0 then n else compare jq1 jq2 + + (** Return true if the two join_prop's are equal *) + let equal jp1 jp2 = + compare jp1 jp2 == 0 + + let rec fav_add fav = function + | Prop (_, p) -> Prop.prop_fav_add fav p + | Joined (_, p, jp1, jp2) -> + Prop.prop_fav_add fav p; + fav_add fav jp1; + fav_add fav jp2 + + let rec jprop_sub sub = function + | Prop (n, p) -> Prop (n, Prop.prop_sub sub p) + | Joined (n, p, jp1, jp2) -> + let p' = Prop.prop_sub sub p in + let jp1' = jprop_sub sub jp1 in + let jp2' = jprop_sub sub jp2 in + Joined (n, p', jp1', jp2') + + let filter (f: 'a t -> 'b option) jpl = + let rec do_filter acc = function + | [] -> acc + | (Prop (_, p) as jp) :: jpl -> + (match f jp with + | Some x -> + do_filter (x:: acc) jpl + | None -> do_filter acc jpl) + | (Joined (_, p, jp1, jp2) as jp) :: jpl -> + (match f jp with + | Some x -> + do_filter (x:: acc) jpl + | None -> + do_filter acc (jpl @ [jp1; jp2])) in + do_filter [] jpl + + let rec map (f : 'a Prop.t -> 'b Prop.t) = function + | Prop (n, p) -> Prop (n, f p) + | Joined (n, p, jp1, jp2) -> Joined (n, f p, map f jp1, map f jp2) +end +(***** End of module Jprop *****) + +module Visitedset = + Set.Make (struct + type t = int * int list + let compare (node_id1, line1) (node_id2, line2) = int_compare node_id1 node_id2 + end) + +let visited_str vis = + let s = ref "" in + let lines = ref IntSet.empty in + let do_one (node, ns) = + (* if list_length ns > 1 then + begin + let ss = ref "" in + list_iter (fun n -> ss := !ss ^ " " ^ string_of_int n) ns; + L.err "Node %d has lines %s@." node !ss + end; *) + list_iter (fun n -> lines := IntSet.add n !lines) ns in + Visitedset.iter do_one vis; + IntSet.iter (fun n -> s := !s ^ " " ^ string_of_int n) !lines; + !s + +(** A spec consists of: +pre: a joined prop +post: a list of props with path +visited: a list of pairs (node_id, line) for the visited nodes *) +type 'a spec = { pre: 'a Jprop.t; posts: ('a Prop.t * Paths.Path.t) list; visited : Visitedset.t } + +module NormSpec : sig (* encapsulate type for normalized specs *) + type t + val normalize : Prop.normal spec -> t + val tospec : t -> Prop.normal spec + val tospecs : t list -> Prop.normal spec list + val compact : Sil.sharing_env -> t -> t (** Return a compact representation of the spec *) + val erase_join_info_pre : t -> t (** Erase join info from pre of spec *) +end = struct + type t = Prop.normal spec + + let tospec spec = spec + + let tospecs specs = specs + + let spec_fav (spec: Prop.normal spec) : Sil.fav = + let fav = Sil.fav_new () in + Jprop.fav_add_dfs fav spec.pre; + list_iter (fun (p, path) -> Prop.prop_fav_add_dfs fav p) spec.posts; + fav + + let spec_sub sub spec = + { pre = Jprop.normalize (Jprop.jprop_sub sub spec.pre); + posts = list_map (fun (p, path) -> (Prop.normalize (Prop.prop_sub sub p), path)) spec.posts; + visited = spec.visited } + + (** Convert spec into normal form w.r.t. variable renaming *) + let normalize (spec: Prop.normal spec) : Prop.normal spec = + let fav = spec_fav spec in + let idlist = Sil.fav_to_list fav in + let count = ref 0 in + let sub = Sil.sub_of_list (list_map (fun id -> incr count; (id, Sil.Var (Ident.create_normal Ident.name_spec !count))) idlist) in + spec_sub sub spec + + (** Return a compact representation of the spec *) + let compact sh spec = + let pre = Jprop.compact sh spec.pre in + let posts = list_map (fun (p, path) -> (Prop.prop_compact sh p, path)) spec.posts in + { pre = pre; posts = posts; visited = spec.visited } + + (** Erase join info from pre of spec *) + let erase_join_info_pre spec = + let spec' = { spec with pre = Jprop.Prop (1, Jprop.to_prop spec.pre) } in + normalize spec' +end + +type norm_spec = NormSpec.t + +(** Convert spec into normal form w.r.t. variable renaming *) +let spec_normalize = + NormSpec.normalize + +(** Cast a list of normalized specs to a list of specs *) +let normalized_specs_to_specs = + NormSpec.tospecs + +module CallStats = struct (** module for tracing stats of function calls *) + module PnameLocHash = Hashtbl.Make (struct + type t = Procname.t * Sil.location + let hash (pname, loc) = Hashtbl.hash (Procname.hash_pname pname, loc.Sil.line) + let equal (pname1, loc1) (pname2, loc2) = + Sil.loc_equal loc1 loc2 && Procname.equal pname1 pname2 + end) + + type call_result = (** kind of result of a procedure call *) + | CR_success (** successful call *) + | CR_not_met (** precondition not met *) + | CR_not_found (** the callee has no specs *) + | CR_skip (** the callee was skipped *) + + type trace = (call_result * bool) list + + type t = trace PnameLocHash.t + + let trace_add tr (res : call_result) in_footprint = (res, in_footprint) :: tr + + let empty_trace : trace = [] + + let init calls = + let hash = PnameLocHash.create 1 in + let do_call pn_loc = PnameLocHash.add hash pn_loc empty_trace in + list_iter do_call calls; + hash + + let trace t proc_name loc res in_footprint = + let tr_old = try PnameLocHash.find t (proc_name, loc) with + | Not_found -> + PnameLocHash.add t (proc_name, loc) empty_trace; + empty_trace in + let tr_new = trace_add tr_old res in_footprint in + PnameLocHash.replace t (proc_name, loc) tr_new + + let tr_elem_str (cr, in_footprint) = + let s1 = match cr with + | CR_success -> "OK" + | CR_not_met -> "NotMet" + | CR_not_found -> "NotFound" + | CR_skip -> "Skip" in + let s2 = if in_footprint then "FP" else "RE" in + s1 ^ ":" ^ s2 + + let pp_trace fmt tr = Utils.pp_seq (fun fmt x -> F.fprintf fmt "%s" (tr_elem_str x)) fmt (list_rev tr) + + let iter f t = + let elems = ref [] in + PnameLocHash.iter (fun x tr -> elems := (x, tr) :: !elems) t; + let sorted_elems = + let compare ((pname1, loc1), _) ((pname2, loc2), _) = + let n = Procname.compare pname1 pname2 in + if n <> 0 then n else Sil.loc_compare loc1 loc2 in + list_sort compare !elems in + list_iter (fun (x, tr) -> f x tr) sorted_elems + + let pp fmt t = + let do_call (pname, loc) tr = F.fprintf fmt "%a %a: %a@\n" Procname.pp pname Sil.pp_loc loc pp_trace tr in + iter do_call t +end + +(** stats of the calls performed during the analysis *) +type call_stats = CallStats.t + +(** Execution statistics *) +type stats = + { stats_time: float; (** Analysis time for the procedure *) + stats_timeout: bool; (** Flag to indicate whether a timeout occurred *) + stats_calls: Cg.in_out_calls; (** num of procs calling, and called *) + symops: int; (** Number of SymOp's throughout the whole analysis of the function *) + err_log: Errlog.t; (** Error log for the procedure *) + mutable nodes_visited_fp : IntSet.t; (** Nodes visited during the footprint phase *) + mutable nodes_visited_re : IntSet.t; (** Nodes visited during the re-execution phase *) + call_stats : call_stats; + cyclomatic : int; + } + +type status = ACTIVE | INACTIVE + +type phase = FOOTPRINT | RE_EXECUTION + +type dependency_map_t = int Procname.Map.t + +type is_library = Source | Library + +(** Payload: results of some analysis *) +type payload = + | PrePosts of NormSpec.t list (** list of specs *) + | TypeState of unit TypeState.t option (** final typestate *) + +type summary = + { dependency_map: dependency_map_t; (** maps children procs to timestamp as last seen at the start of an analysys phase for this proc *) + loc: Sil.location; (** original file and line number *) + nodes: int list; (** ids of cfg nodes of the procedure *) + ret_type : Sil.typ; (** type of the return parameter *) + formals : (string * Sil.typ) list; (** name and type of the formal parameters of the procedure *) + phase: phase; (** in FOOTPRINT phase or in RE_EXECUTION PHASE *) + proc_name : Procname.t; (** name of the procedure *) + proc_flags : proc_flags; (** flags of the procedure *) + payload: payload; (** payload containing the result of some analysis *) + sessions: int ref; (** Session number: how many nodes went trough symbolic execution *) + stats: stats; (** statistics: execution time and list of errors *) + status: status; (** ACTIVE when the proc is being analyzed *) + timestamp: int; (** Timestamp of the specs, >= 0, increased every time the specs change *) + attributes : Sil.proc_attributes; (** Attributes of the procedure *) + } + +(** origin of a summary: current results dir, a spec library, or models *) +type origin = + | Res_dir + | Spec_lib + | Models + +type spec_tbl = (summary * origin) Procname.Hash.t + +let spec_tbl: spec_tbl = Procname.Hash.create 128 + +let clear_spec_tbl () = Procname.Hash.clear spec_tbl + +(** pretty print analysis time; if [whole_seconds] is true, only print time in seconds *) +let pp_time whole_seconds fmt t = + if whole_seconds then F.fprintf fmt "%3.0f s" t + else F.fprintf fmt "%f s" t + +let pp_timeout fmt = function + | true -> F.fprintf fmt "Y" + | false -> F.fprintf fmt "N" + +let pp_stats whole_seconds fmt stats = + F.fprintf fmt "TIME:%a TIMEOUT:%a SYMOPS:%d CALLS:%d,%d@\n" (pp_time whole_seconds) stats.stats_time pp_timeout stats.stats_timeout stats.symops stats.stats_calls.Cg.in_calls stats.stats_calls.Cg.out_calls; + F.fprintf fmt "ERRORS: @[%a@]" Errlog.pp stats.err_log + +(** Print the spec *) +let pp_spec pe num_opt fmt spec = + let num_str = match num_opt with + | None -> "----------" + | Some (n, tot) -> Format.sprintf "%d of %d [nvisited:%s]" n tot (visited_str spec.visited) in + let pre = Jprop.to_prop spec.pre in + let pe_post = Prop.prop_update_obj_sub pe pre in + let post_list = list_map fst spec.posts in + match pe.pe_kind with + | PP_TEXT -> + F.fprintf fmt "--------------------------- %s ---------------------------@\n" num_str; + F.fprintf fmt "PRE:@\n%a@\n" (Prop.pp_prop pe_text) pre; + F.fprintf fmt "%a@\n" (Propgraph.pp_proplist pe_post "POST" (pre, true)) post_list; + F.fprintf fmt "----------------------------------------------------------------" + | PP_HTML -> + F.fprintf fmt "--------------------------- %s ---------------------------@\n" num_str; + F.fprintf fmt "PRE:@\n%a%a%a@\n" Io_infer.Html.pp_start_color Blue (Prop.pp_prop (pe_html Blue)) pre Io_infer.Html.pp_end_color (); + F.fprintf fmt "%a" (Propgraph.pp_proplist pe_post "POST" (Jprop.to_prop spec.pre, true)) post_list; + F.fprintf fmt "----------------------------------------------------------------" + | PP_LATEX -> + F.fprintf fmt "\\textbf{\\large Requires}\\\\@\n@[%a%a%a@]\\\\@\n" Latex.pp_color Blue (Prop.pp_prop (pe_latex Blue)) pre Latex.pp_color pe.pe_color; + F.fprintf fmt "\\textbf{\\large Ensures}\\\\@\n@[%a@]" (Propgraph.pp_proplist pe_post "POST" (pre, true)) post_list + +(** Dump a spec *) +let d_spec (spec: 'a spec) = L.add_print_action (L.PTspec, Obj.repr spec) + +let pp_specs pe fmt specs = + let total = list_length specs in + let cnt = ref 0 in + match pe.pe_kind with + | PP_TEXT -> + list_iter (fun spec -> incr cnt; F.fprintf fmt "%a@\n" (pp_spec pe (Some (!cnt, total))) spec) specs + | PP_HTML -> + list_iter (fun spec -> incr cnt; F.fprintf fmt "%a
@\n" (pp_spec pe (Some (!cnt, total))) spec) specs + | PP_LATEX -> + list_iter (fun spec -> incr cnt; F.fprintf fmt "\\subsection*{Spec %d of %d}@\n\\(%a\\)@\n" !cnt total (pp_spec pe None) spec) specs + +(** Print the decpendency map *) +let pp_dependency_map fmt dependency_map = + let pp_entry fmt proc_name n = F.fprintf fmt "%a=%d " Procname.pp proc_name n in + Procname.Map.iter (pp_entry fmt) dependency_map + +let describe_timestamp summary = + ("Timestamp", Printf.sprintf "%d" summary.timestamp) + +let describe_status summary = + ("Status", if summary.status == ACTIVE then "ACTIVE" else "INACTIVE") + +let describe_phase summary = + ("Phase", if summary.phase == FOOTPRINT then "FOOTRPRINT" else "RE_EXECUTION") + +(** Return the signature of a procedure declaration as a string *) +let get_signature summary = + let s = ref "" in + list_iter (fun (p, typ) -> + let pp_name f () = F.fprintf f "%s" p in + let pp f () = Sil.pp_type_decl pe_text pp_name Sil.pp_exp f typ in + let decl = pp_to_string pp () in + s := if !s = "" then decl else !s ^ ", " ^ decl) summary.formals; + let pp_procname f () = F.fprintf f "%a" Procname.pp summary.proc_name in + let pp f () = Sil.pp_type_decl pe_text pp_procname Sil.pp_exp f summary.ret_type in + let decl = pp_to_string pp () in + decl ^ "(" ^ !s ^ ")" + +let pp_summary_no_stats_specs fmt summary = + let pp_pair fmt (x, y) = F.fprintf fmt "%s: %s" x y in + F.fprintf fmt "%s@\n" (get_signature summary); + F.fprintf fmt "%a@\n" pp_pair (describe_timestamp summary); + F.fprintf fmt "%a@\n" pp_pair (describe_status summary); + F.fprintf fmt "%a@\n" pp_pair (describe_phase summary); + F.fprintf fmt "Dependency_map: @[%a@]@\n" pp_dependency_map summary.dependency_map + +let pp_stats_html fmt stats = + Errlog.pp_html [] fmt stats.err_log + +let get_specs_from_payload summary = match summary.payload with + | PrePosts specs -> NormSpec.tospecs specs + | TypeState _ -> [] + +(** Print the summary *) +let pp_summary pe whole_seconds fmt summary = match pe.pe_kind with + | PP_TEXT -> + pp_summary_no_stats_specs fmt summary; + F.fprintf fmt "%a@\n" (pp_stats whole_seconds) summary.stats; + F.fprintf fmt "%a" (pp_specs pe) (get_specs_from_payload summary) + | PP_HTML -> + Io_infer.Html.pp_start_color fmt Black; + F.fprintf fmt "@\n%a" pp_summary_no_stats_specs summary; + Io_infer.Html.pp_end_color fmt (); + pp_stats_html fmt summary.stats; + Io_infer.Html.pp_hline fmt (); + F.fprintf fmt "@\n"; + pp_specs pe fmt (get_specs_from_payload summary); + F.fprintf fmt "@\n" + | PP_LATEX -> + F.fprintf fmt "\\begin{verbatim}@\n"; + pp_summary_no_stats_specs fmt summary; + F.fprintf fmt "%a@\n" (pp_stats whole_seconds) summary.stats; + F.fprintf fmt "\\end{verbatim}@\n"; + F.fprintf fmt "%a@\n" (pp_specs pe) (get_specs_from_payload summary) + +(** Print the spec table *) +let pp_spec_table pe whole_seconds fmt () = + Procname.Hash.iter (fun proc_name (summ, orig) -> F.fprintf fmt "PROC %a@\n%a@\n" Procname.pp proc_name (pp_summary pe whole_seconds) summ) spec_tbl + +let empty_stats err_log calls cyclomatic in_out_calls_opt = + { stats_time = 0.0; + stats_timeout = false; + stats_calls = + (match in_out_calls_opt with + | Some in_out_calls -> in_out_calls + | None -> { Cg.in_calls = 0; Cg.out_calls = 0 }); + symops = 0; + err_log = err_log; + nodes_visited_fp = IntSet.empty; + nodes_visited_re = IntSet.empty; + call_stats = CallStats.init calls; + cyclomatic = cyclomatic; + } + +let rec post_equal pl1 pl2 = match pl1, pl2 with + | [],[] -> true + | [], _:: _ -> false + | _:: _,[] -> false + | p1:: pl1', p2:: pl2' -> + if Prop.prop_equal p1 p2 then post_equal pl1' pl2' + else false + +let payload_compact sh payload = match payload with + | PrePosts specs -> PrePosts (list_map (NormSpec.compact sh) specs) + | TypeState _ -> payload + +(** Return a compact representation of the summary *) +let summary_compact sh summary = + { summary with payload = payload_compact sh summary.payload } + +let set_summary_origin proc_name summary origin = + Procname.Hash.replace spec_tbl proc_name (summary, origin) + +let add_summary_origin (proc_name : Procname.t) (summary: summary) (origin: origin) : unit = + L.out "Adding summary for %a@\n@[ %a@]@." Procname.pp proc_name (pp_summary pe_text false) summary; + set_summary_origin proc_name summary origin + +(** Add the summary to the table for the given function *) +let add_summary (proc_name : Procname.t) (summary: summary) : unit = + add_summary_origin proc_name summary Res_dir + +let specs_filename pname = + let pname_file = Procname.to_filename pname in + pname_file ^ ".specs" + +(** path to the .specs file for the given procedure in the current results directory *) +let res_dir_specs_filename pname = + DB.Results_dir.path_to_filename DB.Results_dir.Abs_root [Config.specs_dir_name; specs_filename pname] + +let summary_exists pname = + Sys.file_exists (DB.filename_to_string (res_dir_specs_filename pname)) + +(** paths to the .specs file for the given procedure in the current spec libraries *) +let specs_library_filenames pname = + list_map + (fun specs_dir -> DB.filename_from_string (Filename.concat specs_dir (specs_filename pname))) + !Config.specs_library + +(** paths to the .specs file for the given procedure in the models folder *) +let specs_models_filename pname = + DB.filename_from_string (Filename.concat Config.models_dir (specs_filename pname)) + +let summary_exists_in_models pname = + Sys.file_exists (DB.filename_to_string (specs_models_filename pname)) + +let summary_serializer : summary Serialization.serializer = Serialization.create_serializer Serialization.summary_key + +(** Save summary for the procedure into the spec database *) +let store_summary pname (summ: summary) = + let process_payload = function + | PrePosts specs -> PrePosts (list_map NormSpec.erase_join_info_pre specs) + | TypeState typestate_opt -> TypeState typestate_opt in + let summ' = { summ with payload = process_payload summ.payload } in + let summ'' = if !Config.save_compact_summaries + then summary_compact (Sil.create_sharing_env ()) summ' + else summ' in + Serialization.to_file summary_serializer (res_dir_specs_filename pname) summ'' + +(** Load procedure summary from the given file *) +let load_summary specs_file = + Serialization.from_file summary_serializer specs_file + +(** Load procedure summary from the given zip file *) +(* TODO: instead of always going through the same list for zip files for every proc_name, *) +(* create beforehand a map from specs filenames to zip filenames, so that looking up the specs for a given procedure is fast *) +let load_summary_from_zip zip_specs_path zip_channel = + let found_summary = + try + let entry = Zip.find_entry zip_channel zip_specs_path in + begin + match Serialization.from_string summary_serializer (Zip.read_entry zip_channel entry) with + | Some summ -> Some summ + | None -> + L.err "Could not load specs datastructure from %s@." zip_specs_path; + None + end + with Not_found -> None in + found_summary + +(** Load procedure summary for the given procedure name and update spec table *) +let load_summary_to_spec_table proc_name = + let add summ origin = + add_summary_origin proc_name summ origin; + true in + let load_summary_models models_dir = + match load_summary models_dir with + | None -> false + | Some summ -> add summ Models in + let rec load_summary_libs = function (* try to load the summary from a list of libs *) + | [] -> false + | spec_path :: spec_paths -> + (match load_summary spec_path with + | None -> load_summary_libs spec_paths + | Some summ -> + add summ Spec_lib) in + let rec load_summary_ziplibs zip_libraries = (* try to load the summary from a list of zip libraries *) + let zip_specs_filename = specs_filename proc_name in + let zip_specs_path = + let root = Filename.concat Config.default_in_zip_results_dir Config.specs_dir_name in + Filename.concat root zip_specs_filename in + match zip_libraries with + | [] -> false + | zip_library:: zip_libraries -> + begin + match load_summary_from_zip zip_specs_path (Config.zip_channel zip_library) with + | None -> load_summary_ziplibs zip_libraries + | Some summ -> + let origin = if zip_library.Config.models then Models else Spec_lib in + add summ origin + end in + let default_spec_dir = res_dir_specs_filename proc_name in + match load_summary default_spec_dir with + | None -> + (* search on models, libzips, and libs *) + if load_summary_models (specs_models_filename proc_name) then true + else if load_summary_ziplibs !Config.zip_libraries then true + else load_summary_libs (specs_library_filenames proc_name) + + | Some summ -> + add summ Res_dir + +let rec get_summary_origin proc_name = + try + Some (Procname.Hash.find spec_tbl proc_name) + with Not_found -> + if load_summary_to_spec_table proc_name then + get_summary_origin proc_name + else None + +let get_summary proc_name = + match get_summary_origin proc_name with + | Some (summary, _) -> Some summary + | None -> None + +let get_summary_unsafe proc_name = + match get_summary proc_name with + | None -> + raise (Failure ("Specs.get_summary_unsafe: " ^ (Procname.to_string proc_name) ^ "Not_found")) + | Some summary -> summary + +(** Check if the procedure is from a library: +It's not defined in the current proc desc, and there is no spec file for it. *) +let proc_is_library proc_name proc_desc = + let defined = Cfg.Procdesc.is_defined proc_desc in + if not defined then + match get_summary proc_name with + | None -> true + | Some _ -> false + else false + +(** Get the attributes of a procedure, looking first in the procdesc and then in the .specs file. *) +let proc_get_attributes proc_name proc_desc : Sil.proc_attributes = + let from_proc_desc = Cfg.Procdesc.get_attributes proc_desc in + let defined = Cfg.Procdesc.is_defined proc_desc in + if not defined then + match get_summary proc_name with + | None -> from_proc_desc + | Some summary -> + summary.attributes (* get attributes from .specs file *) + else from_proc_desc + +let proc_get_method_annotation proc_name proc_desc = + (proc_get_attributes proc_name proc_desc).Sil.method_annotation + +let get_origin proc_name = + match get_summary_origin proc_name with + | Some (_, origin) -> origin + | None -> Res_dir + +let summary_exists proc_name = + match get_summary proc_name with + | Some _ -> true + | None -> false + +let get_status summary = + summary.status + +let is_active proc_name = + get_status (get_summary_unsafe proc_name) = ACTIVE + +let is_inactive proc_name = + get_status (get_summary_unsafe proc_name) = INACTIVE + +let get_timestamp summary = + summary.timestamp + +let get_proc_name summary = + summary.proc_name + +let get_attributes summary = + summary.attributes + +(** Get the flag with the given key for the procedure, if any *) +(* TODO get_flag should get a summary as parameter *) +let get_flag proc_name key = + match get_summary proc_name with + | None -> None + | Some summary -> + let proc_flags = summary.proc_flags in + try + Some (Hashtbl.find proc_flags key) + with Not_found -> None + +(** Get the iterations associated to the procedure if any, or the default timeout from the +command line *) +let get_iterations proc_name = + match get_summary proc_name with + | None -> + raise (Failure ("Specs.get_iterations: " ^ (Procname.to_string proc_name) ^ "Not_found")) + | Some summary -> + let proc_flags = summary.proc_flags in + try + let time_str = Hashtbl.find proc_flags proc_flag_iterations in + Pervasives.int_of_string time_str + with exn when exn_not_timeout exn -> !iterations_cmdline + +(** Return the specs and parameters for the proc in the spec table *) +let get_specs_formals proc_name = + match get_summary proc_name with + | None -> + raise (Failure ("Specs.get_specs_formals: " ^ (Procname.to_string proc_name) ^ "Not_found")) + | Some summary -> + let specs = get_specs_from_payload summary in + let formals = summary.formals in + (specs, formals) + +(** Return the specs for the proc in the spec table *) +let get_specs proc_name = + fst (get_specs_formals proc_name) + +(** Return the current phase for the proc *) +let get_phase proc_name = + match get_summary_origin proc_name with + | None -> raise (Failure ("Specs.get_phase: " ^ (Procname.to_string proc_name) ^ " Not_found")) + | Some (summary, origin) -> summary.phase + +(** Set the current status for the proc *) +let set_status proc_name status = + match get_summary_origin proc_name with + | None -> raise (Failure ("Specs.set_status: " ^ (Procname.to_string proc_name) ^ " Not_found")) + | Some (summary, origin) -> set_summary_origin proc_name { summary with status = status } origin + +(** Create the initial dependency map with the given list of dependencies *) +let mk_initial_dependency_map proc_list : dependency_map_t = + list_fold_left (fun map pname -> Procname.Map.add pname (- 1) map) Procname.Map.empty proc_list + +(** Re-initialize a dependency map *) +let re_initialize_dependency_map dependency_map = + Procname.Map.map (fun dep_proc -> - 1) dependency_map + +(** Update the dependency map of [proc_name] with the current +timestamps of the dependents *) +let update_dependency_map proc_name = + match get_summary_origin proc_name with + | None -> + raise + (Failure ("Specs.update_dependency_map: " ^ (Procname.to_string proc_name) ^ " Not_found")) + | Some (summary, origin) -> + let current_dependency_map = + Procname.Map.mapi + (fun dep_proc old_stamp -> get_timestamp summary) + summary.dependency_map in + set_summary_origin proc_name { summary with dependency_map = current_dependency_map } origin + +(** [init_summary loc (proc_name, ret_type, formals, depend_list, loc, nodes, +proc_flags, initial_err_log, calls, cyclomatic, in_out_calls_opt, proc_attributes)] +initializes the summary for [proc_name] given dependent procs in list [depend_list]. *) +let init_summary + (proc_name, ret_type, formals, depend_list, loc, + nodes, proc_flags, initial_err_log, calls, cyclomatic, in_out_calls_opt, + proc_attributes) = + let dependency_map = mk_initial_dependency_map depend_list in + let summary = + { + dependency_map = dependency_map; + loc = loc; + nodes = nodes; + ret_type = ret_type; + formals = formals; + phase = FOOTPRINT; + proc_name = proc_name; + proc_flags = proc_flags; + sessions = ref 0; + payload = PrePosts []; + stats = empty_stats initial_err_log calls cyclomatic in_out_calls_opt; + status = INACTIVE; + timestamp = 0; + attributes = proc_attributes; + } in + Procname.Hash.replace spec_tbl proc_name (summary, Res_dir) + +let reset_summary call_graph proc_name loc = + let dependents = Cg.get_defined_children call_graph proc_name in + let proc_attributes = { + Sil.access = Sil.Default; + Sil.exceptions = []; + Sil.is_abstract = false; + Sil.is_bridge_method = false; + Sil.is_objc_instance_method = false; + Sil.is_synthetic_method = false; + Sil.language = !Sil.curr_language; + Sil.method_annotation = Sil.method_annotation_empty; + } in + init_summary ( + proc_name, + Sil.Tvoid, + [], + Procname.Set.elements + dependents, + loc, + [], + proc_flags_empty (), + Errlog.empty (), + [], + 0, + Some (Cg.get_calls call_graph proc_name), + proc_attributes + ) + +(* =============== END of support for spec tables =============== *) diff --git a/infer/src/backend/specs.mli b/infer/src/backend/specs.mli new file mode 100644 index 000000000..8566efcd3 --- /dev/null +++ b/infer/src/backend/specs.mli @@ -0,0 +1,278 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Specifications and spec table *) + +open Utils + +(** {2 Spec Tables} *) + +(** Module for joined props: the result of joining together propositions repeatedly *) +module Jprop : sig +(** Remember when a prop is obtained as the join of two other props; the first parameter is an id *) + type 'a t = + | Prop of int * 'a Prop.t + | Joined of int * 'a Prop.t * 'a t * 'a t + + (** Comparison for joined_prop *) + val compare : 'a t -> 'a t -> int + + (** Dump the toplevel prop *) + val d_shallow : Prop.normal t -> unit + + (** dump a joined prop list, the boolean indicates whether to print toplevel props only *) + val d_list : bool -> Prop.normal t list -> unit + + (** Return true if the two join_prop's are equal *) + val equal : 'a t -> 'a t -> bool + + (** Add fav to a jprop *) + val fav_add : Sil.fav -> 'a t -> unit + + (** [jprop_filter filter joinedprops] applies [filter] to the elements + of [joindeprops] and applies it to the subparts if the result is + [None]. Returns the most absract results which pass [filter]. *) + val filter : ('a t -> 'b option) -> 'a t list -> 'b list + + (** apply a substitution to a jprop *) + val jprop_sub : Sil.subst -> Prop.normal t -> Prop.exposed t + + (** map the function to each prop in the jprop, pointwise *) + val map : ('a Prop.t -> 'b Prop.t) -> 'a t -> 'b t + + (** Print a list of joined props, the boolean indicates whether to print subcomponents of joined props *) + val pp_list : printenv -> bool -> Format.formatter -> Prop.normal t list -> unit + + (** Print the toplevel prop *) + val pp_short : printenv -> Format.formatter -> Prop.normal t -> unit + + (** Extract the number associated to the toplevel jprop of a prop *) + val to_number : 'a t -> int + + (** Extract the toplevel jprop of a prop *) + val to_prop : 'a t -> 'a Prop.t +end + +(** set of visited nodes: node id and list of lines of all the instructions *) +module Visitedset : Set.S with type elt = int * int list + +(** convert a Visitedset to a string *) +val visited_str : Visitedset.t -> string + +(** A spec consists of: +pre: a joined prop +posts: a list of props with path +visited: a list of pairs (node_id, line) for the visited nodes *) +type 'a spec = { pre: 'a Jprop.t; posts: ('a Prop.t * Paths.Path.t) list; visited : Visitedset.t } + +module NormSpec : sig (* encapsulate type for normalized specs *) + type t +end + +module CallStats : (** module for tracing stats of function calls *) +sig + type t + + type call_result = (** kind of result of a procedure call *) + | CR_success (** successful call *) + | CR_not_met (** precondition not met *) + | CR_not_found (** the callee has no specs *) + | CR_skip (** the callee was skipped *) + + (** trace of an occurrence of function call *) + type trace = (call_result * bool) list + + (** iterate over results of procedure calls *) + val iter : (Procname.t * Sil.location -> trace -> unit) -> t -> unit + + (** trace a procedure call *) + val trace : t -> Procname.t -> Sil.location -> call_result -> bool -> unit + + (** pretty print a call trace *) + val pp_trace : Format.formatter -> trace -> unit +end + +(** Execution statistics *) +type stats = + { stats_time: float; (** Analysis time for the procedure *) + stats_timeout: bool; (** Flag to indicate whether a timeout occurred *) + stats_calls: Cg.in_out_calls; (** num of procs calling, and called *) + symops: int; (** Number of SymOp's throughout the whole analysis of the function *) + err_log: Errlog.t; (** Error log for the procedure *) + mutable nodes_visited_fp : IntSet.t; (** Nodes visited during the footprint phase *) + mutable nodes_visited_re : IntSet.t; (** Nodes visited during the re-execution phase *) + call_stats : CallStats.t; + cyclomatic : int; + } + +type status = ACTIVE | INACTIVE + +type phase = FOOTPRINT | RE_EXECUTION + +type dependency_map_t = int Procname.Map.t + +(** Payload: results of some analysis *) +type payload = + | PrePosts of NormSpec.t list (** list of specs *) + | TypeState of unit TypeState.t option (** final typestate *) + +(** Procedure summary *) +type summary = + { dependency_map: dependency_map_t; (** maps children procs to timestamp as last seen at the start of an analysys phase for this proc *) + loc: Sil.location; (** original file and line number *) + nodes: int list; (** ids of cfg nodes of the procedure *) + ret_type : Sil.typ; (** type of the return parameter *) + formals : (string * Sil.typ) list; (** name and type of the formal parameters of the procedure *) + phase: phase; (** in FOOTPRINT phase or in RE_EXECUTION PHASE *) + proc_name : Procname.t; (** name of the procedure *) + proc_flags : proc_flags; (** flags of the procedure *) + payload: payload; (** payload containing the result of some analysis *) + sessions: int ref; (** Session number: how many nodes went trough symbolic execution *) + stats: stats; (** statistics: execution time and list of errors *) + status: status; (** ACTIVE when the proc is being analyzed *) + timestamp: int; (** Timestamp of the specs, >= 0, increased every time the specs change *) + attributes : Sil.proc_attributes; (** Attributes of the procedure *) + } + +(** origin of a summary: current results dir, a spec library, or models *) +type origin = + | Res_dir + | Spec_lib + | Models + +(** Add the summary to the table for the given function *) +val add_summary : Procname.t -> summary -> unit + +(** Check if a summary for a given procedure exists in the results directory *) +val summary_exists : Procname.t -> bool + +(** Check if a summary for a given procedure exists in the models directory *) +val summary_exists_in_models : Procname.t -> bool + +(** remove all the elements from the spec table *) +val clear_spec_tbl : unit -> unit + +(** Dump a spec *) +val d_spec : 'a spec -> unit + +(** Get the procedure name *) +val get_proc_name : summary -> Procname.t + +(** Get the attributes of the procedure. *) +val get_attributes : summary -> Sil.proc_attributes + +(** Get the flag with the given key for the procedure, if any *) +val get_flag : Procname.t -> string -> string option + +(** Get the iterations associated to the procedure if any, or the default timeout from the command line *) +val get_iterations : Procname.t -> int + +(** Return the current phase for the proc *) +val get_phase : Procname.t -> phase + +(** Return the origin of the spec file *) +val get_origin: Procname.t -> origin + +(** Return the signature of a procedure declaration as a string *) +val get_signature : summary -> string + +(** Return the specs for the proc in the spec table *) +val get_specs : Procname.t -> Prop.normal spec list + +(** Return the specs and formal parameters for the proc in the spec table *) +val get_specs_formals : Procname.t -> Prop.normal spec list * (string * Sil.typ) list + +(** Get the specs from the payload of the summary. *) +val get_specs_from_payload : summary -> Prop.normal spec list + +(** Return the summary option for the procedure name *) +val get_summary : Procname.t -> summary option + +(** @deprecated Return the summary for the procedure name. Raises an exception when not found. *) +val get_summary_unsafe : Procname.t -> summary + +(** Return the current timestamp for the summary *) +val get_timestamp : summary -> int + +(** Return the status (active v.s. inactive) of a procedure summary *) +val get_status : summary -> status + +(** Check if the procedure is active *) +val is_active : Procname.t -> bool + +(** Check if the procedure is active *) +val is_inactive : Procname.t -> bool + +(** Initialize the summary for [proc_name] given dependent procs in list [depend_list]. +Do nothing if a summary exists already. *) +val init_summary : +(Procname.t * (** proc_name *) +Sil.typ * (** ret type *) +(string * Sil.typ) list * (** formals *) +Procname.t list * (** depend list *) +Sil.location * (** loc *) +int list * (** nodes *) +proc_flags * (** procedure flags *) +Errlog.t * (** initial error log *) +(Procname.t * Sil.location) list * (** calls *) +int * (** cyclomatic *) +(Cg.in_out_calls option) * (** in and out calls *) +Sil.proc_attributes) (** attributes of the procedure *) +-> unit + +val reset_summary : Cg.t -> Procname.t -> Sil.location -> unit + +(** Load procedure summary from the given file *) +val load_summary : DB.filename -> summary option + +(** Check if a procedure summary exists for the given procedure name *) +val summary_exists : Procname.t -> bool + +(** Cast a list of normalized specs to a list of specs *) +val normalized_specs_to_specs : NormSpec.t list -> Prop.normal spec list + +(** Print the spec *) +val pp_spec : printenv -> (int * int) option -> Format.formatter -> Prop.normal spec -> unit + +(** Print the spec table, the bool indicates whether to print whole seconds only *) +val pp_spec_table : printenv -> bool -> Format.formatter -> unit -> unit + +(** Print the specs *) +val pp_specs : printenv -> Format.formatter -> Prop.normal spec list -> unit + +(** Print the summary, the bool indicates whether to print whole seconds only *) +val pp_summary : printenv -> bool -> Format.formatter -> summary -> unit + +(** Get the attributes of a procedure, looking first in the procdesc and then in the .specs file. *) +val proc_get_attributes : Procname.t -> Cfg.Procdesc.t -> Sil.proc_attributes + +val proc_get_method_annotation : Procname.t -> Cfg.Procdesc.t -> Sil.method_annotation + +(** Check if the procedure is from a library: +It's not defined in the current proc desc, and there is no spec file for it. *) +val proc_is_library : Procname.t -> Cfg.Procdesc.t -> bool + +(** Re-initialize a dependency map *) +val re_initialize_dependency_map : dependency_map_t -> dependency_map_t + +(** Set the current status for the proc *) +val set_status : Procname.t -> status -> unit + +(** Convert spec into normal form w.r.t. variable renaming *) +val spec_normalize : Prop.normal spec -> NormSpec.t + +(** path to the .specs file for the given procedure in the current results dir *) +val res_dir_specs_filename : Procname.t -> DB.filename + +(** Save summary for the procedure into the spec database *) +val store_summary : Procname.t -> summary -> unit + +(** Return a compact representation of the summary *) +val summary_compact : Sil.sharing_env -> summary -> summary + +(** Update the dependency map of [proc_name] with the current +timestamps of the dependents *) +val update_dependency_map : Procname.t -> unit diff --git a/infer/src/backend/state.ml b/infer/src/backend/state.ml new file mode 100644 index 000000000..4f5738411 --- /dev/null +++ b/infer/src/backend/state.ml @@ -0,0 +1,320 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** State of symbolic execution *) + +module L = Logging +module F = Format +open Utils + +(** Diverging states since the last reset for the node *) +let diverging_states_node = ref Paths.PathSet.empty + +(** Diverging states since the last reset for the procedure *) +let diverging_states_proc = ref Paths.PathSet.empty + +(** Node target of a Sil.Goto_node instruction *) +let goto_node = ref None + +(** Last instruction seen *) +let last_instr = ref None + +(** Last node seen *) +let last_node = ref (Cfg.Node.dummy ()) + +(** Last node seen *) +let last_path = ref None + +(** Last prop,tenv,pdesc seen *) +let last_prop_tenv_pdesc = ref None + +(** Last session seen *) +let last_session = ref 0 + +(** failure statistics for symbolic execution on a given node *) +type failure_stats = { + mutable instr_fail: int; (* number of instruction failures since the current node started *) + mutable instr_ok: int; (* number of instruction successes since the current node started *) + mutable node_fail: int; (* number of node failures (i.e. at least one instruction failure) *) + mutable node_ok: int; (* number of node successes (i.e. no instruction failures) *) + mutable first_failure : + (Sil.location * (int * int) * int * Errlog.loc_trace * + (Prop.normal Prop.t) option * exn) option (* exception at the first failure *) +} + +module NodeHash = Cfg.NodeHash + +(** Map visited nodes to failure statistics *) +let failure_map : failure_stats NodeHash.t = NodeHash.create 1 + +let get_failure_stats node = + try NodeHash.find failure_map node + with Not_found -> + let fs = { instr_fail = 0; instr_ok = 0; node_fail = 0; node_ok = 0; first_failure = None } in + NodeHash.add failure_map node fs; + fs + +let add_diverging_states pset = + diverging_states_proc := Paths.PathSet.union pset !diverging_states_proc; + diverging_states_node := Paths.PathSet.union pset !diverging_states_node + +let get_diverging_states_node () = + !diverging_states_node + +let get_diverging_states_proc () = + !diverging_states_proc + +let set_goto_node node_id = + goto_node := Some node_id + +let get_goto_node () = + !goto_node + +let get_instr () = + !last_instr + +let get_loc () = match !last_instr with + | Some instr -> Sil.instr_get_loc instr + | None -> Cfg.Node.get_loc !last_node + +let get_node () = + !last_node + +(** simple key for a node: just look at the instructions *) +let node_simple_key node = + let key = ref [] in + let add_key k = key := k :: !key in + let do_instr instr = + if Sil.instr_is_auxiliary instr then () + else + match instr with + | Sil.Letderef _ -> add_key 1 + | Sil.Set _ -> add_key 2 + | Sil.Prune _ -> add_key 3 + | Sil.Call _ -> add_key 4 + | Sil.Nullify _ -> add_key 5 + | Sil.Abstract _ -> add_key 6 + | Sil.Remove_temps _ -> add_key 7 + | Sil.Stackop _ -> add_key 8 + | Sil.Declare_locals _ -> add_key 9 + | Sil.Goto_node _ -> add_key 10 in + list_iter do_instr (Cfg.Node.get_instrs node); + Hashtbl.hash !key + +(** key for a node: look at the current node, successors and predecessors *) +let node_key node = + let succs = Cfg.Node.get_succs node in + let preds = Cfg.Node.get_preds node in + let v = (node_simple_key node, list_map node_simple_key succs, list_map node_simple_key preds) in + Hashtbl.hash v + +(** normalize the list of instructions by renaming let-bound ids *) +let instrs_normalize instrs = + let bound_ids = + let do_instr ids = function + | Sil.Letderef (id, _, _, _) -> id :: ids + | _ -> ids in + list_fold_left do_instr [] instrs in + let subst = + let count = ref min_int in + let gensym id = + incr count; + Ident.set_stamp id !count in + Sil.sub_of_list (list_map (fun id -> (id, Sil.Var (gensym id))) bound_ids) in + list_map (Sil.instr_sub subst) instrs + +(** Create a function to find duplicate nodes. +A node is a duplicate of another one if they have the same kind and location +and normalized (w.r.t. renaming of let - bound ids) list of instructions. *) +let mk_find_duplicate_nodes proc_desc : (Cfg.Node.t -> Cfg.NodeSet.t) = + let module M = (* map from (loc,kind) *) + Map.Make(struct + type t = Sil.location * Cfg.Node.nodekind + let compare (loc1, k1) (loc2, k2) = + let n = Sil.loc_compare loc1 loc2 in + if n <> 0 then n else Cfg.Node.kind_compare k1 k2 + end) in + + let module S = (* set of nodes with normalized insructions *) + Set.Make(struct + type t = Cfg.Node.t * Sil.instr list + let compare (n1, instrs1) (n2, instrs2) = + Cfg.Node.compare n1 n2 + end) in + + let get_key node = (* map key *) + let loc = Cfg.Node.get_loc node in + let kind = Cfg.Node.get_kind node in + (loc, kind) in + + let map = + let m = ref M.empty in (* map from (loc, kind) to (instructions, node) set *) + + let module E = struct + (** Threshold: do not build the map if too many nodes are duplicates. *) + let threshold = 100 + exception Threshold + end in + + let do_node node = + let normalized_instrs = instrs_normalize (Cfg.Node.get_instrs node) in + let key = get_key node in + let s = try M.find key !m with Not_found -> S.empty in + if S.cardinal s > E.threshold then raise E.Threshold; + let s' = S.add (node, normalized_instrs) s in + m := M.add key s' !m in + + let nodes = Cfg.Procdesc.get_nodes proc_desc in + try + list_iter do_node nodes; + !m + with E.Threshold -> + M.empty in + + let find_duplicate_nodes node = + try + let s = M.find (get_key node) map in + let elements = S.elements s in + let (_, node_normalized_instrs), others = + let filter (node', _) = Cfg.Node.equal node node' in + match list_partition filter elements with + | [this], others -> this, others + | _ -> raise Not_found in + let duplicates = + let equal_normalized_instrs (_, normalized_instrs') = + list_compare Sil.instr_compare node_normalized_instrs normalized_instrs' = 0 in + list_filter equal_normalized_instrs elements in + list_fold_left + (fun nset (node', _) -> Cfg.NodeSet.add node' nset) + Cfg.NodeSet.empty duplicates + with Not_found -> Cfg.NodeSet.singleton node in + + find_duplicate_nodes + +let get_node_id () = + Cfg.Node.get_id !last_node + +let get_node_id_key () = + (Cfg.Node.get_id !last_node, node_key !last_node) + +let get_inst_update pos = + let loc = get_loc () in + Sil.inst_update loc pos + +let get_path () = match !last_path with + | None -> Paths.Path.start !last_node, None + | Some (path, pos_opt) -> path, pos_opt + +let get_loc_trace () : Errlog.loc_trace = + let path, pos_opt = get_path () in + Paths.Path.create_loc_trace path pos_opt + +let get_prop_tenv_pdesc () = + !last_prop_tenv_pdesc + +(** extract the footprint of the prop, and turn it into a normalized precondition using spec variables *) +let extract_pre p tenv pdesc abstract_fun = + let sub = + let fav = Prop.prop_fav p in + let idlist = Sil.fav_to_list fav in + let count = ref 0 in + Sil.sub_of_list (list_map (fun id -> incr count; (id, Sil.Var (Ident.create_normal Ident.name_spec !count))) idlist) in + let _, p' = Cfg.remove_locals_formals pdesc p in + let pre, _ = Prop.extract_spec p' in + let pre' = try abstract_fun tenv pre with exn when exn_not_timeout exn -> pre in + Prop.normalize (Prop.prop_sub sub pre') + +(** return the normalized precondition extracted form the last prop seen, if any +the abstraction function is a parameter to get around module dependencies *) +let get_normalized_pre (abstract_fun : Sil.tenv -> Prop.normal Prop.t -> Prop.normal Prop.t) : Prop.normal Prop.t option = + match get_prop_tenv_pdesc () with + | None -> None + | Some (prop, tenv, pdesc) -> + Some (extract_pre prop tenv pdesc abstract_fun) + +let get_session () = + !last_session + +let get_path_pos () = + let pname = match get_prop_tenv_pdesc () with + | Some (_, _, pdesc) -> Cfg.Procdesc.get_proc_name pdesc + | None -> Procname.from_string "unknown_procedure" in + let nid = get_node_id () in + (pname, nid) + +let mark_execution_start node = + let fs = get_failure_stats node in + fs.instr_ok <- 0; + fs.instr_fail <- 0 + +let mark_execution_end node = + let fs = get_failure_stats node in + let success = fs.instr_fail = 0 in + fs.instr_ok <- 0; + fs.instr_fail <- 0; + if success then fs.node_ok <- fs.node_ok + 1 + else fs.node_fail <- fs.node_fail + 1 + +let mark_instr_ok () = + let fs = get_failure_stats (get_node ()) in + fs.instr_ok <- fs.instr_ok + 1 + +let mark_instr_fail pre_opt exn = + let loc = get_loc () in + let key = get_node_id_key () in + let session = get_session () in + let loc_trace = get_loc_trace () in + let fs = get_failure_stats (get_node ()) in + if fs.first_failure = None then fs.first_failure <- Some (loc, key, session, loc_trace, pre_opt, exn); + fs.instr_fail <- fs.instr_fail + 1 + +type log_issue = + Procname.t -> + ?loc: Sil.location option -> + ?node_id: (int * int) option -> + ?session: int option -> + ?ltr: Errlog.loc_trace option -> + ?pre: Prop.normal Prop.t option -> + exn -> + unit + +let process_execution_failures (log_issue : log_issue) pname = + let do_failure node fs = + (* L.err "Node:%a node_ok:%d node_fail:%d@." Cfg.Node.pp node fs.node_ok fs.node_fail; *) + match fs.node_ok, fs.first_failure with + | 0, Some (loc, key, session, loc_trace, pre_opt, exn) -> + let ex_name, desc, mloco, _, _, _, _ = Exceptions.recognize_exception exn in + let desc' = Localise.verbatim_desc ("exception: " ^ Localise.to_string ex_name) in + let exn' = Exceptions.Analysis_stops (desc', mloco) in + log_issue + pname ~loc: (Some loc) ~node_id: (Some key) ~ltr: (Some loc_trace) ~pre: pre_opt exn' + | _ -> () in + NodeHash.iter do_failure failure_map + +let set_instr (instr: Sil.instr) = + last_instr := Some instr + +let reset_diverging_states_goto_node () = + diverging_states_node := Paths.PathSet.empty; + goto_node := None + +let reset () = + diverging_states_proc := Paths.PathSet.empty; + reset_diverging_states_goto_node (); + NodeHash.clear failure_map + +let set_path path pos_opt = + last_path := Some (path, pos_opt) + +let set_prop_tenv_pdesc prop tenv pdesc = + last_prop_tenv_pdesc := Some (prop, tenv, pdesc) + +let set_node (node: Cfg.node) = + last_instr := None; + last_node := node + +let set_session (session: int) = + last_session := session diff --git a/infer/src/backend/state.mli b/infer/src/backend/state.mli new file mode 100644 index 000000000..36efdb5c4 --- /dev/null +++ b/infer/src/backend/state.mli @@ -0,0 +1,112 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** State of symbolic execution *) + +open Utils + +(** Add diverging states *) +val add_diverging_states : Paths.PathSet.t -> unit + +(** Get the diverging states for the node *) +val get_diverging_states_node : unit -> Paths.PathSet.t + +(** Get the diverging states for the procedure *) +val get_diverging_states_proc : unit -> Paths.PathSet.t + +(** Get the node target of a Sil.Goto_node instruction, if any *) +val get_goto_node : unit -> int option + +(** Get update instrumentation for the current loc *) +val get_inst_update : Sil.path_pos -> Sil.inst + +(** Get last instruction seen in symbolic execution *) +val get_instr : unit -> Sil.instr option + +(** Get last location seen in symbolic execution *) +val get_loc : unit -> Sil.location + +(** Get the location trace of the last path seen in symbolic execution *) +val get_loc_trace : unit -> Errlog.loc_trace + +(** Get last node seen in symbolic execution *) +val get_node : unit -> Cfg.Node.t + +(** Get id of last node seen in symbolic execution *) +val get_node_id : unit -> int + +(** Get id and key of last node seen in symbolic execution *) +val get_node_id_key : unit -> int * int + +(** return the normalized precondition extracted form the last prop seen, if any +the abstraction function is a parameter to get around module dependencies *) +val get_normalized_pre : (Sil.tenv -> Prop.normal Prop.t -> Prop.normal Prop.t) -> Prop.normal Prop.t option + +(** Get last path seen in symbolic execution *) +val get_path : unit -> Paths.Path.t * (Sil.path_pos option) + +(** Get the last path position seen in symbolic execution *) +val get_path_pos : unit -> Sil.path_pos + +(** Get last last prop,tenv,pdesc seen in symbolic execution *) +val get_prop_tenv_pdesc : unit -> (Prop.normal Prop.t * Sil.tenv * Cfg.Procdesc.t) option + +(** Get last session seen in symbolic execution *) +val get_session : unit -> int + +(** Mark the end of symbolic execution of a node *) +val mark_execution_end : Cfg.Node.t -> unit + +(** Mark the start of symbolic execution of a node *) +val mark_execution_start : Cfg.Node.t -> unit + +(** Mark that the execution of the current instruction failed *) +val mark_instr_fail : (Prop.normal Prop.t) option -> exn -> unit + +(** Mark that the execution of the current instruction was OK *) +val mark_instr_ok : unit -> unit + +(** Create a function to find duplicate nodes. +A node is a duplicate of another one if they have the same kind and location +and normalized (w.r.t. renaming of let - bound ids) list of instructions. *) +val mk_find_duplicate_nodes: Cfg.Procdesc.t -> (Cfg.Node.t -> Cfg.NodeSet.t) + +type log_issue = + Procname.t -> + ?loc: Sil.location option -> + ?node_id: (int * int) option -> + ?session: int option -> + ?ltr: Errlog.loc_trace option -> + ?pre: Prop.normal Prop.t option -> + exn -> + unit + +(** Process the failures during symbolic execution of a procedure *) +val process_execution_failures : log_issue -> Procname.t -> unit + +(** Reset all the global data in the module: diverging states and failure stats *) +val reset : unit -> unit + +(** Reset the diverging states and goto information for the node *) +val reset_diverging_states_goto_node : unit -> unit + +(** Set the node target of a Sil.Goto_node instruction *) +val set_goto_node : int -> unit + +(** Set last instruction seen in symbolic execution *) +val set_instr : Sil.instr -> unit + +(** Set last node seen in symbolic execution *) +val set_node : Cfg.node -> unit + +(** Get last path seen in symbolic execution *) +val set_path : Paths.Path.t -> Sil.path_pos option -> unit + +(** Set last prop,tenv,pdesc seen in symbolic execution *) +val set_prop_tenv_pdesc : Prop.normal Prop.t -> Sil.tenv -> Cfg.Procdesc.t -> unit + +(** Set last session seen in symbolic execution *) +val set_session : int -> unit diff --git a/infer/src/backend/symExec.ml b/infer/src/backend/symExec.ml new file mode 100644 index 000000000..e78a27d8c --- /dev/null +++ b/infer/src/backend/symExec.ml @@ -0,0 +1,2249 @@ +(* +* Copyright (c) 2009 - 2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Symbolic Execution *) + +module L = Logging +module F = Format +open Utils + +let rec idlist_assoc id = function + | [] -> raise Not_found + | (i, x):: l -> if Ident.equal i id then x else idlist_assoc id l + +let rec fldlist_assoc fld = function + | [] -> raise Not_found + | (fld', x, a):: l -> if Sil.fld_equal fld fld' then x else fldlist_assoc fld l + +let rec explist_assoc e = function + | [] -> raise Not_found + | (e', x):: l -> if Sil.exp_equal e e' then x else explist_assoc e l + +let append_list_op list_op1 list_op2 = + match list_op1, list_op2 with + | None, _ -> list_op2 + | _, None -> list_op1 + | Some list1, Some list2 -> Some (list1@list2) + +let reverse_list_op list_op = + match list_op with + | None -> None + | Some list -> Some (list_rev list) + +let rec unroll_type tenv typ off = + match (typ, off) with + | Sil.Tvar _, _ -> + let typ' = Sil.expand_type tenv typ in + unroll_type tenv typ' off + | Sil.Tstruct (ftal, sftal, _, _, _, _, _), Sil.Off_fld (fld, _) -> + begin + try fldlist_assoc fld ftal + with Not_found -> + L.d_strln ".... Invalid Field Access ...."; + L.d_strln ("Fld : " ^ Ident.fieldname_to_string fld); + L.d_str "Type : "; Sil.d_typ_full typ; L.d_ln (); + raise (Exceptions.Bad_footprint (try assert false with Assert_failure x -> x)) + end + | Sil.Tarray (typ', _), Sil.Off_index _ -> + typ' + | _, Sil.Off_index (Sil.Const (Sil.Cint i)) when Sil.Int.iszero i -> + typ + | _ -> + L.d_strln ".... Invalid Field Access ...."; + L.d_str "Fld : "; Sil.d_offset off; L.d_ln (); + L.d_str "Type : "; Sil.d_typ_full typ; L.d_ln (); + assert false + +(* This function has the same name the standard list_split in Utils.*) +(* Maybe it's better to change name as we open Utils. *) +let list_split equal x xys = + let (xy, xys') = list_partition (fun (x', _) -> equal x x') xys in + match xy with + | [] -> (xys', None) + | [(x', y')] -> (xys', Some y') + | _ -> assert false + +(* Given a node, returns a list of pvar of blocks that have been nullified in the block *) +let get_nullified_block node = + let null_blocks = list_flatten(list_map (fun i -> match i with + | Sil.Nullify(pvar, _, true) when Sil.is_block_pvar pvar -> [pvar] + | _ -> []) (Cfg.Node.get_instrs node)) in + null_blocks + +(* Given a proposition and an objc block checks whether by existentially quantifying *) +(* captured variables in the block we obtain a leak *) +let check_block_retain_cycle cfg tenv pname _prop block_nullified = + let mblock = Sil.pvar_get_name block_nullified in + let block_captured = (match Cfg.get_block_pdesc cfg mblock with + | Some pd -> fst (Utils.list_split (Cfg.Procdesc.get_captured pd)) + | None -> []) in + let _prop' = Cfg.remove_seed_captured_vars_block block_captured _prop in + let _prop'' = Prop.prop_rename_fav_with_existentials _prop' in + let _ = Abs.abstract_junk ~original_prop: _prop pname tenv _prop'' in + () + +let mark_id_as_undefined id prop att_undef = + let check_attr_change att_old att_new = () in + Prop.add_or_replace_exp_attribute check_attr_change prop (Sil.Var id) att_undef + +(** Apply function [f] to the expression at position [offlist] in [strexp]. +If not found, expand [strexp] and apply [f] to [None]. +The routine should maintain the invariant that strexp and typ correspond to +each other exactly, without involving any re - interpretation of some type t +as the t array. The [fp_root] parameter indicates whether the kind of the +root expression of the corresponding pointsto predicate is a footprint identifier. +The function can expand a list of higher - order [hpara_psto] predicates, if +the list is stored at [offlist] in [strexp] initially. The expanded list +is returned as a part of the result. All these happen under [p], so that it +is sound to call the prover with [p]. Finally, before running this function, +the tool should run strexp_extend_value in rearrange.ml for the same strexp +and offlist, so that all the necessary extensions of strexp are done before +this function. If the tool follows this protocol, it will never hit the assert +false cases for field and array accesses. *) +let rec apply_offlist + footprint_part pdesc tenv p fp_root nullify_struct + (root_lexp, strexp, typ) offlist (f: Sil.exp option -> Sil.exp) inst lookup_inst = + let pname = Cfg.Procdesc.get_proc_name pdesc in + let pp_error () = + L.d_strln ".... Invalid Field ...."; + L.d_str "strexp : "; Sil.d_sexp strexp; L.d_ln (); + L.d_str "offlist : "; Sil.d_offset_list offlist; L.d_ln (); + L.d_str "type : "; Sil.d_typ_full typ; L.d_ln (); + L.d_str "prop : "; Prop.d_prop p; L.d_ln (); L.d_ln () in + match offlist, strexp with + | [], Sil.Eexp (e, inst_curr) -> + let inst_is_uninitialized = function + | Sil.Ialloc -> !Sil.curr_language <> Sil.Java (* java allocation initializes with default values *) + | Sil.Iinitial -> true + | _ -> false in + let is_hidden_field () = + match State.get_instr () with + | Some (Sil.Letderef (_, Sil.Lfield (_, fieldname, _), _, _)) -> + Ident.fieldname_is_hidden fieldname + | _ -> false in + let inst_new = match inst with + | Sil.Ilookup when inst_is_uninitialized inst_curr && not (is_hidden_field()) -> (* we are in a lookup of an uninitialized value *) + lookup_inst := Some inst_curr; + let alloc_attribute_opt = + if inst_curr = Sil.Iinitial then None + else Prop.get_resource_undef_attribute p root_lexp in + let deref_str = Localise.deref_str_uninitialized alloc_attribute_opt in + let err_desc = Errdesc.explain_memory_access deref_str p (State.get_loc ()) in + let exn = (Exceptions.Uninitialized_value (err_desc, try assert false with Assert_failure x -> x)) in + let pre_opt = State.get_normalized_pre (Abs.abstract_no_symop pname) in + Reporting.log_warning pname ~pre: pre_opt exn; + Sil.update_inst inst_curr inst + | Sil.Ilookup -> (* a lookup does not change an inst unless it is inst_initial *) + lookup_inst := Some inst_curr; + inst_curr + | _ -> Sil.update_inst inst_curr inst in + let e' = f (Some e) in + (e', Sil.Eexp (e', inst_new), typ, None) + | [], Sil.Estruct (fesl, inst') -> + if not nullify_struct then (f None, Sil.Estruct (fesl, inst'), typ, None) + else if fp_root then (pp_error(); assert false) + else + begin + L.d_strln "WARNING: struct assignment treated as nondeterministic assignment"; + (f None, Prop.create_strexp_of_type (Some tenv) Prop.Fld_init typ inst, typ, None) + end + | [], Sil.Earray _ -> + let offlist' = (Sil.Off_index Sil.exp_zero):: offlist in + apply_offlist + footprint_part pdesc tenv p fp_root nullify_struct + (root_lexp, strexp, typ) offlist' f inst lookup_inst + | (Sil.Off_fld (fld, _)):: offlist', Sil.Earray _ -> + let offlist_new = Sil.Off_index(Sil.exp_zero) :: offlist in + apply_offlist + footprint_part pdesc tenv p fp_root nullify_struct + (root_lexp, strexp, typ) offlist_new f inst lookup_inst + | (Sil.Off_fld (fld, fld_typ)):: offlist', Sil.Estruct (fsel, inst') -> + begin + let typ' = Sil.expand_type tenv typ in + let ftal, sftal, csu, nameo, supers, def_mthds, iann = match typ' with Sil.Tstruct (ftal, sftal, csu, nameo, supers, def_mthds, iann) -> ftal, sftal, csu, nameo, supers, def_mthds, iann | _ -> assert false in + let t' = unroll_type tenv typ (Sil.Off_fld (fld, fld_typ)) in + try + let _, se' = list_find (fun fse -> Ident.fieldname_equal fld (fst fse)) fsel in + let res_e', res_se', res_t', res_pred_insts_op' = + apply_offlist + footprint_part pdesc tenv p fp_root nullify_struct + (root_lexp, se', t') offlist' f inst lookup_inst in + let replace_fse fse = if Sil.fld_equal fld (fst fse) then (fld, res_se') else fse in + let res_se = Sil.Estruct (list_map replace_fse fsel, inst') in + let replace_fta (f, t, a) = if Sil.fld_equal fld f then (fld, res_t', a) else (f, t, a) in + let res_t = Sil.Tstruct (list_map replace_fta ftal, sftal, csu, nameo, supers, def_mthds, iann) in + (res_e', res_se, res_t, res_pred_insts_op') + with Not_found -> + pp_error(); + assert false + (* This case should not happen. The rearrangement should + have materialized all the accessed cells. *) + end + | (Sil.Off_fld _):: _, _ -> + pp_error(); + assert false + + | (Sil.Off_index idx):: offlist', Sil.Earray (size, esel, inst1) -> + let nidx = Prop.exp_normalize_prop p idx in + begin + let typ' = Sil.expand_type tenv typ in + let t', size' = match typ' with Sil.Tarray (t', size') -> (t', size') | _ -> assert false in + try + let idx_ese', se' = list_find (fun ese -> Prover.check_equal p nidx (fst ese)) esel in + let res_e', res_se', res_t', res_pred_insts_op' = + apply_offlist + footprint_part pdesc tenv p fp_root nullify_struct + (root_lexp, se', t') offlist' f inst lookup_inst in + let replace_ese ese = if Sil.exp_equal idx_ese' (fst ese) then (idx_ese', res_se') else ese in + let res_se = Sil.Earray(size, list_map replace_ese esel, inst1) in + let res_t = Sil.Tarray(res_t', size') in + (res_e', res_se, res_t, res_pred_insts_op') + with Not_found -> (* return a nondeterministic value if the index is not found after rearrangement *) + L.d_str "apply_offlist: index "; Sil.d_exp idx; L.d_strln " not materialized -- returning nondeterministic value"; + let res_e' = Sil.Var (Ident.create_fresh Ident.kprimed) in + (res_e', strexp, typ, None) + end + | (Sil.Off_index idx):: offlist', _ -> + pp_error(); + raise (Exceptions.Internal_error (Localise.verbatim_desc "Array out of bounds in Symexec")) +(* This case should not happen. The rearrangement should +have materialized all the accessed cells. *) + +(** Given [lexp |-> se: typ], if the location [offlist] exists in [se], +function [ptsto_lookup p (lexp, se, typ) offlist id] returns a tuple. +The first component of the tuple is an expression at position [offlist] in [se]. +The second component is an expansion of the predicate [lexp |-> se: typ], +where the entity at [offlist] in [se] is expanded if the entity is a list of +higher - order parameters [hpara_psto]. If this expansion happens, +the last component of the tuple is a list of pi - sigma pairs obtained +by instantiating the [hpara_psto] list. Otherwise, the last component is None. +All these steps happen under [p]. So, we can call a prover with [p]. +Finally, before running this function, the tool should run strexp_extend_value +in rearrange.ml for the same se and offlist, so that all the necessary +extensions of se are done before this function. *) +let ptsto_lookup footprint_part pdesc tenv p (lexp, se, typ, st) offlist id = + let f = + function Some exp -> exp | None -> Sil.Var id in + let fp_root = + match lexp with Sil.Var id -> Ident.is_footprint id | _ -> false in + let lookup_inst = ref None in + let e', se', typ', pred_insts_op' = + apply_offlist + footprint_part pdesc tenv p fp_root false + (lexp, se, typ) offlist f Sil.inst_lookup lookup_inst in + let lookup_uninitialized = (* true if we have looked up an uninitialized value *) + match !lookup_inst with + | Some (Sil.Iinitial | Sil.Ialloc | Sil.Ilookup) -> true + | _ -> false in + let ptsto' = Prop.mk_ptsto lexp se' (Sil.Sizeof (typ', st)) in + (e', ptsto', pred_insts_op', lookup_uninitialized) + +(** [ptsto_update p (lexp,se,typ) offlist exp] takes +[lexp |-> se: typ], and updates [se] by replacing the +expression at [offlist] with [exp]. Then, it returns +the updated pointsto predicate. If [lexp |-> se: typ] gets +expanded during this update, the generated pi - sigma list from +the expansion gets returned, and otherwise, None is returned. +All these happen under the proposition [p], so it is ok call +prover with [p]. Finally, before running this function, +the tool should run strexp_extend_value in rearrange.ml for the same +se and offlist, so that all the necessary extensions of se are done +before this function. *) +let ptsto_update footprint_part pdesc tenv p (lexp, se, typ, st) offlist exp = + let f _ = exp in + let fp_root = + match lexp with Sil.Var id -> Ident.is_footprint id | _ -> false in + let lookup_inst = ref None in + let _, se', typ', pred_insts_op' = + let pos = State.get_path_pos () in + apply_offlist + footprint_part pdesc tenv p fp_root true (lexp, se, typ) + offlist f (State.get_inst_update pos) lookup_inst in + let ptsto' = Prop.mk_ptsto lexp se' (Sil.Sizeof (typ', st)) in + (ptsto', pred_insts_op') + +let update_iter iter pi sigma = + let iter' = Prop.prop_iter_update_current_by_list iter sigma in + list_fold_left (Prop.prop_iter_add_atom false) iter' pi + +let execute_letderef pdesc tenv id rhs_exp acc_in iter = + let iter_ren = Prop.prop_iter_make_id_primed id iter in + let prop_ren = Prop.prop_iter_to_prop iter_ren in + match Prop.prop_iter_current iter_ren with + | (Sil.Hpointsto(lexp, strexp, Sil.Sizeof (typ, st)), offlist) -> + let contents, new_ptsto, pred_insts_op, lookup_uninitialized = + ptsto_lookup false pdesc tenv prop_ren (lexp, strexp, typ, st) offlist id in + let update acc (pi, sigma) = + let pi' = Sil.Aeq (Sil.Var(id), contents):: pi in + let sigma' = new_ptsto:: sigma in + let iter' = update_iter iter_ren pi' sigma' in + let prop' = Prop.prop_iter_to_prop iter' in + let prop'' = + if lookup_uninitialized + then + let check_attr_change att_old att_new = () in + Prop.add_or_replace_exp_attribute check_attr_change prop' (Sil.Var id) (Sil.Adangling Sil.DAuninit) + else prop' in + prop'' :: acc in + begin + match pred_insts_op with + | None -> update acc_in ([],[]) + | Some pred_insts -> list_rev (list_fold_left update acc_in pred_insts) + end + + | (Sil.Hpointsto _, _) -> + Errdesc.warning_err (State.get_loc ()) "no offset access in execute_letderef -- treating as skip@."; + (Prop.prop_iter_to_prop iter_ren) :: acc_in + (* The implementation of this case means that we + ignore this dereferencing operator. When the analyzer treats + numerical information and arrays more precisely later, we + should change the implementation here. *) + + | _ -> assert false + +let execute_set pdesc tenv rhs_exp acc_in iter = + let (lexp, strexp, typ, st, offlist) = + match Prop.prop_iter_current iter with + | (Sil.Hpointsto(lexp, strexp, Sil.Sizeof (typ, st)), offlist) -> (lexp, strexp, typ, st, offlist) + | _ -> assert false in + let p = Prop.prop_iter_to_prop iter in + let new_ptsto, pred_insts_op = + ptsto_update false pdesc tenv p (lexp, strexp, typ, st) offlist rhs_exp in + let update acc (pi, sigma) = + let sigma' = new_ptsto:: sigma in + let iter' = update_iter iter pi sigma' in + let prop' = Prop.prop_iter_to_prop iter' in + prop' :: acc in + match pred_insts_op with + | None -> update acc_in ([],[]) + | Some pred_insts -> list_fold_left update acc_in pred_insts + +(** Module for builtin functions with their symbolic execution handler *) +module Builtin = struct + type ret_typ = (Prop.normal Prop.t * Paths.Path.t) list + type sym_exe_builtin = + Cfg.cfg -> Cfg.Procdesc.t -> Sil.instr -> Sil.tenv -> Prop.normal Prop.t -> Paths.Path.t -> + Ident.t list -> (Sil.exp * Sil.typ) list -> Procname.t -> Sil.location -> ret_typ + + (* builtin function names for which we do symbolic execution *) + let builtin_functions = Procname.Hash.create 4 + (* builtin plain function names: they match all the function names whose plain name is the given string *) + let builtin_plain_functions = Hashtbl.create 4 + + (* Check if the function is a builtin *) + let is_registered name = + Procname.Hash.mem builtin_functions name + || + Hashtbl.mem builtin_plain_functions (Procname.to_string name) + + (* get the symbolic execution handler associated to the builtin function name *) + let get_sym_exe_builtin name : sym_exe_builtin = + try Procname.Hash.find builtin_functions name + with Not_found -> + try Hashtbl.find builtin_plain_functions (Procname.to_string name) + with Not_found -> assert false + + (* register a builtin function name and symbolic execution handler *) + let register proc_name_str (sym_exe_fun: sym_exe_builtin) = + let proc_name = Procname.from_string proc_name_str in + Procname.Hash.replace builtin_functions proc_name sym_exe_fun; + proc_name + + (* register a builtin plain function name and symbolic execution handler *) + let register_plain proc_name_str (sym_exe_fun: sym_exe_builtin) = + let proc_name = Procname.from_string proc_name_str in + Hashtbl.replace builtin_plain_functions proc_name_str sym_exe_fun; + proc_name + + (* register a builtin [Procname.t] and symbolic execution handler *) + let register_procname proc_name (sym_exe_fun: sym_exe_builtin) = + Procname.Hash.replace builtin_functions proc_name sym_exe_fun + + (* register a builtin plain [Procname.t] and symbolic execution handler *) + let register_plain_procname proc_name (sym_exe_fun: sym_exe_builtin) = + Hashtbl.replace builtin_plain_functions (Procname.to_string proc_name) sym_exe_fun + + (** print the functions registered *) + let pp_registered fmt () = + let builtin_names = ref [] in + Procname.Hash.iter (fun name _ -> builtin_names := name :: !builtin_names) builtin_functions; + builtin_names := list_sort Procname.compare !builtin_names; + let pp pname = Format.fprintf fmt "%a@\n" Procname.pp pname in + Format.fprintf fmt "Registered builtins:@\n @["; + list_iter pp !builtin_names; + Format.fprintf fmt "@]@." +end + +(** print the builtin functions and exit *) +let print_builtins () = + Builtin.pp_registered Format.std_formatter (); + exit 0 + +(** Check if the function is a builtin *) +let function_is_builtin = Builtin.is_registered + +(** Precondition: se should not include hpara_psto +that could mean nonempty heaps. *) +let rec execute_nullify_se = function + | Sil.Eexp _ -> + Sil.Eexp (Sil.exp_zero, Sil.inst_nullify) + | Sil.Estruct (fsel, _) -> + let fsel' = list_map (fun (fld, se) -> (fld, execute_nullify_se se)) fsel in + Sil.Estruct (fsel', Sil.inst_nullify) + | Sil.Earray (size, esel, inst) -> + let esel' = list_map (fun (idx, se) -> (idx, execute_nullify_se se)) esel in + Sil.Earray (size, esel', Sil.inst_nullify) + +(** Do pruning for conditional [if (e1 != e2) ] if [positive] is true +and [(if (e1 == e2)] if [positive] is false *) +let prune_ne tenv positive e1 e2 prop = + let is_inconsistent = + if positive then Prover.check_equal prop e1 e2 + else Prover.check_disequal prop e1 e2 in + if is_inconsistent then Propset.empty else + let new_prop = + if positive then Prop.conjoin_neq ~footprint: (!Config.footprint) e1 e2 prop + else Prop.conjoin_eq ~footprint: (!Config.footprint) e1 e2 prop + in if Prover.check_inconsistency new_prop then Propset.empty + else Propset.singleton new_prop + +let rec prune_polarity tenv positive (condition : Sil.exp) (prop : Prop.normal Prop.t) = match condition with + | Sil.Var _ | Sil.Lvar _ -> + prune_ne tenv positive condition Sil.exp_zero prop + | Sil.Const (Sil.Cint i) when Sil.Int.iszero i -> + if positive then Propset.empty else Propset.singleton prop + | Sil.Const (Sil.Cint _) | Sil.Sizeof _ | Sil.Const (Sil.Cstr _) | Sil.Const (Sil.Cclass _) -> + if positive then Propset.singleton prop else Propset.empty + | Sil.Const _ -> + assert false + | Sil.Cast (_, condition') -> + prune_polarity tenv positive condition' prop + | Sil.UnOp (Sil.LNot, condition', _) -> + prune_polarity tenv (not positive) condition' prop + | Sil.UnOp _ -> + assert false + | Sil.BinOp (Sil.Eq, e, Sil.Const (Sil.Cint i)) + | Sil.BinOp (Sil.Eq, Sil.Const (Sil.Cint i), e) when Sil.Int.iszero i && not (Sil.Int.isnull i) -> + prune_polarity tenv (not positive) e prop + | Sil.BinOp (Sil.Eq, e1, e2) -> + prune_ne tenv (not positive) e1 e2 prop + | Sil.BinOp (Sil.Ne, e, Sil.Const (Sil.Cint i)) + | Sil.BinOp (Sil.Ne, Sil.Const (Sil.Cint i), e) when Sil.Int.iszero i && not (Sil.Int.isnull i) -> + prune_polarity tenv positive e prop + | Sil.BinOp (Sil.Ne, e1, e2) -> + prune_ne tenv positive e1 e2 prop + | Sil.BinOp (Sil.Ge, e2, e1) | Sil.BinOp (Sil.Le, e1, e2) -> + (* e1<=e2 Case. Encode it as (e1<=e2)=1 *) + if Sil.exp_equal e1 e2 then + if positive then Propset.singleton prop else Propset.empty + else + let e2_lt_e1 = Sil.BinOp (Sil.Lt, e2, e1) in (* e2 < e1 *) + let e1_le_e2 = Sil.BinOp (Sil.Le, e1, e2) in (* e1 <= e2 *) + let is_inconsistent = + if positive then Prover.check_atom prop (Prop.mk_inequality e2_lt_e1) (* e2 < e1 *) + else Prover.check_atom prop (Prop.mk_inequality e1_le_e2) (* e1 <= e2 *) in + begin + if is_inconsistent then + Propset.empty + else if positive then + Propset.singleton + (Prop.conjoin_eq ~footprint: (!Config.footprint) e1_le_e2 Sil.exp_one prop) + else + Propset.singleton + (Prop.conjoin_eq ~footprint: (!Config.footprint) e2_lt_e1 Sil.exp_one prop) + end + | Sil.BinOp (Sil.Gt, e2, e1) | Sil.BinOp (Sil.Lt, e1, e2) -> + (* e1 < e2 Case. Encode it as (e1 + (if positive then prune_polarity_inter else prune_polarity_union) tenv positive condition1 condition2 prop + | Sil.BinOp (Sil.LOr, condition1, condition2) -> + (if positive then prune_polarity_union else prune_polarity_inter) tenv positive condition1 condition2 prop + | Sil.BinOp _ | Sil.Lfield _ | Sil.Lindex _ -> + prune_ne tenv positive condition Sil.exp_zero prop + +and prune_polarity_inter tenv positive condition1 condition2 prop = + let res = ref Propset.empty in + let pset1 = prune_polarity tenv positive condition1 prop in + let do_p p = + res := Propset.union (prune_polarity tenv positive condition2 p) !res in + Propset.iter do_p pset1; + !res + +and prune_polarity_union tenv positive condition1 condition2 prop = + let pset1 = prune_polarity tenv positive condition1 prop in + let pset2 = prune_polarity tenv positive condition2 prop in + Propset.union pset1 pset2 + +let prune_prop tenv condition prop = + match condition with + | Sil.Const (Sil.Cint i) when Sil.Int.iszero i -> Propset.empty + | Sil.Const (Sil.Cint _) -> Propset.singleton prop + | _ -> prune_polarity tenv true condition prop + +let dangerous_functions = + let dangerous_list = ["gets"] in + ref ((list_map Procname.from_string) dangerous_list) + +let check_inherently_dangerous_function caller_pname callee_pname callee_pdesc = + if list_exists (Procname.equal callee_pname) !dangerous_functions then + let exn = Exceptions.Inherently_dangerous_function (Localise.desc_inherently_dangerous_function callee_pname) in + let pre_opt = State.get_normalized_pre (Abs.abstract_no_symop caller_pname) in + Reporting.log_warning caller_pname ~pre: pre_opt exn + +let call_should_be_skipped caller_pname callee_pname callee_pdesc = + let print_skip_warning () = + let exn = Exceptions.Skip_function (Localise.desc_skip_function callee_pname) in + Reporting.log_info caller_pname exn in + let should_skip () = + match Specs.get_summary callee_pname with + | None -> + not (Cfg.Procdesc.is_defined callee_pdesc) + (* treat calls with no specs as skip functions in angelic mode *) + (* TODO: turn angelic back on for C_CPP once we have abductive angelic analysis (t6935559) *) + || (!Config.angelic_execution && !Sil.curr_language <> Sil.C_CPP) + | Some summary -> + Specs.get_flag callee_pname proc_flag_skip <> None (* check skip flag *) + || summary.Specs.attributes.Sil.is_abstract (* skip abstract methods *) + (* treat calls with no specs as skip functions in angelic mode *) + (* TODO: turn angelic back on for C_CPP once we have abductive angelic analysis (t6935559) *) + || (!Config.angelic_execution && !Sil.curr_language <> Sil.C_CPP + && Specs.get_specs_from_payload summary == []) in + if !Config.intraprocedural then true + else if should_skip () then (print_skip_warning (); true) + else false + +let report_raise_memory_leak tenv msg hpred prop = + L.d_strln msg; + L.d_increase_indent 1; + L.d_strln "PROP:"; + Prop.d_prop prop; L.d_ln (); + L.d_strln "PREDICATE:"; + Prop.d_sigma [hpred]; + L.d_decrease_indent 1; + L.d_ln (); + let footprint_part = false in + let resource = match Errdesc.hpred_is_open_resource prop hpred with + | Some res -> res + | None -> Sil.Rmemory Sil.Mmalloc in + raise (Exceptions.Leak (footprint_part, prop, hpred, Errdesc.explain_leak tenv hpred prop None None, false, resource, try assert false with Assert_failure x -> x)) + +(** In case of constant string dereference, return the result immediately *) +let check_constant_string_dereference lexp = + let string_lookup s n = + let c = try Char.code (String.get s (Sil.Int.to_int n)) with Invalid_argument _ -> 0 in + Sil.exp_int (Sil.Int.of_int c) in + match lexp with + | Sil.BinOp(Sil.PlusPI, Sil.Const (Sil.Cstr s), e) + | Sil.Lindex (Sil.Const (Sil.Cstr s), e) -> + let value = match e with + | Sil.Const (Sil.Cint n) when Sil.Int.geq n Sil.Int.zero && Sil.Int.leq n (Sil.Int.of_int (String.length s)) -> + string_lookup s n + | _ -> Sil.exp_get_undefined false in + Some value + | Sil.Const (Sil.Cstr s) -> + Some (string_lookup s Sil.Int.zero) + | _ -> None + +(** Normalize an expression and check for arithmetic problems *) +let exp_norm_check_arith pdesc prop exp = + let pname = Cfg.Procdesc.get_proc_name pdesc in + match Prop.find_arithmetic_problem (State.get_path_pos ()) prop exp with + | Some (Prop.Div0 div), prop' -> + let desc = Errdesc.explain_divide_by_zero div (State.get_node ()) (State.get_loc ()) in + let exn = Exceptions.Divide_by_zero (desc, try assert false with Assert_failure x -> x) in + let pre_opt = State.get_normalized_pre (Abs.abstract_no_symop pname) in + Reporting.log_warning pname ~pre: pre_opt exn; + Prop.exp_normalize_prop prop exp, prop' + | Some (Prop.UminusUnsigned (e, typ)), prop' -> + let desc = Errdesc.explain_unary_minus_applied_to_unsigned_expression e typ (State.get_node ()) (State.get_loc ()) in + let exn = Exceptions.Unary_minus_applied_to_unsigned_expression (desc, try assert false with Assert_failure x -> x) in + let pre_opt = State.get_normalized_pre (Abs.abstract_no_symop pname) in + Reporting.log_warning pname ~pre: pre_opt exn; + Prop.exp_normalize_prop prop exp, prop' + | None, prop' -> Prop.exp_normalize_prop prop exp, prop' + +(** Check if [cond] is testing for NULL a pointer already dereferenced *) +let check_already_dereferenced pname cond prop = + let find_hpred lhs = + try Some (list_find (function + | Sil.Hpointsto (e, _, _) -> Sil.exp_equal e lhs + | _ -> false) (Prop.get_sigma prop)) + with Not_found -> None in + let rec is_check_zero = function + | Sil.Var id -> + Some id + | Sil.UnOp(Sil.LNot, e, _) -> + is_check_zero e + | Sil.BinOp ((Sil.Eq | Sil.Ne), Sil.Const Sil.Cint i, Sil.Var id) + | Sil.BinOp ((Sil.Eq | Sil.Ne), Sil.Var id, Sil.Const Sil.Cint i) when Sil.Int.iszero i -> + Some id + | _ -> None in + let dereferenced_line = match is_check_zero cond with + | Some id -> + (match find_hpred (Prop.exp_normalize_prop prop (Sil.Var id)) with + | Some (Sil.Hpointsto (_, se, _)) -> + (match Tabulation.find_dereference_without_null_check_in_sexp se with + | Some n -> Some (id, n) + | None -> None) + | _ -> None) + | None -> + None in + match dereferenced_line with + | Some (id, (n, pos)) -> + let desc = Errdesc.explain_null_test_after_dereference (Sil.Var id) (State.get_node ()) n (State.get_loc ()) in + let exn = (Exceptions.Null_test_after_dereference (desc, try assert false with Assert_failure x -> x)) in + let pre_opt = State.get_normalized_pre (Abs.abstract_no_symop pname) in + Reporting.log_warning pname ~pre: pre_opt exn + | None -> () + +let run_with_abs_val_eq_zero f = + let abs_val_old = !Config.abs_val in + Config.abs_val := 0; + let res = try f () with + | exn -> + Config.abs_val := abs_val_old; + raise exn in + Config.abs_val := abs_val_old; + res + +(** Check whether symbolic execution de-allocated a stack variable or a constant string, raising an exception in that case *) +let check_deallocate_static_memory prop_after = + let check_deallocated_attribute = function + | Sil.Lvar pv, Sil.Aresource ({ Sil.ra_kind = Sil.Rrelease } as ra) when Sil.pvar_is_local pv || Sil.pvar_is_global pv -> + let freed_desc = Errdesc.explain_deallocate_stack_var pv ra in + raise (Exceptions.Deallocate_stack_variable freed_desc) + | Sil.Const (Sil.Cstr s), Sil.Aresource ({ Sil.ra_kind = Sil.Rrelease } as ra) -> + let freed_desc = Errdesc.explain_deallocate_constant_string s ra in + raise (Exceptions.Deallocate_static_memory freed_desc) + | _ -> () in + let exp_att_list = Prop.get_all_attributes prop_after in + list_iter check_deallocated_attribute exp_att_list; + prop_after + +(** create a copy of a procdesc with a new proc name *) +let proc_desc_copy cfg pdesc pname pname' = + if (Procname.equal pname pname') then pdesc + else + (match Cfg.Procdesc.find_from_name cfg pname' with + | Some pdesc' -> pdesc' + | None -> + let open Cfg.Procdesc in + create { + cfg = cfg; + name = pname'; + proc_attributes = Sil.copy_proc_attributes (get_attributes pdesc); + is_defined = is_defined pdesc; + ret_type = get_ret_type pdesc; + formals = get_formals pdesc; + locals = get_locals pdesc; + captured = get_captured pdesc; + loc = get_loc pdesc; + }) + +let method_exists right_proc_name methods = + if !Sil.curr_language = Sil.Java then + list_exists (fun meth_name -> Procname.equal right_proc_name meth_name) methods + else (* ObjC case *) + Specs.summary_exists right_proc_name + +let resolve_method tenv class_name proc_name = + let found_class = + let visited = ref Mangled.MangledSet.empty in + let rec resolve class_name = + visited := Mangled.MangledSet.add class_name !visited; + let right_proc_name = + if Procname.is_java proc_name then + Procname.java_replace_class proc_name (Mangled.to_string class_name) + else Procname.objc_replace_class proc_name (Mangled.to_string class_name) in + let type_name = Sil.TN_csu (Sil.Class, class_name) in + match Sil.tenv_lookup tenv type_name with + | Some (Sil.Tstruct (_, _, Sil.Class, cls, super_classes, methods, iann)) -> + if method_exists right_proc_name methods then + Some right_proc_name + else + (match super_classes with + | (Sil.Class, super_class):: interfaces -> + if not (Mangled.MangledSet.mem super_class !visited) + then resolve super_class + else None + | _ -> None) + | _ -> None in + resolve class_name in + match found_class with + | None -> + Logging.d_strln + ("Couldn't find method in the hierarchy of type "^(Mangled.to_string class_name)); + proc_name + | Some proc_name -> proc_name + +(** If the dynamic type of the object calling a method is known, the method from the dynamic type is called *) +let call_virtual cfg tenv (_prop: Prop.normal Prop.t) actual_params pdesc fn : Cfg.Procdesc.t = + let exp = (match actual_params with + | [] -> assert false + | (exp, typ):: rest -> exp) in + let typexp = + try + let hpred = list_find (function + | Sil.Hpointsto(e, _, _) -> Sil.exp_equal e exp + | _ -> false) (Prop.get_sigma _prop) in + match hpred with + | Sil.Hpointsto(e, _, texp) -> + Some texp + | _ -> None + with Not_found -> None in + match typexp with + | Some (Sil.Sizeof (Sil.Tstruct (l, _, Sil.Class, (Some class_name), _, _, _), st)) -> + let fn' = resolve_method tenv class_name fn in + proc_desc_copy cfg pdesc fn fn' + | _ -> pdesc + +(** recognize calls to shared_ptr procedures and re-direct them to infer procedures for modelling *) +let redirect_shared_ptr tenv cfg pname actual_params = + let class_shared_ptr typ = + try match Sil.expand_type tenv typ with + | Sil.Tstruct (_, _, Sil.Class, Some cl_name, _, _, _) -> + let name = Mangled.to_string cl_name in + name = "shared_ptr" || name = "__shared_ptr" + | t -> false + with exn when exn_not_timeout exn -> false in + (* We pattern match over some specific library function, *) + (* so we make precise matching to distinghuis between *) + (* references and pointers in C++ *) + let ptr_to filter = function + | Sil.Tptr (t, Sil.Pk_pointer) + | Sil.Tptr (t, Sil.Pk_objc_weak) + | Sil.Tptr (t, Sil.Pk_objc_unsafe_unretained) + | Sil.Tptr (t, Sil.Pk_objc_autoreleasing) -> filter t + | _ -> false in + let ref_to filter = function + | Sil.Tptr (t, Sil.Pk_reference) -> filter t + | _ -> false in + let ptr_to_shared_ptr typ = ptr_to class_shared_ptr typ in + let ref_to_shared_ptr typ = ref_to class_shared_ptr typ in + let ptr_to_something typ = ptr_to (fun _ -> true) typ in + let pname' = match Procname.to_string pname, actual_params with + | "shared_ptr", [(_, this_t); (_, t1)] when ptr_to_shared_ptr this_t && ptr_to_something t1 -> + Procname.from_string "__infer_shared_ptr" + | "shared_ptr", [(_, this_t); (_, t1)] when ptr_to_shared_ptr this_t && ref_to_shared_ptr t1 -> + Procname.from_string "__infer_shared_ptr_ref" + | "operator=", [(_, this_t); (_, t1)] when ptr_to_shared_ptr this_t && ref_to_shared_ptr t1 -> + Procname.from_string "__infer_shared_ptr_eq" + | "operator==", [(_, t1); (_, t2)] when ref_to_shared_ptr t1 && ref_to_shared_ptr t2 -> + Procname.from_string "__infer_shared_ptr_eqeq" + | ("operator->" | "operator*"),[(_, t1)] when ptr_to_shared_ptr t1 -> + Procname.from_string "__infer_shared_ptr_arrow" + | "~shared_ptr",[(_, t1)] -> + Procname.from_string "__infer_shared_ptr_destructor" + | _ -> pname in + if Procname.equal pname pname' then pname + else + let found = Specs.summary_exists pname' in + if found then + match Cfg.Procdesc.find_from_name cfg pname with + | None -> pname + | Some pdesc -> + let _pdesc = proc_desc_copy cfg pdesc pname pname' in + pname' + else pname + +(** recognize calls to the constructor java.net.URL and splits the argument string to be only the protocol. *) +let call_constructor_url_update_args tenv cfg pname actual_params = + let url_pname = Procname.mangled_java ((Some "java.net"), "URL") None "" [(Some "java.lang"), "String"] in + if (Procname.equal url_pname pname) then + (match actual_params with + | [this; (Sil.Const (Sil.Cstr s), atype)] -> + let parts = Str.split (Str.regexp_string "://") s in + (match parts with + | frst:: parts -> + if (frst = "http") || (frst = "ftp") || (frst = "https") || (frst = "mailto") || (frst = "jar") then + [this; (Sil.Const (Sil.Cstr frst), atype)] + else actual_params + | _ -> actual_params) + | [this; _, atype] -> [this; (Sil.Const (Sil.Cstr "file"), atype)] + | _ -> actual_params) + else actual_params + +(** Handles certain method calls in a special way *) +let handle_special_cases_call tenv cfg pname actual_params = + if (!Sil.curr_language = Sil.Java) then + pname, (call_constructor_url_update_args tenv cfg pname actual_params) + else if (!Sil.curr_language = Sil.C_CPP) then + (redirect_shared_ptr tenv cfg pname actual_params), actual_params + else pname, actual_params + +let handle_objc_method_call actual_pars actual_params pre tenv cfg ret_ids pdesc callee_pname loc path = + let receiver_self receiver prop = + list_exists (fun hpred -> + match hpred with + | Sil.Hpointsto (Sil.Lvar pv, Sil.Eexp (e, _), _) -> + Sil.exp_equal e receiver && Sil.pvar_is_seed pv && + Sil.pvar_get_name pv = Mangled.from_string "self" + | _ -> false) (Prop.get_sigma prop) in + let path_description = "Message "^(Procname.to_simplified_string callee_pname)^" with receiver nil returns nil." in + let receiver = (match actual_pars with + | (e, _):: _ -> e + | _ -> raise (Exceptions.Internal_error + (Localise.verbatim_desc "In Objective-C instance method call there should be a receiver."))) in + let is_receiver_null = + match actual_pars with + | (e, _):: _ when Sil.exp_equal e Sil.exp_zero || Option.is_some (Prop.get_objc_null_attribute pre e) -> true + | _ -> false in + let prop_null = + match ret_ids with + | [ret_id] -> Prop.conjoin_eq (Sil.Var ret_id) Sil.exp_zero pre + | _ -> pre in + let add_objc_null_attribute_or_nullify_result prop = + match ret_ids with + | [ret_id] -> + (match Prop.find_equal_formal_path receiver prop with + | Some info -> + Prop.add_or_replace_exp_attribute (fun a1 a2 -> ()) prop (Sil.Var ret_id) + (Sil.Aobjc_null info) + | None -> Prop.conjoin_eq (Sil.Var ret_id) Sil.exp_zero prop) + | _ -> prop in + if is_receiver_null then (* objective-c instance method with a null receiver just return objc_null(res) *) + let path = Paths.Path.add_description path path_description in + L.d_strln ("Object-C method " ^ Procname.to_string callee_pname^ " called with nil receiver. Returning 0/nil"); + (* We wish to nullify the result. However, in some cases, we want to add the attribute OBJC_NULL to it so that we *) + (* can keep track of how this object became null, so that in a NPE we can separate it into a different error type *) + [(add_objc_null_attribute_or_nullify_result pre, path)] + else + let res = Tabulation.exe_function_call tenv cfg ret_ids pdesc callee_pname loc actual_params pre path in + let is_undef = match Prop.get_resource_undef_attribute pre receiver with + | Some (Sil.Aundef _) -> true + | _ -> false in + if !Config.footprint && not is_undef then + let res_null = (* returns: (objc_null(res) /\ receiver=0) or an empty list of results *) + let is_receiver_self = receiver_self receiver pre in + let pre_with_attr_or_null = + if is_receiver_self then prop_null + else add_objc_null_attribute_or_nullify_result pre in + let propset = prune_ne tenv false receiver Sil.exp_zero pre_with_attr_or_null in + if Propset.is_empty propset then [] + else + let prop = list_hd (Propset.to_proplist propset) in + let path = + if is_receiver_self then path + else Paths.Path.add_description path path_description in + [(prop, path)] in + res_null @ res + else res (* Not known if receiver = 0 and not footprint. Standard tabulation *) + +(** Execute [instr] with a symbolic heap [prop].*) +let rec sym_exec cfg tenv pdesc _instr (_prop: Prop.normal Prop.t) path +: (Prop.normal Prop.t * Paths.Path.t) list = + let pname = Cfg.Procdesc.get_proc_name pdesc in + State.set_instr _instr; (* mark instruction last seen *) + State.set_prop_tenv_pdesc _prop tenv pdesc; (* mark prop,tenv,pdesc last seen *) + SymOp.pay(); (* pay one symop *) + let ret_old_path pl = (* return the old path unchanged *) + list_map (fun p -> (p, path)) pl in + let instr = match _instr with + | Sil.Call (ret, exp, par, loc, call_flags) -> + let exp' = Prop.exp_normalize_prop _prop exp in + let instr' = match exp' with + | Sil.Const (Sil.Ctuple (e1 :: el)) -> (* closure: combine arguments to call *) + let e1' = Prop.exp_normalize_prop _prop e1 in + let par' = list_map (fun e -> (e, Sil.Tvoid)) el in + Sil.Call (ret, e1', par' @ par, loc, call_flags) + | _ -> + Sil.Call (ret, exp', par, loc, call_flags) in + instr' + | _ -> _instr in + match instr with + | Sil.Letderef (id, rhs_exp, typ, loc) -> + begin + try + let n_rhs_exp, prop = exp_norm_check_arith pdesc _prop rhs_exp in + let n_rhs_exp' = Prop.exp_collapse_consecutive_indices_prop prop typ n_rhs_exp in + match check_constant_string_dereference n_rhs_exp' with + | Some value -> + ret_old_path [Prop.conjoin_eq (Sil.Var id) value prop] + | None -> + let iter_list = Rearrange.rearrange pdesc tenv n_rhs_exp' typ prop loc in + let prop_list = + list_fold_left (execute_letderef pdesc tenv id n_rhs_exp') [] iter_list in + ret_old_path (list_rev prop_list) + with + | Rearrange.ARRAY_ACCESS -> + if (!Config.array_level = 0) then assert false + else + let undef = Sil.exp_get_undefined false in + ret_old_path [Prop.conjoin_eq (Sil.Var id) undef _prop] + end + | Sil.Set (lhs_exp, typ, rhs_exp, loc) -> + begin + try + let n_lhs_exp, _prop' = exp_norm_check_arith pdesc _prop lhs_exp in + let n_rhs_exp, prop = exp_norm_check_arith pdesc _prop' rhs_exp in + let prop = Prop.replace_objc_null prop n_lhs_exp n_rhs_exp in + let n_lhs_exp' = Prop.exp_collapse_consecutive_indices_prop prop typ n_lhs_exp in + let iter_list = Rearrange.rearrange pdesc tenv n_lhs_exp' typ prop loc in + let prop_list = list_fold_left (execute_set pdesc tenv n_rhs_exp) [] iter_list in + ret_old_path (list_rev prop_list) + with + | Rearrange.ARRAY_ACCESS -> + if (!Config.array_level = 0) then assert false + else ret_old_path [_prop] + end + | Sil.Prune (cond, loc, true_branch, ik) -> + let check_condition_always_true_false () = + let report_condition_always_true_false i = + let skip_loop = match ik with + | Sil.Ik_while | Sil.Ik_for -> not (Sil.Int.iszero i) (* skip wile(1) and for (;1;) *) + | Sil.Ik_dowhile -> true (* skip do..while *) + | Sil.Ik_land_lor -> true (* skip subpart of a condition obtained from compilation of && and || *) + | _ -> false in + true_branch && not skip_loop in + match Prop.exp_normalize_prop Prop.prop_emp cond with + | Sil.Const (Sil.Cint i) when report_condition_always_true_false i -> + let node = State.get_node () in + let desc = Errdesc.explain_condition_always_true_false i cond node loc in + let exn = Exceptions.Condition_always_true_false (desc, not (Sil.Int.iszero i), try assert false with Assert_failure x -> x) in + let pre_opt = State.get_normalized_pre (Abs.abstract_no_symop pname) in + Reporting.log_warning pname ~pre: pre_opt exn + | _ -> () in + check_already_dereferenced pname cond _prop; + check_condition_always_true_false (); + let n_cond, prop = exp_norm_check_arith pdesc _prop cond in + ret_old_path (Propset.to_proplist (prune_prop tenv n_cond prop)) + | Sil.Call (ret_ids, Sil.Const (Sil.Cfun fn), args, loc, call_flags) when function_is_builtin fn -> + let sym_exe_builtin = Builtin.get_sym_exe_builtin fn in + sym_exe_builtin cfg pdesc instr tenv _prop path ret_ids args fn loc + | Sil.Call (ret_ids, Sil.Const (Sil.Cfun _fn), _actual_params, loc, call_flags) -> (** Generic fun call with known name *) + let prop_r = ref _prop in + let _n_actual_params = list_map (fun (e, t) -> + let e', p' = exp_norm_check_arith pdesc !prop_r e in + prop_r := p'; + e', t) _actual_params in + let fn, n_actual_params = handle_special_cases_call tenv cfg _fn _n_actual_params in + let callee_pdesc = match Cfg.Procdesc.find_from_name cfg fn with + | Some callee_pdesc -> callee_pdesc + | None -> assert false in + let callee_pdesc' = + if call_flags.Sil.cf_virtual then + (call_virtual cfg tenv _prop n_actual_params callee_pdesc fn) + else callee_pdesc in + sym_exe_call cfg pdesc tenv !prop_r path ret_ids n_actual_params callee_pdesc' loc + | Sil.Call (ret_ids, fun_exp, actual_params, loc, call_flags) -> (** Call via function pointer *) + let prop_r = ref _prop in + let n_actual_params = list_map (fun (e, t) -> + let e', p' = exp_norm_check_arith pdesc !prop_r e in + prop_r := p'; + e', t) actual_params in + if call_flags.Sil.cf_is_objc_block then + Rearrange.check_call_to_objc_block_error pdesc !prop_r fun_exp loc; + Rearrange.check_dereference_error pdesc !prop_r fun_exp loc; + if call_flags.Sil.cf_noreturn then begin + L.d_str "Unknown function pointer with noreturn attribute "; Sil.d_exp fun_exp; L.d_strln ", diverging."; + execute_diverge !prop_r path + end else begin + L.d_str "Unknown function pointer "; Sil.d_exp fun_exp; L.d_strln ", returning undefined value."; + let callee_pname = Procname.from_string "__function_pointer__" in + call_unknown_or_scan + false cfg pdesc tenv !prop_r path ret_ids None n_actual_params callee_pname loc + end + | Sil.Nullify (pvar, loc, deallocate) -> + begin + let eprop = Prop.expose _prop in + match list_partition + (function + | Sil.Hpointsto (Sil.Lvar pvar', _, _) -> Sil.pvar_equal pvar pvar' + | _ -> false) (Prop.get_sigma eprop) with + | [Sil.Hpointsto(e, se, typ)], sigma' -> + let sigma'' = match deallocate with + | false -> + let se' = execute_nullify_se se in + Sil.Hpointsto(e, se', typ):: sigma' + | true -> sigma' in + let eprop_res = Prop.replace_sigma sigma'' eprop in + ret_old_path [Prop.normalize eprop_res] + | _ -> assert false + end + | Sil.Abstract loc -> + let node = State.get_node () in + let blocks_nullified = get_nullified_block node in + list_iter (check_block_retain_cycle cfg tenv pname _prop) blocks_nullified; + if Prover.check_inconsistency _prop + then + ret_old_path [] + else + ret_old_path [Abs.remove_redundant_array_elements pname tenv + (Abs.abstract pname tenv _prop)] + | Sil.Remove_temps (temps, loc) -> + ret_old_path [Prop.exist_quantify (Sil.fav_from_list temps) _prop] + | Sil.Declare_locals (ptl, loc) -> + let sigma_locals = + let add_None (x, y) = (x, Sil.Sizeof (y, Sil.Subtype.exact), None) in + let fp_mode = !Config.footprint in + Config.footprint := false; (* no footprint vars for locals *) + let sigma_locals = + list_map + (Prop.mk_ptsto_lvar (Some tenv) Prop.Fld_init Sil.inst_initial) + (list_map add_None ptl) in + Config.footprint := fp_mode; + sigma_locals in + let sigma' = Prop.get_sigma _prop @ sigma_locals in + let prop' = Prop.normalize (Prop.replace_sigma sigma' _prop) in + ret_old_path [prop'] + | Sil.Stackop _ -> (* this should be handled at the propset level *) + assert false + | Sil.Goto_node (node_e, loc) -> + let n_node_e, prop = exp_norm_check_arith pdesc _prop node_e in + begin + match n_node_e with + | Sil.Const (Sil.Cint i) -> + let node_id = Sil.Int.to_int i in + State.set_goto_node node_id; + [(prop, path)] + | _ -> (* target not known, do nothing as the next nodes are set to the possible targets by the front-end *) + [(prop, path)] + end +and execute_diverge prop path = + State.add_diverging_states (Paths.PathSet.from_renamed_list [(prop, path)]); (* diverge *) + [] + +(** Like sym_exec but for generated instructions. +If errors occur and [mask_errors] is false, just treat as skip.*) +and sym_exec_generated mask_errors cfg tenv pdesc instrs ppl = + let exe_instr instr (p, path) = + L.d_str "Executing Generated Instruction "; Sil.d_instr instr; L.d_ln (); + try sym_exec cfg tenv pdesc instr p path + with exn when exn_not_timeout exn && mask_errors -> + let err_name, _, ml_source, _ , _, _, _ = Exceptions.recognize_exception exn in + let loc = (match ml_source with + | Some (src, l, c) -> "at "^(src^" "^(string_of_int l)) + | None -> "") in + L.d_warning ("Generated Instruction Failed with: " ^ (Localise.to_string err_name)^loc ); L.d_ln(); + [(p, path)] in + let f plist instr = list_flatten (list_map (exe_instr instr) plist) in + list_fold_left f ppl instrs + +(** execute a call for an unknown or scan function *) +and call_unknown_or_scan is_scan cfg pdesc tenv pre path + ret_ids ret_type_option actual_pars callee_pname loc = + let instrs, undef_vars = (* instructions to set vars passed by reference to fresh ids *) + let gen_instrs = ref [] in + let gen_undef_vars = ref [] in + let add_instr instr = gen_instrs := instr :: !gen_instrs in + let add_undef_var var = gen_undef_vars := var :: !gen_undef_vars in + let type_is_not_structured = function + | Sil.Tint _ | Sil.Tfloat _ | Sil.Tvoid | Sil.Tfun _ | Sil.Tptr _ | Sil.Tenum _ -> true + | Sil.Tvar _ | Sil.Tstruct _ | Sil.Tarray _ -> false in + let expand_ptr_type t = match t with + | Sil.Tptr (Sil.Tvar tn, pk) -> + (match Sil.tenv_lookup tenv tn with + | Some typ -> Sil.Tptr (typ, pk) + | None -> t) + | _ -> t in + let do_actual_par (e, _t) = + let t = expand_ptr_type _t in + L.d_str "do_actual_par exp: "; Sil.d_exp e; L.d_str" typ: "; Sil.d_typ_full t; L.d_ln(); + match e, t with + | _, Sil.Tptr (Sil.Tint ik, pk) when is_scan && Sil.ikind_is_char ik -> (* scan case "%s" *) + let infer_get_array_size = Procname.from_string "__get_array_size" in + let instr1 = Sil.Call ([], Sil.Const (Sil.Cfun infer_get_array_size), [(e, Sil.Tptr (Sil.Tint ik, pk))], loc, Sil.cf_default) in + let id_fresh = Ident.create_fresh Ident.kprimed in + let instr2 = Sil.Letderef (id_fresh, e, t, loc) in + add_instr instr1; + add_instr instr2 + | Sil.Lvar _, Sil.Tptr (t', _) when type_is_not_structured t' -> + let id_fresh = Ident.create_fresh Ident.kprimed in + let instr = Sil.Set (e, t, Sil.Var id_fresh, loc) in + add_instr instr; + add_undef_var id_fresh + | Sil.Lvar _, Sil.Tptr ((Sil.Tstruct (ftal, sftal, _, _, _, _, _) as tstruct, _)) -> + let do_fld (f, typ, ann) = + let id_fresh = Ident.create_fresh Ident.kprimed in + let instr = Sil.Set (Sil.Lfield (e, f, tstruct), typ, Sil.Var id_fresh, loc) in + add_instr instr; + add_undef_var id_fresh in + list_iter do_fld ftal + | _ -> () in + list_iter do_actual_par actual_pars; + list_rev !gen_instrs, list_rev !gen_undef_vars in + let remove_resource_att prop = + let do_exp p (e, t) = + let do_attribute q = function + | Sil.Aresource _ as res -> + Prop.remove_attribute res q + | _ -> q in + list_fold_left do_attribute p (Prop.get_exp_attributes p e) in + list_fold_left do_exp prop actual_pars in + let prop = + if !Sil.curr_language <> Sil.Java || Procname.is_infer_undefined callee_pname then pre + else + let prop_no_res = remove_resource_att pre in + match ret_ids, ret_type_option with + | [ret_id], Some ret_typ -> + (* To avoid obvious false positives, assume skip functions do not return null pointers *) + let add_ret_non_null ret_id ret_typ prop = + match ret_typ with + | Sil.Tptr _ -> Prop.conjoin_neq (Sil.Var ret_id) Sil.exp_zero prop + | _ -> prop in + let is_rec_call pname = (* TODO: (t7147096) extend this to detect mutual recursion *) + Procname.equal pname (Cfg.Procdesc.get_proc_name pdesc) in + if !Config.angelic_execution && not (is_rec_call callee_pname) then + (* introduce a fresh program variable to allow abduction on the return value *) + let ret_pv = Sil.mk_pvar_abducted_ret callee_pname (State.get_loc ()) in + let already_has_abducted_retval p = + list_exists + (fun hpred -> match hpred with + | Sil.Hpointsto (Sil.Lvar pv, _, _) -> Sil.pvar_equal pv ret_pv + | _ -> false + ) + (Prop.get_sigma_footprint p) in + (* prevent introducing multiple abducted retvals for a single call site in a loop *) + if already_has_abducted_retval prop_no_res then prop_no_res + else + let ret_lvar = Sil.Lvar ret_pv in + let sizeof_exp = Sil.Sizeof (ret_typ, Sil.Subtype.subtypes) in + if !Config.footprint then + let fresh_fp_var = Sil.Var (Ident.create_fresh Ident.kfootprint) in + let retval_pt_fpvar = + Prop.mk_ptsto ret_lvar (Sil.Eexp (fresh_fp_var, Sil.Inone)) sizeof_exp in + let sigma_fp = Prop.get_sigma_footprint prop_no_res in + let prop = Prop.replace_sigma_footprint (retval_pt_fpvar :: sigma_fp) prop_no_res in + let prop' = Prop.normalize prop in + let prop'' = Prop.conjoin_eq ~footprint: true (Sil.Var ret_id) fresh_fp_var prop' in + add_ret_non_null ret_id ret_typ prop'' + else + (* bind return id to the abducted value pointed to by the pvar we introduced *) + let bind_return_id prop = function + | Sil.Hpointsto (Sil.Lvar pv, Sil.Eexp (rhs, _), _) + when Sil.pvar_equal pv ret_pv -> + Prop.conjoin_eq (Sil.Var ret_id) rhs prop + | _ -> prop in + list_fold_left bind_return_id prop_no_res (Prop.get_sigma prop_no_res) + else add_ret_non_null ret_id ret_typ prop_no_res + | _ -> prop_no_res in + let after_ref_var_instrs = (* assign fresh values to vars passed by ref *) + let report_error = is_scan in + sym_exec_generated (not report_error) cfg tenv pdesc instrs [(prop, path)] in + let vars_to_mark_undefined = ret_ids @ undef_vars in + let att_undef = Sil.Aundef (callee_pname, loc, State.get_path_pos ()) in + let mark_undefined_vars p = (* add undefined attribute to returned vars and new values for vars passed by ref *) + list_fold_left (fun p id -> mark_id_as_undefined id p att_undef) p vars_to_mark_undefined in + if is_scan (* if scan function, don't mark values with undef attributes *) + then list_map (fun (p, path) -> (Tabulation.remove_constant_string_class p, path)) after_ref_var_instrs + else list_map (fun (p, path) -> (mark_undefined_vars p, path)) after_ref_var_instrs + +(** Perform symbolic execution for a function call *) +and sym_exe_call cfg pdesc tenv pre path ret_ids actual_pars callee_pdesc loc = + let caller_pname = Cfg.Procdesc.get_proc_name pdesc in + let callee_pname = Cfg.Procdesc.get_proc_name callee_pdesc in + let check_return_value_ignored () = (* check if the return value of the call is ignored, and issue a warning *) + let ret_typ = Cfg.Procdesc.get_ret_type callee_pdesc in + let is_ignored = match ret_typ, ret_ids with + | Sil.Tvoid, _ -> false + | Sil.Tint _, _ when Cfg.Procdesc.is_defined callee_pdesc = false -> + (* if the proc returns Tint and is not defined, *) + (* don't report ignored return value *) + false + | _, [] -> true + | _, [id] -> Errdesc.id_is_assigned_then_dead (State.get_node ()) id + | _ -> false in + if is_ignored + && Specs.get_flag callee_pname proc_flag_ignore_return = None then + let err_desc = Localise.desc_return_value_ignored callee_pname loc in + let exn = (Exceptions.Return_value_ignored (err_desc, try assert false with Assert_failure x -> x)) in + let pre_opt = State.get_normalized_pre (Abs.abstract_no_symop caller_pname) in + Reporting.log_warning caller_pname ~pre: pre_opt exn in + check_inherently_dangerous_function caller_pname callee_pname callee_pdesc; + let caller_pname = Cfg.Procdesc.get_proc_name pdesc in + if call_should_be_skipped caller_pname callee_pname callee_pdesc then + begin + L.d_strln ("Undefined function " ^ Procname.to_string callee_pname ^ ", returning undefined value."); + let proc_name = Cfg.Procdesc.get_proc_name pdesc in + let summary = Specs.get_summary_unsafe proc_name in + Specs.CallStats.trace + summary.Specs.stats.Specs.call_stats callee_pname loc + (Specs.CallStats.CR_skip) !Config.footprint; + let callee_pname = Cfg.Procdesc.get_proc_name callee_pdesc + and ret_type_option = Some (Cfg.Procdesc.get_ret_type callee_pdesc) in + call_unknown_or_scan + false cfg pdesc tenv pre path ret_ids ret_type_option actual_pars callee_pname loc + end + else begin + let formal_types = list_map (fun (_, typ) -> typ) (Cfg.Procdesc.get_formals callee_pdesc) in + let rec comb actual_pars formal_types = + match actual_pars, formal_types with + | [], [] -> actual_pars + | (e, t_e):: etl', t:: tl' -> + (e, if (!Config.Experiment.activate_subtyping_in_cpp || !Sil.curr_language = Sil.Java) then t_e else t) :: comb etl' tl' + | _,[] -> + if !Config.developer_mode then Errdesc.warning_err (State.get_loc ()) "likely use of variable-arguments function, or function prototype missing@."; + L.d_warning "likely use of variable-arguments function, or function prototype missing"; L.d_ln(); + L.d_str "actual parameters: "; Sil.d_exp_list (list_map fst actual_pars); L.d_ln (); + L.d_str "formal parameters: "; Sil.d_typ_list formal_types; L.d_ln (); + actual_pars + | [], _ -> + L.d_str ("**** ERROR: Procedure " ^ Procname.to_string callee_pname); + L.d_strln (" mismatch in the number of parameters ****"); + L.d_str "actual parameters: "; Sil.d_exp_list (list_map fst actual_pars); L.d_ln (); + L.d_str "formal parameters: "; Sil.d_typ_list formal_types; L.d_ln (); + raise (Exceptions.Wrong_argument_number (try assert false with Assert_failure x -> x)) in + let actual_params = comb actual_pars formal_types in + (* Actual parameters are associated to their formal + parameter type if there are enough formal parameters, and + to their actual type otherwise. The latter case happens + with variable - arguments functions *) + check_return_value_ignored (); + (* In case we call an objc instance method we add and extra spec *) + (* were the receiver is null and the semantics of the call is nop*) + if (!Sil.curr_language <> Sil.Java) && !Config.objc_method_call_semantics && + (Cfg.Procdesc.get_attributes callee_pdesc).Sil.is_objc_instance_method then + handle_objc_method_call actual_pars actual_params pre tenv cfg ret_ids pdesc callee_pname loc path + else (* non-objective-c method call. Standard tabulation *) + Tabulation.exe_function_call tenv cfg ret_ids pdesc callee_pname loc actual_params pre path + end + +(** perform symbolic execution for a single prop, and check for junk *) +and sym_exec_wrapper handle_exn cfg tenv pdesc instr ((prop: Prop.normal Prop.t), path) +: Paths.PathSet.t = + let pname = Cfg.Procdesc.get_proc_name pdesc in + let prop_primed_to_normal p = (** Rename primed vars with fresh normal vars, and return them *) + let fav = Prop.prop_fav p in + Sil.fav_filter_ident fav Ident.is_primed; + let ids_primed = Sil.fav_to_list fav in + let ids_primed_normal = + list_map (fun id -> (id, Ident.create_fresh Ident.knormal)) ids_primed in + let ren_sub = Sil.sub_of_list (list_map (fun (id1, id2) -> (id1, Sil.Var id2)) ids_primed_normal) in + let p' = Prop.normalize (Prop.prop_sub ren_sub p) in + let fav_normal = Sil.fav_from_list (list_map snd ids_primed_normal) in + p', fav_normal in + let prop_normal_to_primed fav_normal p = (* rename given normal vars to fresh primed *) + if Sil.fav_to_list fav_normal = [] then p + else Prop.exist_quantify fav_normal p in + try + let pre_process_prop p = + let p', fav = + if Sil.instr_is_auxiliary instr + then p, Sil.fav_new () + else prop_primed_to_normal p in + let p'' = + let map_res_action e ra = (* update the vpath in resource attributes *) + let vpath, _ = Errdesc.vpath_find p' e in + { ra with Sil.ra_vpath = vpath } in + Prop.attribute_map_resource p' map_res_action in + p'', fav in + let post_process_result fav_normal p path = + let p' = prop_normal_to_primed fav_normal p in + State.set_path path None; + let node_has_abstraction node = + let instr_is_abstraction = function + | Sil.Abstract _ -> true + | _ -> false in + list_exists instr_is_abstraction (Cfg.Node.get_instrs node) in + let curr_node = State.get_node () in + match Cfg.Node.get_kind curr_node with + | Cfg.Node.Prune_node _ when not (node_has_abstraction curr_node) -> + (* don't check for leaks in prune nodes, unless there is abstraction anyway, but force them into either branch *) + p' + | _ -> + check_deallocate_static_memory (Abs.abstract_junk ~original_prop: p pname tenv p') in + L.d_str "Instruction "; Sil.d_instr instr; L.d_ln (); + let prop', fav_normal = pre_process_prop prop in + let res_list = run_with_abs_val_eq_zero (* no exp abstraction during sym exe *) + (fun () -> + sym_exec cfg tenv pdesc instr prop' path) in + let res_list_nojunk = list_map (fun (p, path) -> (post_process_result fav_normal p path, path)) res_list in + let results = list_map (fun (p, path) -> (Prop.prop_rename_primed_footprint_vars p, path)) res_list_nojunk in + L.d_strln "Instruction Returns"; + Propgraph.d_proplist prop (list_map fst results); L.d_ln (); + State.mark_instr_ok (); + Paths.PathSet.from_renamed_list results + with exn when Exceptions.handle_exception exn && !Config.footprint -> + handle_exn exn; (* calls State.mark_instr_fail *) + if !Config.nonstop + then (Paths.PathSet.from_renamed_list [(prop, path)]) (* in nonstop mode treat the instruction as skip *) + else Paths.PathSet.empty + +(** {2 Lifted Abstract Transfer Functions} *) + +let lifted_sym_exec + handle_exn cfg tenv pdesc (pset : Paths.PathSet.t) node (instrs : Sil.instr list) +: Paths.PathSet.t = + let exe_instr_prop instr p tr (pset1: Paths.PathSet.t) = + let pset2 = + if Tabulation.prop_is_exn pdesc p && not (Sil.instr_is_auxiliary instr) + && Cfg.Node.get_kind node <> Cfg.Node.exn_handler_kind + (* skip normal instructions if an exception was thrown, unless this is an exception handler node *) + then + begin + L.d_str "Skipping instr "; Sil.d_instr instr; L.d_strln " due to exception"; + Paths.PathSet.from_renamed_list [(p, tr)] + end + else sym_exec_wrapper handle_exn cfg tenv pdesc instr (p, tr) in + Paths.PathSet.union pset2 pset1 in + let exe_instr_pset (pset, stack) instr = (** handle a single instruction at the set level *) + let pp_stack_instr pset' = + L.d_str "Stack Instruction "; Sil.d_instr instr; L.d_ln (); + L.d_strln "Stack Instruction Returns"; + Propset.d Prop.prop_emp (Paths.PathSet.to_propset pset'); L.d_ln () in + match instr, stack with + | Sil.Stackop (Sil.Push, _), _ -> + pp_stack_instr pset; + (pset, pset :: stack) + | Sil.Stackop (Sil.Swap, _), (pset':: stack') -> + pp_stack_instr pset'; + (pset', pset:: stack') + | Sil.Stackop (Sil.Pop, _), (pset':: stack') -> + let pset'' = Paths.PathSet.union pset pset' in + pp_stack_instr pset''; + (pset'', stack') + | Sil.Stackop _, _ -> (* should not happen *) + assert false + | _ -> + let pset' = Paths.PathSet.fold (exe_instr_prop instr) pset Paths.PathSet.empty in + (pset', stack) in + let stack = [] in + let pset', stack' = list_fold_left exe_instr_pset (pset, stack) instrs in + if stack' != [] then assert false; (* final stack must be empty *) + pset' + +(* ============== START of ModelBuiltins ============== *) +module ModelBuiltins = struct + (** This module contains models for the builtin functions supported *) + + let execute___no_op prop path: Builtin.ret_typ = + [(prop, path)] + + (** model va_arg as always returning 0 *) + let execute___builtin_va_arg cfg pdesc instr tenv prop path ret_ids args callee_pname loc + : Builtin.ret_typ = + match args, ret_ids with + | [(lexp1, typ1); (lexp2, typ2); (lexp3, typ3)], _ -> + let instr' = Sil.Set (lexp3, typ3, Sil.exp_zero, loc) in + sym_exec_generated true cfg tenv pdesc [instr'] [(prop, path)] + | _ -> raise (Exceptions.Wrong_argument_number (try assert false with Assert_failure x -> x)) + + let mk_empty_array size = + Sil.Earray (size, [], Sil.inst_none) + + let extract_array_type typ = + if (!Sil.curr_language = Sil.Java) then + match typ with + | Sil.Tptr ( Sil.Tarray (typ', _), _) -> Some typ' + | _ -> None + else + match typ with + | Sil.Tptr (typ', _) | Sil.Tarray (typ', _) -> + Some typ' + | _ -> None + + (** Return a result from a procedure call. *) + let return_result e prop ret_ids = + match ret_ids with + | [ret_id] -> Prop.conjoin_eq e (Sil.Var ret_id) prop + | _ -> prop + + let execute___get_array_size cfg pdesc instr tenv _prop path ret_ids args callee_pname loc + : Builtin.ret_typ = + match args with + | [(lexp, typ)] when list_length ret_ids <= 1 -> + let return_result_for_array_size e prop ret_ids = return_result e prop ret_ids in + let n_lexp, prop = exp_norm_check_arith pdesc _prop lexp in + begin + try + let hpred = list_find (function + | Sil.Hpointsto(e, _, _) -> Sil.exp_equal e n_lexp + | _ -> false) (Prop.get_sigma prop) in + match hpred with + | Sil.Hpointsto(e, Sil.Earray(size, _, _), _) -> + [(return_result_for_array_size size prop ret_ids, path)] + | _ -> [] + with Not_found -> + let otyp' = (extract_array_type typ) in + match otyp' with + | Some typ' -> + let size = Sil.Var(Ident.create_fresh Ident.kfootprint) in + let s = mk_empty_array size in + let hpred = Prop.mk_ptsto n_lexp s (Sil.Sizeof(Sil.Tarray(typ', size), Sil.Subtype.exact)) in + let sigma = Prop.get_sigma prop in + let sigma_fp = Prop.get_sigma_footprint prop in + let prop'= Prop.replace_sigma (hpred:: sigma) prop in + let prop''= Prop.replace_sigma_footprint (hpred:: sigma_fp) prop' in + let prop''= Prop.normalize prop'' in + [(return_result_for_array_size size prop'' ret_ids, path)] + | _ -> [] + end + | _ -> raise (Exceptions.Wrong_argument_number (try assert false with Assert_failure x -> x)) + + let execute___set_array_size cfg pdesc instr tenv _prop path ret_ids args callee_pname loc + : Builtin.ret_typ = + match args, ret_ids with + | [(lexp, typ); (size, _)], [] -> + let n_lexp, _prop' = exp_norm_check_arith pdesc _prop lexp in + let n_size, prop = exp_norm_check_arith pdesc _prop' size in + begin + try + let hpred, sigma' = list_partition (function + | Sil.Hpointsto(e, _, t) -> Sil.exp_equal e n_lexp + | _ -> false) (Prop.get_sigma prop) in + match hpred with + | [Sil.Hpointsto(e, Sil.Earray(_, esel, inst), t)] -> + let hpred' = Sil.Hpointsto (e, Sil.Earray (n_size, esel, inst), t) in + let prop' = Prop.replace_sigma (hpred':: sigma') prop in + [(Prop.normalize prop', path)] + | _ -> raise Not_found + with Not_found -> + match typ with + | Sil.Tptr (typ', _) -> + let size_fp = Sil.Var(Ident.create_fresh Ident.kfootprint) in + let se = mk_empty_array n_size in + let se_fp = mk_empty_array size_fp in + let hpred = Prop.mk_ptsto n_lexp se (Sil.Sizeof(Sil.Tarray(typ', size), Sil.Subtype.exact)) in + let hpred_fp = Prop.mk_ptsto n_lexp se_fp (Sil.Sizeof(Sil.Tarray(typ', size_fp), Sil.Subtype.exact)) in + let sigma = Prop.get_sigma prop in + let sigma_fp = Prop.get_sigma_footprint prop in + let prop'= Prop.replace_sigma (hpred:: sigma) prop in + let prop''= Prop.replace_sigma_footprint (hpred_fp:: sigma_fp) prop' in + let prop''= Prop.normalize prop'' in + [(prop'', path)] + | _ -> [] + end + | _ -> raise (Exceptions.Wrong_argument_number (try assert false with Assert_failure x -> x)) + + let execute___print_value cfg pdesc instr tenv prop path ret_ids args callee_pname loc + : Builtin.ret_typ = + L.err "__print_value: "; + let do_arg (lexp, typ) = + let n_lexp, _ = exp_norm_check_arith pdesc prop lexp in + L.err "%a " (Sil.pp_exp pe_text) n_lexp in + list_iter do_arg args; + L.err "@."; + [(prop, path)] + + let is_undefined_opt prop n_lexp = + match Prop.get_resource_undef_attribute prop n_lexp with + | Some (Sil.Aundef _) -> !Config.angelic_execution || !Config.optimistic_cast + | _ -> false + + (** Creates an object in the heap with a given type, when the object is not known to be null or when it doesn't + appear already in the heap. *) + let create_type tenv n_lexp typ prop = + let prop_type = + try + let _ = list_find (function + | Sil.Hpointsto(e, _, _) -> Sil.exp_equal e n_lexp + | _ -> false) (Prop.get_sigma prop) in + prop + with Not_found -> + let mhpred = + match typ with + | Sil.Tptr (typ', _) -> + let sexp = Sil.Estruct ([], Sil.inst_none) in + let typ'' = Sil.expand_type tenv typ' in + let texp = Sil.Sizeof (typ'', Sil.Subtype.subtypes) in + let hpred = Prop.mk_ptsto n_lexp sexp texp in + Some hpred + | Sil.Tarray (typ', _) -> + let size = Sil.Var(Ident.create_fresh Ident.kfootprint) in + let sexp = mk_empty_array size in + let texp = Sil.Sizeof (typ, Sil.Subtype.subtypes) in + let hpred = Prop.mk_ptsto n_lexp sexp texp in + Some hpred + | _ -> None in + match mhpred with + | Some hpred -> + let sigma = Prop.get_sigma prop in + let sigma_fp = Prop.get_sigma_footprint prop in + let prop'= Prop.replace_sigma (hpred:: sigma) prop in + let prop''= + let has_normal_variables = + Sil.fav_exists (Sil.exp_fav n_lexp) Ident.is_normal in + if (is_undefined_opt prop n_lexp) || has_normal_variables + then prop' + else Prop.replace_sigma_footprint (hpred:: sigma_fp) prop' in + let prop''= Prop.normalize prop'' in + prop'' + | None -> prop in + let sil_is_null = Sil.BinOp (Sil.Eq, n_lexp, (Sil.exp_zero)) in + let sil_is_nonnull = Sil.UnOp(Sil.LNot, sil_is_null, None) in + let null_case = Propset.to_proplist (prune_prop tenv sil_is_null prop) in + let non_null_case = Propset.to_proplist (prune_prop tenv sil_is_nonnull prop_type) in + if ((list_length non_null_case) > 0) && (!Config.footprint) then + non_null_case + else if (is_undefined_opt prop n_lexp) then non_null_case + else null_case@non_null_case + + let execute___get_type_of cfg pdesc instr tenv _prop path ret_ids args callee_pname loc + : Builtin.ret_typ = + match args with + | [(lexp, typ)] when list_length ret_ids <= 1 -> + let n_lexp, prop = exp_norm_check_arith pdesc _prop lexp in + let props = create_type tenv n_lexp typ prop in + let aux prop = + begin + try + let hpred = list_find (function + | Sil.Hpointsto(e, _, _) -> Sil.exp_equal e n_lexp + | _ -> false) (Prop.get_sigma prop) in + match hpred with + | Sil.Hpointsto(e, _, texp) -> + (return_result texp prop ret_ids), path + | _ -> assert false + with Not_found -> (return_result Sil.exp_zero prop ret_ids), path + end in + (list_map aux props) + | _ -> raise (Exceptions.Wrong_argument_number (try assert false with Assert_failure x -> x)) + + (** replace the type of the ptsto rooted at [root_e] with [texp] in [prop] *) + let replace_ptsto_texp prop root_e texp = + let process_sigma sigma = + let sigma1, sigma2 = + list_partition (function + | Sil.Hpointsto(e, _, _) -> Sil.exp_equal e root_e + | _ -> false) sigma in + match sigma1 with + | [Sil.Hpointsto(e, se, _)] -> (Sil.Hpointsto (e, se, texp)) :: sigma2 + | _ -> sigma in + let sigma = Prop.get_sigma prop in + let sigma_fp = Prop.get_sigma_footprint prop in + let prop'= Prop.replace_sigma (process_sigma sigma) prop in + let prop''= Prop.replace_sigma_footprint (process_sigma sigma_fp) prop' in + Prop.normalize prop'' + + let execute___instanceof_cast + cfg pdesc instr tenv _prop path ret_ids args callee_pname loc instof + : Builtin.ret_typ = + match args with + | [(_val1, typ1); (_texp2, typ2)] when list_length ret_ids <= 1 -> + let val1, __prop = exp_norm_check_arith pdesc _prop _val1 in + let texp2, prop = exp_norm_check_arith pdesc __prop _texp2 in + let exe_one_prop prop = + if Sil.exp_equal texp2 Sil.exp_zero then + [(return_result Sil.exp_zero prop ret_ids, path)] + else + begin + try + let hpred = list_find (function + | Sil.Hpointsto(e1, _, _) -> Sil.exp_equal e1 val1 + | _ -> false) (Prop.get_sigma prop) in + match hpred with + | Sil.Hpointsto(_, _, texp1) -> + let pos_type_opt, neg_type_opt = Prover.subtype_case_analysis tenv texp1 texp2 in + let mk_res type_opt res_e = match type_opt with + | None -> [] + | Some texp1' -> + let prop' = + if Sil.exp_equal texp1 texp1' then prop + else replace_ptsto_texp prop val1 texp1' in + [(return_result res_e prop' ret_ids, path)] in + if (instof) then (* instanceof *) + begin + let pos_res = mk_res pos_type_opt Sil.exp_one in + let neg_res = mk_res neg_type_opt Sil.exp_zero in + pos_res @ neg_res + end + else (* cast *) + begin + if (!Config.footprint = true) then + begin + match pos_type_opt with + | None -> + Tabulation.raise_cast_exception + (try assert false with Assert_failure ml_loc -> ml_loc) + None texp1 texp2 val1 + | Some texp1' -> (mk_res pos_type_opt val1) + end + else (* !Config.footprint = false *) + begin + match neg_type_opt with + | Some _ -> + if (is_undefined_opt prop val1) then (mk_res pos_type_opt val1) + else + Tabulation.raise_cast_exception + (try assert false with Assert_failure ml_loc -> ml_loc) + None texp1 texp2 val1 + | None -> (mk_res pos_type_opt val1) + end + end + | _ -> [] + with Not_found -> + [(return_result val1 prop ret_ids, path)] + end in + let props = create_type tenv val1 typ1 prop in + list_flatten (list_map exe_one_prop props) + | _ -> raise (Exceptions.Wrong_argument_number (try assert false with Assert_failure x -> x)) + + let execute___instanceof cfg pdesc instr tenv _prop path ret_ids args callee_pname loc + : Builtin.ret_typ = + (execute___instanceof_cast cfg pdesc instr tenv _prop path ret_ids args callee_pname loc true) + + let execute___cast cfg pdesc instr tenv _prop path ret_ids args callee_pname loc + : Builtin.ret_typ = + (execute___instanceof_cast cfg pdesc instr tenv _prop path ret_ids args callee_pname loc false) + + let set_resource_attribute prop path n_lexp loc ra_res = + let prop' = match Prop.get_resource_undef_attribute prop n_lexp with + | Some (Sil.Aresource (_ as ra)) -> + let check_attr_change att_old att_new = () in + Prop.add_or_replace_exp_attribute check_attr_change prop n_lexp (Sil.Aresource { ra with Sil.ra_res = ra_res }) + | _ -> + ( let pname = Sil.mem_alloc_pname Sil.Mnew in + let ra = { Sil.ra_kind = Sil.Racquire; Sil.ra_res = ra_res; Sil.ra_pname = pname; Sil.ra_loc = loc; Sil.ra_vpath = None } in + let check_attr_change att_old att_new = () in + Prop.add_or_replace_exp_attribute check_attr_change prop n_lexp (Sil.Aresource ra)) in + [(prop', path)] + + (** Set the attibute of the value as file *) + let execute___set_file_attribute cfg pdesc instr tenv _prop path ret_ids args callee_pname loc + : Builtin.ret_typ = + match args, ret_ids with + | [(lexp, typ)], _ -> + let n_lexp, prop = exp_norm_check_arith pdesc _prop lexp in + set_resource_attribute prop path n_lexp loc Sil.Rfile + | _ -> raise (Exceptions.Wrong_argument_number (try assert false with Assert_failure x -> x)) + + (** Set the attibute of the value as lock *) + let execute___set_lock_attribute cfg pdesc instr tenv _prop path ret_ids args callee_pname loc + : Builtin.ret_typ = + match args, ret_ids with + | [(lexp, typ)], _ -> + let n_lexp, prop = exp_norm_check_arith pdesc _prop lexp in + set_resource_attribute prop path n_lexp loc Sil.Rlock + | _ -> raise (Exceptions.Wrong_argument_number (try assert false with Assert_failure x -> x)) + + (** Set the resource attribute of the first real argument of method as ignore, the first argument is assumed to be "this" *) + let execute___method_set_ignore_attribute + cfg pdesc instr tenv _prop path ret_ids args callee_pname loc + : Builtin.ret_typ = + match args, ret_ids with + | [_ ; (lexp, typ)], _ -> + let n_lexp, prop = exp_norm_check_arith pdesc _prop lexp in + set_resource_attribute prop path n_lexp loc Sil.Rignore + | _ -> raise (Exceptions.Wrong_argument_number (try assert false with Assert_failure x -> x)) + + (** Set the attibute of the value as memory *) + let execute___set_mem_attribute cfg pdesc instr tenv _prop path ret_ids args callee_pname loc + : Builtin.ret_typ = + match args, ret_ids with + | [(lexp, typ)], _ -> + let n_lexp, prop = exp_norm_check_arith pdesc _prop lexp in + set_resource_attribute prop path n_lexp loc (Sil.Rmemory Sil.Mnew) + | _ -> raise (Exceptions.Wrong_argument_number (try assert false with Assert_failure x -> x)) + + (** Set the attibute of the value as tainted *) + let execute___set_taint_attribute cfg pdesc instr tenv _prop path ret_ids args callee_name loc + : Builtin.ret_typ = + match args, ret_ids with + | [(lexp, typ)], _ -> + let n_lexp, prop = exp_norm_check_arith pdesc _prop lexp in + let prop' = match Prop.get_taint_attribute prop n_lexp with + | _ -> + let check_attr_change att_old att_new = () in + let p'= Prop.add_or_replace_exp_attribute check_attr_change prop n_lexp (Sil.Ataint) in + p' in + [(prop', path)] + | _ -> raise (Exceptions.Wrong_argument_number (try assert false with Assert_failure x -> x)) + + (** Set the attibute of the value as untainted *) + let execute___set_untaint_attribute cfg pdesc instr tenv _prop path ret_ids args callee_name loc + : Builtin.ret_typ = + match args, ret_ids with + | [(lexp, typ)], _ -> + let n_lexp, prop = exp_norm_check_arith pdesc _prop lexp in + let prop' = match Prop.get_taint_attribute prop n_lexp with + | _ -> + let check_attr_change att_old att_new = () in + let p'= Prop.add_or_replace_exp_attribute check_attr_change prop n_lexp (Sil.Auntaint) in + p' in + [(prop', path)] + | _ -> raise (Exceptions.Wrong_argument_number (try assert false with Assert_failure x -> x)) + + (** take a pointer to a struct, and return the value of a hidden field in the struct *) + let execute___get_hidden_field cfg pdesc instr tenv _prop path ret_ids args callee_name loc + : Builtin.ret_typ = + match args with + | [(lexp, typ)] -> + let n_lexp, prop = exp_norm_check_arith pdesc _prop lexp in + let ret_val = ref None in + let return_val p = match !ret_val with + | Some e -> return_result e p ret_ids + | None -> p in + let foot_var = lazy (Sil.Var (Ident.create_fresh Ident.kfootprint)) in + let filter_fld_hidden (f, _ ) = Ident.fieldname_is_hidden f in + let has_fld_hidden fsel = list_exists filter_fld_hidden fsel in + let do_hpred in_foot hpred = match hpred with + | Sil.Hpointsto(e, Sil.Estruct (fsel, inst), texp) when Sil.exp_equal e n_lexp && (not (has_fld_hidden fsel)) -> + let foot_e = Lazy.force foot_var in + ret_val := Some foot_e; + let se = Sil.Eexp(foot_e, Sil.inst_none) in + let fsel' = (Ident.fieldname_hidden, se) :: fsel in + Sil.Hpointsto(e, Sil.Estruct (fsel', inst), texp) + | Sil.Hpointsto(e, Sil.Estruct (fsel, _), texp) when Sil.exp_equal e n_lexp && not in_foot && has_fld_hidden fsel -> + let set_ret_val () = + match list_find filter_fld_hidden fsel with + | _, Sil.Eexp(e, _) -> ret_val := Some e + | _ -> () in + set_ret_val(); + hpred + | _ -> hpred in + let sigma' = list_map (do_hpred false) (Prop.get_sigma prop) in + let sigma_fp' = list_map (do_hpred true) (Prop.get_sigma_footprint prop) in + let prop' = Prop.replace_sigma_footprint sigma_fp' (Prop.replace_sigma sigma' prop) in + let prop'' = return_val (Prop.normalize prop') in + [(prop'', path)] + | _ -> raise (Exceptions.Wrong_argument_number (try assert false with Assert_failure x -> x)) + + (** take a pointer to a struct and a value, and set a hidden field in the struct to the given value *) + let execute___set_hidden_field cfg pdesc instr tenv _prop path ret_ids args callee_name loc + : Builtin.ret_typ = + match args with + | [(lexp1, typ1); (lexp2, typ2)] -> + let n_lexp1, _prop1 = exp_norm_check_arith pdesc _prop lexp1 in + let n_lexp2, prop = exp_norm_check_arith pdesc _prop1 lexp2 in + let foot_var = lazy (Sil.Var (Ident.create_fresh Ident.kfootprint)) in + let filter_fld_hidden (f, _ ) = Ident.fieldname_is_hidden f in + let has_fld_hidden fsel = list_exists filter_fld_hidden fsel in + let do_hpred in_foot hpred = match hpred with + | Sil.Hpointsto(e, Sil.Estruct (fsel, inst), texp) when Sil.exp_equal e n_lexp1 && not in_foot -> + let se = Sil.Eexp(n_lexp2, Sil.inst_none) in + let fsel' = (Ident.fieldname_hidden, se) :: (list_filter (fun x -> not (filter_fld_hidden x)) fsel) in + Sil.Hpointsto(e, Sil.Estruct (fsel', inst), texp) + | Sil.Hpointsto(e, Sil.Estruct (fsel, inst), texp) when Sil.exp_equal e n_lexp1 && in_foot && not (has_fld_hidden fsel) -> + let foot_e = Lazy.force foot_var in + let se = Sil.Eexp(foot_e, Sil.inst_none) in + let fsel' = (Ident.fieldname_hidden, se) :: fsel in + Sil.Hpointsto(e, Sil.Estruct (fsel', inst), texp) + | _ -> hpred in + let sigma' = list_map (do_hpred false) (Prop.get_sigma prop) in + let sigma_fp' = list_map (do_hpred true) (Prop.get_sigma_footprint prop) in + let prop' = Prop.replace_sigma_footprint sigma_fp' (Prop.replace_sigma sigma' prop) in + let prop'' = Prop.normalize prop' in + [(prop'', path)] + | _ -> raise (Exceptions.Wrong_argument_number (try assert false with Assert_failure x -> x)) + + let execute___state_untainted cfg pdesc instr tenv _prop path ret_ids args callee_name loc + : Builtin.ret_typ = + match args with + | [(lexp, typ)] when list_length ret_ids <= 1 -> + (match ret_ids with + | [ret_id] -> + let n_lexp, prop = exp_norm_check_arith pdesc _prop lexp in + [(return_result (Sil.BinOp(Sil.Ne, n_lexp, (Sil.Const(Sil.Cattribute((Sil.Auntaint)))))) prop ret_ids, path)] + | _ -> [(_prop, path)]) + | _ -> raise (Exceptions.Wrong_argument_number (try assert false with Assert_failure x -> x)) + + (* Update the objective-c hidden counter by applying the operation op and the operand delta.*) + (* Eg. op=+/- delta is an integer *) + let execute___objc_counter_update + suppress_npe_report op delta cfg pdesc instr tenv _prop path ret_ids args callee_name loc + : Builtin.ret_typ = + match args with + | [(lexp, typ)] -> + let typ' = (match Sil.expand_type tenv typ with + | Sil.Tstruct _ as s -> s + | Sil.Tptr(t, _) -> Sil.expand_type tenv t + | s' -> + L.d_str ("Trying to update hidden field of not a struc. Type: "^(Sil.typ_to_string s')); + assert false) in + (* Assumes that lexp is a temp n$1 that has the value of the object. *) + (* This is the case as a call f(o) it's translates as n$1=*&o; f(n$1) *) + (* n$2 = *n$1.hidden *) + let tmp = Ident.create_fresh Ident.knormal in + let hidden_field = Sil.Lfield(lexp, Ident.fieldname_hidden, typ') in + let counter_to_tmp = Sil.Letderef(tmp, hidden_field, typ', loc) in + (* *n$1.hidden = (n$2 +/- delta) *) + let update_counter = Sil.Set(hidden_field, typ', Sil.BinOp(op, Sil.Var tmp, Sil.Const (Sil.Cint delta)), loc) in + let update_counter_instrs = [counter_to_tmp; update_counter; Sil.Remove_temps([tmp], loc)] in + sym_exec_generated suppress_npe_report cfg tenv pdesc update_counter_instrs [(_prop, path)] + | _ -> raise (Exceptions.Wrong_argument_number (try assert false with Assert_failure x -> x)) + + (* Given a list of args checks if the first is the flag indicating whether is a call to retain/release for which*) + (* we have to suppress NPE report or not. If the flag is present it is removed from the list of args. *) + let get_suppress_npe_flag args = + match args with + | (Sil.Const (Sil.Cint i), Sil.Tint Sil.IBool):: args' when Sil.Int.isone i -> + false, args' (* this is a CFRelease/CFRetain *) + | _ -> true, args + + let execute___objc_retain_impl cfg pdesc instr tenv _prop path ret_ids args callee_name loc + : Builtin.ret_typ = + let suppress_npe_report, args' = get_suppress_npe_flag args in + match args' with + | [(lexp, typ)] -> + let prop = return_result lexp _prop ret_ids in + execute___objc_counter_update suppress_npe_report (Sil.PlusA) (Sil.Int.one) + cfg pdesc instr tenv prop path ret_ids args' callee_name loc + | _ -> raise (Exceptions.Wrong_argument_number (try assert false with Assert_failure x -> x)) + + let execute___objc_retain cfg pdesc instr tenv _prop path ret_ids args callee_name loc + : Builtin.ret_typ = + if !Config.objc_memory_model_on then + execute___objc_retain_impl cfg pdesc instr tenv _prop path ret_ids args callee_name loc + else execute___no_op _prop path + + let execute___objc_retain_cf cfg pdesc instr tenv _prop path ret_ids args callee_name loc + : Builtin.ret_typ = + execute___objc_retain_impl cfg pdesc instr tenv _prop path ret_ids args callee_name loc + + let execute___objc_release_impl cfg pdesc instr tenv _prop path ret_ids args callee_name loc + : Builtin.ret_typ = + let suppress_npe_flag, args' = get_suppress_npe_flag args in + execute___objc_counter_update suppress_npe_flag (Sil.MinusA) (Sil.Int.one) + cfg pdesc instr tenv _prop path ret_ids args' callee_name loc + + let execute___objc_release cfg pdesc instr tenv _prop path ret_ids args callee_name loc + : Builtin.ret_typ = + if !Config.objc_memory_model_on then + execute___objc_release_impl cfg pdesc instr tenv _prop path ret_ids args callee_name loc + else execute___no_op _prop path + + let execute___objc_release_cf cfg pdesc instr tenv _prop path ret_ids args callee_name loc + : Builtin.ret_typ = + execute___objc_release_impl cfg pdesc instr tenv _prop path ret_ids args callee_name loc + + (** Set the attibute of the value as objc autoreleased *) + let execute___set_autorelease_attribute + cfg pdesc instr tenv _prop path ret_ids args callee_pname loc + : Builtin.ret_typ = + match args, ret_ids with + | [(lexp, typ)], _ -> + let prop = return_result lexp _prop ret_ids in + if !Config.objc_memory_model_on then + let n_lexp, prop = exp_norm_check_arith pdesc prop lexp in + let check_attr_change att_old att_new = () in + let prop' = Prop.add_or_replace_exp_attribute check_attr_change prop n_lexp Sil.Aautorelease in + [(prop', path)] + else execute___no_op prop path + | _ -> raise (Exceptions.Wrong_argument_number (try assert false with Assert_failure x -> x)) + + (** Release all the objects in the pool *) + let execute___release_autorelease_pool + cfg pdesc instr tenv _prop path ret_ids args callee_pname loc + : Builtin.ret_typ = + if !Config.objc_memory_model_on then + let autoreleased_objects = Prop.get_atoms_with_attribute Sil.Aautorelease _prop in + let prop = Prop.remove_attribute Sil.Aautorelease _prop in + let call_release res exp = + match res with + | (prop, path):: _ -> + (try + let hpred = list_find (function + | Sil.Hpointsto(e1, _, _) -> Sil.exp_equal e1 exp + | _ -> false) (Prop.get_sigma _prop) in + match hpred with + | Sil.Hpointsto(_, _, Sil.Sizeof (typ, st)) -> + let res1 = + execute___objc_release cfg pdesc instr tenv prop path ret_ids + [(exp, typ)] callee_pname loc in + res1 + | _ -> res + with Not_found -> res) + | [] -> res in + list_fold_left call_release [(prop, path)] autoreleased_objects + else execute___no_op _prop path + + let execute___objc_cast cfg pdesc instr tenv _prop path ret_ids args callee_pname loc + : Builtin.ret_typ = + match args with + | [(_val1, typ1); (_texp2, typ2)] when list_length ret_ids <= 1 -> + let val1, __prop = exp_norm_check_arith pdesc _prop _val1 in + let texp2, prop = exp_norm_check_arith pdesc __prop _texp2 in + (try + let hpred = list_find (function + | Sil.Hpointsto(e1, _, _) -> Sil.exp_equal e1 val1 + | _ -> false) (Prop.get_sigma prop) in + match hpred, texp2 with + | Sil.Hpointsto(val1, _, texp1), Sil.Sizeof (typ, st) -> + let prop' = replace_ptsto_texp prop val1 texp2 in + [(return_result val1 prop' ret_ids, path)] + | _ -> [(return_result val1 prop ret_ids, path)] + with Not_found -> [(return_result val1 prop ret_ids, path)]) + | _ -> raise (Exceptions.Wrong_argument_number (try assert false with Assert_failure x -> x)) + + let execute_abort cfg pdesc instr tenv prop path ret_ids args callee_pname loc + : Builtin.ret_typ = + raise (Exceptions.Precondition_not_found (Localise.verbatim_desc (Procname.to_string callee_pname), try assert false with Assert_failure x -> x)) + + let execute_exit cfg pdesc instr tenv prop path ret_ids args callee_pname loc + : Builtin.ret_typ = + execute_diverge prop path + + let _execute_free tenv mk loc acc iter = + match Prop.prop_iter_current iter with + | (Sil.Hpointsto(lexp, se, _), []) -> + let prop = Prop.prop_iter_remove_curr_then_to_prop iter in + let pname = Sil.mem_dealloc_pname mk in + let ra = { Sil.ra_kind = Sil.Rrelease; Sil.ra_res = Sil.Rmemory mk; Sil.ra_pname = pname; Sil.ra_loc = loc; Sil.ra_vpath = None } in + let p_res = + Prop.add_or_replace_exp_attribute Tabulation.check_attr_dealloc_mismatch prop lexp (Sil.Aresource ra) in (* mark value as freed *) + p_res :: acc + | (Sil.Hpointsto _, o :: os) -> assert false (* alignment error *) + | _ -> assert false (* should not happen *) + + let _execute_free_nonzero mk pdesc tenv instr prop path lexp typ loc = + try + begin + match Prover.is_root prop lexp lexp with + | None -> + L.d_strln ".... Alignment Error: Freed a non root ...."; + assert false + | Some _ -> + let prop_list = + list_fold_left (_execute_free tenv mk loc) [] + (Rearrange.rearrange pdesc tenv lexp typ prop loc) in + list_rev prop_list + end + with Rearrange.ARRAY_ACCESS -> + if (!Config.array_level = 0) then assert false + else begin + L.d_strln ".... Array containing allocated heap cells ...."; + L.d_str " Instr: "; Sil.d_instr instr; L.d_ln (); + L.d_str " PROP: "; Prop.d_prop prop; L.d_ln (); + raise (Exceptions.Array_of_pointsto (try assert false with Assert_failure x -> x)) + end + + let execute_free mk cfg pdesc instr tenv _prop path ret_ids args callee_pname loc + : Builtin.ret_typ = + match args with + | [(lexp, typ)] -> + begin + let n_lexp, prop = exp_norm_check_arith pdesc _prop lexp in + let prop_nonzero = (* case n_lexp!=0 *) + Propset.to_proplist (prune_polarity tenv true n_lexp prop) in + let prop_zero = (* case n_lexp==0 *) + Propset.to_proplist (prune_polarity tenv false n_lexp prop) in + let plist = + prop_zero @ (* model: if 0 then skip else _execute_free_nonzero *) + list_flatten (list_map (fun p -> + _execute_free_nonzero mk pdesc tenv instr p path + (Prop.exp_normalize_prop p lexp) typ loc) prop_nonzero) in + list_map (fun p -> (p, path)) plist + end + | _ -> raise (Exceptions.Wrong_argument_number (try assert false with Assert_failure x -> x)) + + let execute_alloc mk can_return_null cfg pdesc instr tenv _prop path ret_ids args callee_pname loc + : Builtin.ret_typ = + let rec evaluate_char_sizeof e = match e with + | Sil.Var _ -> e + | Sil.UnOp (uop, e', typ) -> + Sil.UnOp (uop, evaluate_char_sizeof e', typ) + | Sil.BinOp (bop, e1', e2') -> + Sil.BinOp (bop, evaluate_char_sizeof e1', evaluate_char_sizeof e2') + | Sil.Const _ | Sil.Cast _ | Sil.Lvar _ | Sil.Lfield _ | Sil.Lindex _ -> e + | Sil.Sizeof (Sil.Tarray(Sil.Tint ik, size), _) when Sil.ikind_is_char ik -> + evaluate_char_sizeof size + | Sil.Sizeof _ -> e in + let handle_sizeof_exp size_exp = + Sil.Sizeof (Sil.Tarray (Sil.Tint Sil.IChar, size_exp), Sil.Subtype.exact) in + let size_exp = match args with + | [(size_exp, _)] -> (* for malloc and __new *) + size_exp + | [(num_obj, _); (base_exp, _)] -> (* for __new_array *) + Sil.BinOp (Sil.Mult, num_obj, base_exp) + | _ -> + raise (Exceptions.Wrong_argument_number (try assert false with Assert_failure x -> x)) in + let ret_id = match ret_ids with + | [ret_id] -> ret_id + | _ -> Ident.create_fresh Ident.kprimed in + let size_exp', prop = + let n_size_exp, prop = exp_norm_check_arith pdesc _prop size_exp in + let n_size_exp' = evaluate_char_sizeof n_size_exp in + Prop.exp_normalize_prop prop n_size_exp', prop in + let cnt_te = handle_sizeof_exp size_exp' in + let id_new = Ident.create_fresh Ident.kprimed in + let exp_new = Sil.Var id_new in + let ptsto_new = + Prop.mk_ptsto_exp (Some tenv) Prop.Fld_init (exp_new, cnt_te, None) Sil.Ialloc in + let prop_plus_ptsto = + let pname = Sil.mem_alloc_pname mk in + let prop' = Prop.normalize (Prop.prop_sigma_star prop [ptsto_new]) in + let ra = { Sil.ra_kind = Sil.Racquire; Sil.ra_res = Sil.Rmemory mk; Sil.ra_pname = pname; Sil.ra_loc = loc; Sil.ra_vpath = None } in + let check_attr_change att_old att_new = () in + Prop.add_or_replace_exp_attribute check_attr_change prop' exp_new (Sil.Aresource ra) in (* mark value as allocated *) + let prop_alloc = Prop.conjoin_eq (Sil.Var ret_id) exp_new prop_plus_ptsto in + if can_return_null then + let prop_null = Prop.conjoin_eq (Sil.Var ret_id) Sil.exp_zero prop in + [(prop_alloc, path); (prop_null, path)] + else [(prop_alloc, path)] + + let execute_pthread_create cfg pdesc instr tenv prop path ret_ids args callee_pname loc + : Builtin.ret_typ = + match args with + | [thread; attr; start_routine; arg] -> + let routine_name = Prop.exp_normalize_prop prop (fst start_routine) in + let routine_arg = Prop.exp_normalize_prop prop (fst arg) in + (match routine_name, (snd start_routine) with + | Sil.Lvar pvar, _ -> + let fun_name = Sil.pvar_get_name pvar in + let fun_string = Mangled.to_string fun_name in + let callee_pdesc = + match Cfg.Procdesc.find_from_name cfg (Procname.from_string fun_string) with + | Some callee_pdesc -> callee_pdesc + | None -> assert false in + L.d_strln ("pthread_create: calling function " ^ fun_string); + sym_exe_call + cfg pdesc tenv prop path ret_ids [(routine_arg, snd arg)] callee_pdesc loc + | _ -> + L.d_str "pthread_create: unknown function "; Sil.d_exp routine_name; L.d_strln ", skipping call."; + [(prop, path)]) + | _ -> raise (Exceptions.Wrong_argument_number (try assert false with Assert_failure x -> x)) + + let execute_skip cfg pdesc instr tenv prop path ret_ids args callee_pname loc : Builtin.ret_typ = + [(prop, path)] + + let execute_scan_function + skip_n_arguments cfg pdesc instr tenv prop path ret_ids args callee_pname loc + : Builtin.ret_typ = + match args with + | _ when list_length args >= skip_n_arguments -> + let varargs = ref args in + for i = 1 to skip_n_arguments do varargs := list_tl !varargs done; + call_unknown_or_scan true cfg pdesc tenv prop path ret_ids None !varargs callee_pname loc + | _ -> raise (Exceptions.Wrong_argument_number (try assert false with Assert_failure x -> x)) + + let execute__unwrap_exception cfg pdesc instr tenv _prop path ret_ids args callee_pname loc + : Builtin.ret_typ = + match args with + | [(ret_exn, _)] -> + begin + let n_ret_exn, prop = exp_norm_check_arith pdesc _prop ret_exn in + match n_ret_exn with + | Sil.Const (Sil.Cexn exp) -> + let prop_with_exn = return_result exp prop ret_ids in + [(prop_with_exn, path)] + | _ -> assert false + end + | _ -> raise (Exceptions.Wrong_argument_number (try assert false with Assert_failure x -> x)) + + let execute_return_first_argument cfg pdesc instr tenv _prop path ret_ids args callee_pname loc + : Builtin.ret_typ = + match args with + | (_arg1, _):: _ -> + let arg1, prop = exp_norm_check_arith pdesc _prop _arg1 in + let prop' = return_result arg1 prop ret_ids in + [(prop', path)] + | _ -> raise (Exceptions.Wrong_argument_number (try assert false with Assert_failure x -> x)) + + let execute___split_get_nth cfg pdesc instr tenv _prop path ret_ids args callee_pname loc + : Builtin.ret_typ = + match args with + | [(lexp1, _); (lexp2, _); (lexp3, _)] -> + let n_lexp1, prop = exp_norm_check_arith pdesc _prop lexp1 in + let n_lexp2, prop = exp_norm_check_arith pdesc _prop lexp2 in + let n_lexp3, prop = exp_norm_check_arith pdesc _prop lexp3 in + (match n_lexp1, n_lexp2, n_lexp3 with + | Sil.Const (Sil.Cstr str1), Sil.Const (Sil.Cstr str2), Sil.Const (Sil.Cint n_sil) -> + (let n = Sil.Int.to_int n_sil in + try + let parts = Str.split (Str.regexp_string str2) str1 in + let n_part = list_nth parts n in + let res = Sil.Const (Sil.Cstr n_part) in + [(return_result res prop ret_ids, path)] + with Not_found -> assert false) + | _ -> [(prop, path)]) + | _ -> raise (Exceptions.Wrong_argument_number (try assert false with Assert_failure x -> x)) + + let execute___create_tuple cfg pdesc instr tenv prop path ret_ids args callee_pname loc + : Builtin.ret_typ = + let el = list_map fst args in + let res = Sil.Const (Sil.Ctuple el) in + [(return_result res prop ret_ids, path)] + + let execute___tuple_get_nth cfg pdesc instr tenv _prop path ret_ids args callee_pname loc + : Builtin.ret_typ = + match args with + | [(lexp1, _); (lexp2, _)] -> + let n_lexp1, _prop' = exp_norm_check_arith pdesc _prop lexp1 in + let n_lexp2, prop = exp_norm_check_arith pdesc _prop' lexp2 in + (match n_lexp1, n_lexp2 with + | Sil.Const (Sil.Ctuple el), Sil.Const (Sil.Cint i) -> + let n = Sil.Int.to_int i in + let en = list_nth el n in + [(return_result en prop ret_ids, path)] + | _ -> [(prop, path)]) + | _ -> raise (Exceptions.Wrong_argument_number (try assert false with Assert_failure x -> x)) + + (* forces the expression passed as parameter to be assumed true at the point where this + builtin is called *) + let execute___infer_assume + cfg pdesc instr tenv prop path ret_ids args callee_pname loc: Builtin.ret_typ = + match args with + | [(lexp, typ)] -> + let prop_assume = Prop.conjoin_eq lexp (Sil.exp_bool true) prop in + [(prop_assume, path)] + | _ -> raise (Exceptions.Wrong_argument_number (try assert false with Assert_failure x -> x)) + + (* creates a named error state *) + let execute___infer_fail cfg pdesc instr tenv prop path ret_ids args callee_pname loc + : Builtin.ret_typ = + let error_str = + match args with + | [(lexp_msg, _)] -> + begin + match Prop.exp_normalize_prop prop lexp_msg with + | Sil.Const (Sil.Cstr str) -> str + | _ -> assert false + end + | _ -> + raise (Exceptions.Wrong_argument_number (try assert false with Assert_failure x -> x)) in + let set_instr = + Sil.Set (Sil.Lvar Sil.global_error, Sil.Tvoid, Sil.Const (Sil.Cstr error_str), loc) in + sym_exec_generated true cfg tenv pdesc [set_instr] [(prop, path)] + + (* translate builtin assertion failure *) + let execute___assert_fail cfg pdesc instr tenv prop path ret_ids args callee_pname loc + : Builtin.ret_typ = + let error_str = + match args with + | l when list_length l = 4 -> + Config.default_failure_name + | _ -> + raise (Exceptions.Wrong_argument_number (try assert false with Assert_failure x -> x)) in + let set_instr = + Sil.Set (Sil.Lvar Sil.global_error, Sil.Tvoid, Sil.Const (Sil.Cstr error_str), loc) in + sym_exec_generated true cfg tenv pdesc [set_instr] [(prop, path)] + + (** match any function whose plain name is shared_ptr, and set the ignore attribute *) + (* let _ = Builtin.register_plain "shared_ptr" execute___method_set_ignore_attribute *) + + let _ = Builtin.register "__method_set_ignore_attribute" execute___method_set_ignore_attribute + let _ = Builtin.register "__builtin_va_arg" execute___builtin_va_arg (* model va_arg *) + let _ = Builtin.register "__builtin_va_copy" execute_skip (** NOTE: __builtin_va_copy should have been handled in the translation already (see frontend.ml) *) + let _ = Builtin.register "__builtin_va_end" execute_skip (* model va_end as skip *) + let _ = Builtin.register "__builtin_va_start" execute_skip (** NOTE: __builtin_va_start should have been handled in the translation already (see frontend.ml) *) + let __create_tuple = Builtin.register "__create_tuple" execute___create_tuple (* create a tuple value from the arguments *) + let __delete = Builtin.register "__delete" (execute_free Sil.Mnew) (* like free *) + let __delete_array = Builtin.register "__delete_array" (execute_free Sil.Mnew_array) (* like free *) + let __exit = Builtin.register "_exit" execute_exit (* _exit from C library *) + let __get_array_size = Builtin.register "__get_array_size" execute___get_array_size (* return the size of the array passed as a parameter *) + let _ = Builtin.register "__get_hidden_field" execute___get_hidden_field (* return the value of a hidden field in the struct *) + let __get_type_of = Builtin.register "__get_type_of" execute___get_type_of (* return the get the type of the allocated object passed as a parameter *) + let _ = Builtin.register "__infer_set_flag" execute_skip (** NOTE: __infer_set_flag should have been handled in the translation already (see frontend.ml) *) + let __instanceof = Builtin.register "__instanceof" execute___instanceof (** [__instanceof(val,typ)] implements java's [val instanceof typ] *) + let __cast = Builtin.register "__cast" execute___cast (** [__cast(val,typ)] implements java's [typ(val)] *) + let __new = Builtin.register "__new" (execute_alloc Sil.Mnew false) (* like malloc, but always succeeds *) + let __new_array = Builtin.register "__new_array" (execute_alloc Sil.Mnew_array false) (* like malloc, but always succeeds *) + let __objc_alloc = Builtin.register "__objc_alloc" (execute_alloc Sil.Mobjc true) (* Objective C alloc *) + let __objc_alloc_no_fail = Builtin.register "__objc_alloc_no_fail" (execute_alloc Sil.Mobjc false) (* like __objc_alloc, but does not return nil *) + let __placement_delete = Builtin.register "__placement_delete" execute_skip (* placement delete is skip *) + let __placement_new = Builtin.register "__placement_new" execute_return_first_argument (* placement new returns the first argument *) + let _ = Builtin.register "__print_value" execute___print_value (* print a value as seen by the engine *) + let __set_array_size = Builtin.register "__set_array_size" execute___set_array_size (* set the size of the array passed as a parameter *) + let __set_file_attribute = Builtin.register "__set_file_attribute" execute___set_file_attribute (* set the attribute of the parameter as file *) + let __set_lock_attribute = Builtin.register "__set_lock_attribute" execute___set_lock_attribute (* set the attribute of the parameter as file *) + let __set_mem_attribute = Builtin.register "__set_mem_attribute" execute___set_mem_attribute (* set the attribute of the parameter as memory *) + let __set_autorelease_attribute = Builtin.register "__set_autorelease_attribute" execute___set_autorelease_attribute (* set the attribute of the parameter as autorelease *) + let __objc_release_autorelease_pool = Builtin.register "__objc_release_autorelease_pool" execute___release_autorelease_pool (* set the attribute of the parameter as autorelease *) + let __split_get_nth = Builtin.register "__split_get_nth" execute___split_get_nth (* splits a string given a separator and returns the nth string *) + + (* builtin function to externally create new errors *) + let __infer_fail = Builtin.register "__infer_fail" execute___infer_fail + let __assert_fail = Builtin.register "__assert_fail" execute___assert_fail + + let _ = Builtin.register "__set_hidden_field" execute___set_hidden_field (* set a hidden field in the struct to the given value *) + let _ = Builtin.register "__set_taint_attribute" execute___set_taint_attribute (* set the attribute of the parameter as tainted *) + let _ = Builtin.register "__set_untaint_attribute" execute___set_untaint_attribute (* set the attribute of the parameter as tainted *) + let _ = Builtin.register "__state_untainted" execute___state_untainted (* check if the parameter is tainted *) + let __objc_retain = Builtin.register "__objc_retain" execute___objc_retain (* objective-c "retain" *) + let __objc_release = Builtin.register "__objc_release" execute___objc_release (* objective-c "release" *) + let __objc_retain_cf = Builtin.register "__objc_retain_cf" execute___objc_retain_cf (* objective-c "retain" *) + let __objc_release_cf = Builtin.register "__objc_release_cf" execute___objc_release_cf (* objective-c "release" *) + let __objc_cast = Builtin.register "__objc_cast" execute___objc_cast (* objective-c "cast" *) + let _ = Builtin.register "__throw" execute_skip (** NOTE: __throw should have been handled in the translation already (see frontend.ml) *) + let __tuple_get_nth = Builtin.register "__tuple_get_nth" execute___tuple_get_nth (* return the nth element of a tuple *) + let __unwrap_exception = Builtin.register "__unwrap_exception" execute__unwrap_exception (* the correct function to unwrapp execption remains to be written *) + let __infer_assume = Builtin.register "__infer_assume" execute___infer_assume + let _ = Builtin.register "abort" execute_abort (* abort from C library *) + let _ = Builtin.register "exit" execute_exit (* exit from C library *) + let _ = Builtin.register "free" (execute_free Sil.Mmalloc) (* free from C library, requires allocated memory *) + let _ = Builtin.register "fscanf" (execute_scan_function 2) (* fscanf from C library *) + let _ = Builtin.register "fwscanf" (execute_scan_function 2) (* vsscanf from C library *) + let _ = Builtin.register "malloc" (execute_alloc Sil.Mmalloc true) (* malloc from C library *) + let malloc_no_fail = Builtin.register "malloc_no_fail" (execute_alloc Sil.Mmalloc false) (* malloc from ObjC library *) + let _ = Builtin.register "pthread_create" execute_pthread_create (* register execution handler for pthread_create *) + let _ = Builtin.register "scanf" (execute_scan_function 1) (* scanf from C library *) + let _ = Builtin.register "sscanf" (execute_scan_function 2) (* sscanf from C library *) + let _ = Builtin.register "swscanf" (execute_scan_function 2) (* vsscanf from C library *) + let _ = Builtin.register "vfscanf" (execute_scan_function 2) (* vfwscanf from C library *) + let _ = Builtin.register "vfwscanf" (execute_scan_function 2) (* vsscanf from C library *) + let _ = Builtin.register "vscanf" (execute_scan_function 1) (* vscanf from C library *) + let _ = Builtin.register "vsscanf" (execute_scan_function 2) (* vsscanf from C library *) + let _ = Builtin.register "vswscanf" (execute_scan_function 2) (* vsscanf from C library *) + let _ = Builtin.register "vwscanf" (execute_scan_function 1) (* vsscanf from C library *) + let _ = Builtin.register "wscanf" (execute_scan_function 1) (* vsscanf from C library *) + + let execute_NSArray_arrayWithObjects_count cfg pdesc instr tenv prop path ret_ids args callee_pname loc = + (** like list_map but applies [g] to the last element instead of [f] *) + let rec list_map_except_last f g = function + | [] -> [] + | a::[] -> g a::[] + | x:: l -> f x:: list_map_except_last f g l in + (** simulates a Letderef for each argument *) + let create_letderef (lexp, typ) = + let tmp_id = Ident.create_fresh Ident.kprimed in + Sil.Letderef (tmp_id, lexp, typ, loc) in + (** allocates an object *) + let create_alloc _ = + let alloc_fun = Sil.Const (Sil.Cfun __objc_alloc_no_fail) in + let nsarray_typ = Sil.Tvar (Sil.TN_csu (Sil.Class, Mangled.from_string "NSArray")) in + let nsarray_typ = Sil.expand_type tenv nsarray_typ in + let nsarray_ptr_typ = Sil.Tptr (nsarray_typ, Sil.Pk_pointer) in + let sizeof_nsarray = Sil.Sizeof (nsarray_typ, Sil.Subtype.exact) in + Sil.Call (ret_ids, alloc_fun, [sizeof_nsarray, nsarray_ptr_typ], loc, Sil.cf_default) in + (* OPTIM: take advantage of the fact that [args] has an extra nil + argument we don't care about to replace it with our final + instruction *) + let instrs = list_map_except_last create_letderef create_alloc args in + sym_exec_generated false cfg tenv pdesc instrs [(prop, path)] + + let objc_nsarray_arrayWithObjects = Procname.mangled_objc "NSArray" "arrayWithObjects:" + + let execute_NSArray_arrayWithObjects cfg pdesc instr tenv prop path ret_ids args callee_pname loc = + (* tag args to trigger a custom error in Rearrange.rearrange *) + let n = list_length args in + (** add our attribute to [arg] (which is argument #[i] of the + * function) in [prop] and accumulate the added attributes *) + let tag (prop, added_attrs, i) arg = + (* skip last element *) + if i <> n - 1 then + let attr = + Sil.Avariadic_function_argument (objc_nsarray_arrayWithObjects, list_length args, i) in + L.err "tagging: %s\n" (Sil.exp_to_string (fst arg)); + let dummy_check_attr_change att_old att_new = () in + let tagged_prop = + Prop.add_or_replace_exp_attribute dummy_check_attr_change prop (fst arg) attr in + (tagged_prop, attr:: added_attrs, i + 1) + else (prop, added_attrs, i) in + let (prop, added_attrs, _) = list_fold_left tag (prop, [], 0) args in + let result = + execute_NSArray_arrayWithObjects_count cfg pdesc instr tenv prop path ret_ids args callee_pname loc in + + (* remove the tags we just added to temporarily instrument the Letderefs *) + let remove_attrs (prop, path) = + let remove_attr prop tag = Prop.remove_attribute tag prop in + (list_fold_left remove_attr prop added_attrs, path) in + list_map remove_attrs result + + let _ = + Builtin.register_procname + (Procname.mangled_objc "NSArray" "arrayWithObjects:count:") + execute_NSArray_arrayWithObjects_count + let _ = Builtin.register_procname objc_nsarray_arrayWithObjects execute_NSArray_arrayWithObjects +end +(* ============== END of ModelBuiltins ============== *) diff --git a/infer/src/backend/symExec.mli b/infer/src/backend/symExec.mli new file mode 100644 index 000000000..32495e627 --- /dev/null +++ b/infer/src/backend/symExec.mli @@ -0,0 +1,55 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Symbolic Execution *) + +(** print the builtin functions and exit *) +val print_builtins : unit -> unit + +(** Check if the function is a builtin *) +val function_is_builtin : Procname.t -> bool + +(** symbolic execution on the level of sets of propositions *) +val lifted_sym_exec : (exn -> unit) -> Cfg.cfg -> Sil.tenv -> Cfg.Procdesc.t -> +Paths.PathSet.t -> Cfg.Node.t -> Sil.instr list -> Paths.PathSet.t + +(** OO method resolution: given a class name and a method name, climb the class hierarchy to find +* the procname that the method name will actually resolve to at runtime. For example, if we have +* a procname like Foo.toString() and Foo does not override toString(), we must resolve the call to +* toString(). We will end up with Super.toString() where Super is some superclass of Foo. *) +val resolve_method : Sil.tenv -> Mangled.t -> Procname.t -> Procname.t +(** {2 Functions for handling builtins } *) + +module ModelBuiltins : sig + val __assert_fail : Procname.t + val __delete : Procname.t + val __delete_array : Procname.t + val __exit : Procname.t + val __get_array_size : Procname.t + val __get_type_of : Procname.t + val __infer_fail : Procname.t + val __instanceof : Procname.t (** [__instanceof(val,typ)] implements java's [val instanceof typ] *) + val __cast : Procname.t (** [__cast(val,typ)] implements java's [typ(val)] *) + val __placement_delete : Procname.t + val __placement_new : Procname.t + val __new : Procname.t + val __new_array : Procname.t + val __objc_alloc : Procname.t + val __objc_alloc_no_fail : Procname.t + val __set_array_size : Procname.t + val __unwrap_exception : Procname.t + val __set_file_attribute : Procname.t + val __set_mem_attribute : Procname.t + val __infer_assume : Procname.t + val __objc_retain : Procname.t + val __objc_release : Procname.t + val __objc_retain_cf : Procname.t + val __objc_release_cf : Procname.t + val __set_autorelease_attribute : Procname.t + val __objc_release_autorelease_pool : Procname.t + val __objc_cast : Procname.t + val malloc_no_fail : Procname.t +end diff --git a/infer/src/backend/tabulation.ml b/infer/src/backend/tabulation.ml new file mode 100644 index 000000000..6933d1a78 --- /dev/null +++ b/infer/src/backend/tabulation.ml @@ -0,0 +1,1096 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Interprocedural footprint analysis *) + +module L = Logging +module F = Format +open Utils + +type splitting = { + sub: Sil.subst; + frame: Sil.hpred list; + missing_pi: Sil.atom list; + missing_sigma: Sil.hpred list; + frame_fld : Sil.hpred list; + missing_fld : Sil.hpred list; + frame_typ : (Sil.exp * Sil.exp) list; + missing_typ : (Sil.exp * Sil.exp) list; +} + +type deref_error = + | Deref_freed of Sil.res_action (** dereference a freed pointer *) + | Deref_minusone (** dereference -1 *) + | Deref_null of Sil.path_pos (** dereference null *) + | Deref_undef of Procname.t * Sil.location * Sil.path_pos (** dereference a value coming from the given undefined function *) + +type invalid_res = + | Dereference_error of deref_error * Localise.error_desc * Paths.Path.t option (** dereference error and description *) + | Prover_checks of Prover.check list (** the abduction prover failed some checks *) + | Cannot_combine (** cannot combine actual pre with splitting and post *) + | Missing_fld_not_empty (** missing_fld not empty in re-execution mode *) + | Missing_sigma_not_empty (** missing sigma not empty in re-execution mode *) + +type valid_res = + { incons_pre_missing : bool; (** whether the actual pre is consistent with the missing part *) + vr_pi: Sil.atom list; (** missing pi *) + vr_sigma: Sil.hpred list; (** missing sigma *) + vr_cons_res : (Prop.normal Prop.t * Paths.Path.t) list; (** consistent result props *) + vr_incons_res : (Prop.normal Prop.t * Paths.Path.t) list; (** inconsistent result props *) } + +(** Result of (bi)-abduction on a single spec. +A result is invalid if no splitting was found, or if combine failed, or if we are in re - execution mode and the sigma +part of the splitting is not empty. +A valid result contains the missing pi ans sigma, as well as the resulting props. *) +type abduction_res = + | Valid_res of valid_res (** valid result for a function cal *) + | Invalid_res of invalid_res (** reason for invalid result *) + +(**************** printing functions ****************) +let d_splitting split = + L.d_strln "Actual splitting"; + L.d_increase_indent 1; + L.d_strln "------------------------------------------------------------"; + L.d_strln "SUB = "; Prop.d_sub split.sub; L.d_ln (); + L.d_strln "FRAME ="; Prop.d_sigma split.frame; L.d_ln (); + L.d_strln "MISSING ="; Prop.d_pi_sigma split.missing_pi split.missing_sigma; L.d_ln (); + L.d_strln "FRAME FLD = "; Prop.d_sigma split.frame_fld; L.d_ln (); + L.d_strln "MISSING FLD = "; Prop.d_sigma split.missing_fld; L.d_ln (); + if split.frame_typ <> [] then L.d_strln "FRAME TYP = "; Prover.d_typings split.frame_typ; L.d_ln (); + if split.missing_typ <> [] then L.d_strln "MISSING TYP = "; Prover.d_typings split.missing_typ; L.d_ln (); + L.d_strln "------------------------------------------------------------"; + L.d_decrease_indent 1 + +let print_results actual_pre results = + L.d_strln "***** RESULTS FUNCTION CALL *******"; + Propset.d actual_pre (Propset.from_proplist results); + L.d_strln "***** END RESULTS FUNCTION CALL *******" +(***************) + +(** Rename the variables in the spec. *) +let spec_rename_vars pname spec = + let prop_add_callee_suffix p = + let f = function + | Sil.Lvar pv -> + Sil.Lvar (Sil.pvar_to_callee pname pv) + | e -> e in + Prop.prop_expmap f p in + let jprop_add_callee_suffix = function + | Specs.Jprop.Prop (n, p) -> Specs.Jprop.Prop (n, prop_add_callee_suffix p) + | Specs.Jprop.Joined (n, p, jp1, jp2) -> Specs.Jprop.Joined (n, prop_add_callee_suffix p, jp1, jp2) in + let fav = Sil.fav_new () in + Specs.Jprop.fav_add fav spec.Specs.pre; + list_iter (fun (p, path) -> Prop.prop_fav_add fav p) spec.Specs.posts; + let ids = Sil.fav_to_list fav in + let ids' = list_map (fun i -> (i, Ident.create_fresh Ident.kprimed)) ids in + let ren_sub = Sil.sub_of_list (list_map (fun (i, i') -> (i, Sil.Var i')) ids') in + let pre' = Specs.Jprop.jprop_sub ren_sub spec.Specs.pre in + let posts' = list_map (fun (p, path) -> (Prop.prop_sub ren_sub p, path)) spec.Specs.posts in + let pre'' = jprop_add_callee_suffix pre' in + let posts'' = list_map (fun (p, path) -> (prop_add_callee_suffix p, path)) posts' in + { Specs.pre = pre''; Specs.posts = posts''; Specs.visited = spec.Specs.visited } + +(** Find and number the specs for [proc_name], after renaming their vars, and also return the parameters *) +let spec_find_rename trace_call (proc_name : Procname.t) : (int * Prop.exposed Specs.spec) list * Sil.pvar list = + try + let count = ref 0 in + let f spec = + incr count; (!count, spec_rename_vars proc_name spec) in + let specs, formals = Specs.get_specs_formals proc_name in + if specs == [] then + begin + trace_call Specs.CallStats.CR_not_found; + raise (Exceptions.Precondition_not_found (Localise.verbatim_desc (Procname.to_string proc_name), try assert false with Assert_failure x -> x)) + end; + let formal_parameters = + list_map (fun (x, _) -> Sil.mk_pvar_callee (Mangled.from_string x) proc_name) formals in + list_map f specs, formal_parameters + with Not_found -> begin + L.d_strln ("ERROR: found no entry for procedure " ^ Procname.to_string proc_name ^ ". Give up..."); + raise (Exceptions.Precondition_not_found (Localise.verbatim_desc (Procname.to_string proc_name), try assert false with Assert_failure x -> x)) + end + +(** Process a splitting coming straight from a call to the prover: +change the instantiating substitution so that it returns primed vars, +except for vars occurring in the missing part, where it returns +footprint vars. *) +let process_splitting actual_pre sub1 sub2 frame missing_pi missing_sigma frame_fld missing_fld frame_typ missing_typ = + (* + let check_precondition () = + let dom1 = Sil.sub_domain sub1 in + let rng1 = Sil.sub_range sub1 in + let dom2 = Sil.sub_domain sub2 in + let rng2 = Sil.sub_range sub2 in + let overlap = list_exists (fun id -> list_exists (Ident.equal id) dom1) dom2 in + if overlap then begin + L.d_str "Dom(Sub1): "; Sil.d_exp_list (list_map (fun id -> Sil.Var id) dom1); L.d_ln (); + L.d_str "Ran(Sub1): "; Sil.d_exp_list rng1; L.d_ln (); + L.d_str "Dom(Sub2): "; Sil.d_exp_list (list_map (fun id -> Sil.Var id) dom2); L.d_ln (); + L.d_str "Ran(Sub2): "; Sil.d_exp_list rng2; L.d_ln (); + assert false + end in + check_precondition (); + *) + let sub = Sil.sub_join sub1 sub2 in + + let sub1_inverse = + let sub1_list = Sil.sub_to_list sub1 in + let sub1_list' = list_filter (function (_, Sil.Var _) -> true | _ -> false) sub1_list in + let sub1_inverse_list = list_map (function (id, Sil.Var id') -> (id', Sil.Var id) | _ -> assert false) sub1_list' + in Sil.sub_of_list_duplicates sub1_inverse_list in + let fav_actual_pre = + let fav_sub2 = (* vars which represent expansions of fields *) + let fav = Sil.fav_new () in + list_iter (Sil.exp_fav_add fav) (Sil.sub_range sub2); + let filter id = Ident.get_stamp id = - 1 in + Sil.fav_filter_ident fav filter; + fav in + let fav_pre = Prop.prop_fav actual_pre in + Sil.ident_list_fav_add (Sil.fav_to_list fav_sub2) fav_pre; + fav_pre in + + let fav_missing = Prop.sigma_fav (Prop.sigma_sub sub missing_sigma) in + Prop.pi_fav_add fav_missing (Prop.pi_sub sub missing_pi); + let fav_missing_primed = + let filter id = Ident.is_primed id && not (Sil.fav_mem fav_actual_pre id) + in Sil.fav_copy_filter_ident fav_missing filter in + let fav_missing_fld = Prop.sigma_fav (Prop.sigma_sub sub missing_fld) in + + let map_var_to_pre_var_or_fresh id = + match Sil.exp_sub sub1_inverse (Sil.Var id) with + | Sil.Var id' -> + if Sil.fav_mem fav_actual_pre id' || Ident.is_path id' (** a path id represents a position in the pre *) + then Sil.Var id' + else Sil.Var (Ident.create_fresh Ident.kprimed) + | _ -> assert false in + + let sub_list = Sil.sub_to_list sub in + let fav_sub_list = + let fav_sub = Sil.fav_new () in + list_iter (fun (_, e) -> Sil.exp_fav_add fav_sub e) sub_list; + Sil.fav_to_list fav_sub in + let sub1 = + let f id = + if Sil.fav_mem fav_actual_pre id then (id, Sil.Var id) + else if Ident.is_normal id then (id, map_var_to_pre_var_or_fresh id) + else if Sil.fav_mem fav_missing_fld id then (id, Sil.Var id) + else if Ident.is_footprint id then (id, Sil.Var id) + else begin + let dom1 = Sil.sub_domain sub1 in + let rng1 = Sil.sub_range sub1 in + let dom2 = Sil.sub_domain sub2 in + let rng2 = Sil.sub_range sub2 in + let vars_actual_pre = list_map (fun id -> Sil.Var id) (Sil.fav_to_list fav_actual_pre) in + L.d_str "fav_actual_pre: "; Sil.d_exp_list vars_actual_pre; L.d_ln (); + L.d_str "Dom(Sub1): "; Sil.d_exp_list (list_map (fun id -> Sil.Var id) dom1); L.d_ln (); + L.d_str "Ran(Sub1): "; Sil.d_exp_list rng1; L.d_ln (); + L.d_str "Dom(Sub2): "; Sil.d_exp_list (list_map (fun id -> Sil.Var id) dom2); L.d_ln (); + L.d_str "Ran(Sub2): "; Sil.d_exp_list rng2; L.d_ln (); + L.d_str "Don't know about id: "; Sil.d_exp (Sil.Var id); L.d_ln (); + assert false; + end + in Sil.sub_of_list (list_map f fav_sub_list) in + let sub2_list = + let f id = (id, Sil.Var (Ident.create_fresh Ident.kfootprint)) + in list_map f (Sil.fav_to_list fav_missing_primed) in + let sub_list' = + list_map (fun (id, e) -> (id, Sil.exp_sub sub1 e)) sub_list in + let sub' = Sil.sub_of_list (sub2_list @ sub_list') in + { sub = sub'; frame = frame; missing_pi = missing_pi; missing_sigma = missing_sigma; frame_fld = frame_fld; missing_fld = missing_fld; frame_typ = frame_typ; missing_typ = missing_typ } + +(** Check whether an inst represents a dereference without null check, and return the line number and path position *) +let find_dereference_without_null_check_in_inst = function + | Sil.Iupdate (Some true, _, n, pos) + | Sil.Irearrange (Some true, _, n, pos) -> Some (n, pos) + | _ -> None + +(** Check whether a sexp contains a dereference without null check, and return the line number and path position *) +let rec find_dereference_without_null_check_in_sexp = function + | Sil.Eexp (_, inst) -> find_dereference_without_null_check_in_inst inst + | Sil.Estruct (fsel, inst) -> + let res = find_dereference_without_null_check_in_inst inst in + if res = None then + find_dereference_without_null_check_in_sexp_list (list_map snd fsel) + else res + | Sil.Earray (_, esel, inst) -> + let res = find_dereference_without_null_check_in_inst inst in + if res = None then + find_dereference_without_null_check_in_sexp_list (list_map snd esel) + else res +and find_dereference_without_null_check_in_sexp_list = function + | [] -> None + | se:: sel -> + (match find_dereference_without_null_check_in_sexp se with + | None -> find_dereference_without_null_check_in_sexp_list sel + | Some x -> Some x) + +(** Check dereferences implicit in the spec pre. +In case of dereference error, return [Some(deref_error, description)], otherwise [None] *) +let check_dereferences callee_pname actual_pre sub spec_pre formal_params = + let check_dereference e sexp = + let e_sub = Sil.exp_sub sub e in + let desc use_buckets deref_str = + let error_desc = + Errdesc.explain_dereference_as_caller_expression + ~use_buckets + deref_str actual_pre spec_pre e (State.get_node ()) (State.get_loc ()) formal_params in + (L.d_strln_color Red) "found error in dereference"; + L.d_strln "spec_pre:"; Prop.d_prop spec_pre; L.d_ln(); + L.d_str "exp "; Sil.d_exp e; L.d_strln (" desc: " ^ (pp_to_string Localise.pp_error_desc error_desc)); + error_desc in + let deref_no_null_check_pos = + if Sil.exp_equal e_sub Sil.exp_zero then + match find_dereference_without_null_check_in_sexp sexp with + | Some (_, pos) -> Some pos + | None -> None + else None in + if deref_no_null_check_pos != None + then (* only report a dereference null error if we know there was a dereference without null check *) + match deref_no_null_check_pos with + | Some pos -> Some (Deref_null pos, desc true (Localise.deref_str_null (Some callee_pname))) + | None -> assert false + else if Sil.exp_equal e_sub Sil.exp_minus_one then Some (Deref_minusone, desc true (Localise.deref_str_dangling None)) + else match Prop.get_resource_undef_attribute actual_pre e_sub with + | Some (Sil.Aundef (s, loc, pos)) -> + Some (Deref_undef (s, loc, pos), desc false (Localise.deref_str_undef (s, loc))) + | Some (Sil.Aresource ({ Sil.ra_kind = Sil.Rrelease } as ra)) -> + Some (Deref_freed ra, desc true (Localise.deref_str_freed ra)) + | _ -> None in + let check_hpred = function + | Sil.Hpointsto (lexp, se, _) -> + check_dereference (Sil.root_of_lexp lexp) se + | _ -> None in + let deref_err_list = list_fold_left (fun deref_errs hpred -> match check_hpred hpred with + | Some reason -> reason :: deref_errs + | None -> deref_errs + ) [] (Prop.get_sigma spec_pre) in + match deref_err_list with + | [] -> None + | deref_err :: _ -> + if !Config.angelic_execution then + (* In angelic mode, prefer to report Deref_null over other kinds of deref errors. this + * makes sure we report a NULL_DEREFERENCE instead of a less interesting PRECONDITION_NOT_MET + * whenever possible *) + (* TOOD (t4893533): use this trick outside of angelic mode and in other parts of the code *) + Some + (try + list_find + (fun err -> match err with + | (Deref_null _, _) -> true + | _ -> false ) + deref_err_list + with Not_found -> deref_err) + else Some deref_err + +let post_process_sigma (sigma: Sil.hpred list) loc : Sil.hpred list = + let map_inst inst = Sil.inst_new_loc loc inst in + let do_hpred (_, _, hpred) = Sil.hpred_instmap map_inst hpred in (** update the location of instrumentations *) + list_map (fun hpred -> do_hpred (Prover.expand_hpred_pointer false hpred)) sigma + +(** check for interprocedural path errors in the post *) +let check_path_errors_in_post caller_pname post post_path = + let check_attr (e, att) = match att with + | Sil.Adiv0 path_pos -> + if Prover.check_zero e then + let desc = Errdesc.explain_divide_by_zero e (State.get_node ()) (State.get_loc ()) in + let new_path, path_pos_opt = + let current_path, _ = State.get_path () in + if Paths.Path.contains_position post_path path_pos + then post_path, Some path_pos + else current_path, None in (* position not found, only use the path up to the callee *) + State.set_path new_path path_pos_opt; + let exn = Exceptions.Divide_by_zero (desc, try assert false with Assert_failure x -> x) in + let pre_opt = State.get_normalized_pre (fun te p -> p) (* Abs.abstract_no_symop *) in + Reporting.log_warning caller_pname ~pre: pre_opt exn + | _ -> () in + list_iter check_attr (Prop.get_all_attributes post) + +(** Post process the instantiated post after the function call so that +x.f |-> se becomes x |-> \{ f: se \}. +Also, update any Aresource attributes to refer to the caller *) +let post_process_post + caller_pname callee_pname loc actual_pre ((post: Prop.exposed Prop.t), post_path) = + let actual_pre_has_freed_attribute e = match Prop.get_resource_undef_attribute actual_pre e with + | Some (Sil.Aresource ({ Sil.ra_kind = Sil.Rrelease })) -> true + | _ -> false in + let atom_update_alloc_attribute = function + | Sil.Aneq (e , Sil.Const (Sil.Cattribute (Sil.Aresource ({ Sil.ra_res = res } as ra)))) + | Sil.Aneq (Sil.Const (Sil.Cattribute (Sil.Aresource ({ Sil.ra_res = res } as ra))), e) + when not (ra.Sil.ra_kind = Sil.Rrelease && actual_pre_has_freed_attribute e) -> (* unless it was already freed before the call *) + let vpath, _ = Errdesc.vpath_find post e in + let ra' = { ra with Sil.ra_pname = callee_pname; Sil.ra_loc = loc; Sil.ra_vpath = vpath } in + let c = Sil.Const (Sil.Cattribute (Sil.Aresource ra')) in + Sil.Aneq (e, c) + | a -> a in + let prop' = Prop.replace_sigma (post_process_sigma (Prop.get_sigma post) loc) post in + let pi' = list_map atom_update_alloc_attribute (Prop.get_pi prop') in (* update alloc attributes to refer to the caller *) + let post' = Prop.replace_pi pi' prop' in + check_path_errors_in_post caller_pname post' post_path; + post', post_path + +let hpred_has_only_footprint_vars hpred = + let fav = Sil.fav_new () in + Sil.hpred_fav_add fav hpred; + Sil.fav_for_all fav Ident.is_footprint + +let hpred_lhs_compare hpred1 hpred2 = match hpred1, hpred2 with + | Sil.Hpointsto(e1, _, _), Sil.Hpointsto(e2, _, _) -> Sil.exp_compare e1 e2 + | Sil.Hpointsto _, _ -> - 1 + | _, Sil.Hpointsto _ -> 1 + | hpred1, hpred2 -> Sil.hpred_compare hpred1 hpred2 + +(** set the inst everywhere in a sexp *) +let rec sexp_set_inst inst = function + | Sil.Eexp (e, _) -> + Sil.Eexp (e, inst) + | Sil.Estruct (fsel, _) -> + Sil.Estruct ((list_map (fun (f, se) -> (f, sexp_set_inst inst se)) fsel), inst) + | Sil.Earray (size, esel, _) -> + Sil.Earray (size, list_map (fun (e, se) -> (e, sexp_set_inst inst se)) esel, inst) + +let rec fsel_star_fld fsel1 fsel2 = match fsel1, fsel2 with + | [], fsel2 -> fsel2 + | fsel1,[] -> fsel1 + | (f1, se1):: fsel1', (f2, se2):: fsel2' -> + (match Ident.fieldname_compare f1 f2 with + | 0 -> (f1, sexp_star_fld se1 se2) :: fsel_star_fld fsel1' fsel2' + | n when n < 0 -> (f1, se1) :: fsel_star_fld fsel1' fsel2 + | _ -> (f2, se2) :: fsel_star_fld fsel1 fsel2') + +and array_content_star se1 se2 = + try sexp_star_fld se1 se2 with + | exn when exn_not_timeout exn -> se1 (* let postcondition override *) + +and esel_star_fld esel1 esel2 = match esel1, esel2 with + | [], esel2 -> (* don't know whether element is read or written in fun call with array *) + list_map (fun (e, se) -> (e, sexp_set_inst Sil.Inone se)) esel2 + | esel1,[] -> esel1 + | (e1, se1):: esel1', (e2, se2):: esel2' -> + (match Sil.exp_compare e1 e2 with + | 0 -> (e1, array_content_star se1 se2) :: esel_star_fld esel1' esel2' + | n when n < 0 -> (e1, se1) :: esel_star_fld esel1' esel2 + | _ -> + let se2' = sexp_set_inst Sil.Inone se2 in (* don't know whether element is read or written in fun call with array *) + (e2, se2') :: esel_star_fld esel1 esel2') + +and sexp_star_fld se1 se2 : Sil.strexp = + (* L.d_str "sexp_star_fld "; Sil.d_sexp se1; L.d_str " "; Sil.d_sexp se2; L.d_ln (); *) + match se1, se2 with + | Sil.Estruct (fsel1, _), Sil.Estruct (fsel2, inst2) -> + Sil.Estruct (fsel_star_fld fsel1 fsel2, inst2) + | Sil.Earray (size1, esel1, _), Sil.Earray (size2, esel2, inst2) -> + Sil.Earray (size1, esel_star_fld esel1 esel2, inst2) + | Sil.Eexp (e1, inst1), Sil.Earray (size2, esel2, _) -> + let esel1 = [(Sil.exp_zero, se1)] in + Sil.Earray (size2, esel_star_fld esel1 esel2, inst1) + | _ -> + L.d_str "cannot star "; + Sil.d_sexp se1; L.d_str " and "; Sil.d_sexp se2; + L.d_ln (); + assert false + +let texp_star texp1 texp2 = + let rec ftal_sub ftal1 ftal2 = match ftal1, ftal2 with + | [], _ -> true + | _, [] -> false + | (f1, t1, a1):: ftal1', (f2, t2, a2):: ftal2' -> + begin match Ident.fieldname_compare f1 f2 with + | n when n < 0 -> false + | 0 -> ftal_sub ftal1' ftal2' + | _ -> ftal_sub ftal1 ftal2' end in + let rec typ_star t1 t2 = match t1, t2 with + | Sil.Tstruct (ftal1, sftal1, csu1, _, _, _, _), Sil.Tstruct (ftal2, sftal2, csu2, _, _, _, _) when csu1 = csu2 -> + if ftal_sub ftal1 ftal2 then t2 else t1 + | _ -> t1 in + match texp1, texp2 with + | Sil.Sizeof (t1, st1), Sil.Sizeof (t2, st2) -> Sil.Sizeof (typ_star t1 t2, Sil.Subtype.join st1 st2) + | _ -> texp1 + +let hpred_star_fld (hpred1 : Sil.hpred) (hpred2 : Sil.hpred) : Sil.hpred = + match hpred1, hpred2 with + | Sil.Hpointsto(e1, se1, t1), Sil.Hpointsto(_, se2, t2) -> + (* L.d_str "hpred_star_fld t1: "; Sil.d_texp_full t1; L.d_str " t2: "; Sil.d_texp_full t2; + L.d_str " se1: "; Sil.d_sexp se1; L.d_str " se2: "; Sil.d_sexp se2; L.d_ln (); *) + Sil.Hpointsto(e1, sexp_star_fld se1 se2, texp_star t1 t2) + | _ -> assert false + +(** Implementation of [*] for the field-splitting model *) +let sigma_star_fld (sigma1 : Sil.hpred list) (sigma2 : Sil.hpred list) : Sil.hpred list = + let sigma1 = list_stable_sort hpred_lhs_compare sigma1 in + let sigma2 = list_stable_sort hpred_lhs_compare sigma2 in + (* L.out "@.@. computing %a@.STAR @.%a@.@." pp_sigma sigma1 pp_sigma sigma2; *) + let rec star sg1 sg2 : Sil.hpred list = + match sg1, sg2 with + | [], sigma2 -> [] + | sigma1,[] -> sigma1 + | hpred1:: sigma1', hpred2:: sigma2' -> + begin + match hpred_lhs_compare hpred1 hpred2 with + | 0 -> hpred_star_fld hpred1 hpred2 :: star sigma1' sigma2' + | n when n < 0 -> hpred1 :: star sigma1' sg2 + | _ -> star sg1 sigma2' + end + in + try star sigma1 sigma2 + with exn when exn_not_timeout exn -> + L.d_str "cannot star "; + Prop.d_sigma sigma1; L.d_str " and "; Prop.d_sigma sigma2; + L.d_ln (); + raise (Prop.Cannot_star (try assert false with Assert_failure x -> x)) + +let hpred_typing_lhs_compare hpred1 (e2, te2) = match hpred1 with + | Sil.Hpointsto(e1, _, _) -> Sil.exp_compare e1 e2 + | _ -> - 1 + +let hpred_star_typing (hpred1 : Sil.hpred) (e2, te2) : Sil.hpred = + match hpred1 with + | Sil.Hpointsto(e1, se1, te1) -> Sil.Hpointsto (e1, se1, te2) + | _ -> assert false + +(** Implementation of [*] between predicates and typings *) +let sigma_star_typ (sigma1 : Sil.hpred list) (typings2 : (Sil.exp * Sil.exp) list) : Sil.hpred list = + if !Config.Experiment.activate_subtyping_in_cpp || !Sil.curr_language = Sil.Java then + begin + let typing_lhs_compare (e1, _) (e2, _) = Sil.exp_compare e1 e2 in + let sigma1 = list_stable_sort hpred_lhs_compare sigma1 in + let typings2 = list_stable_sort typing_lhs_compare typings2 in + let rec star sg1 typ2 : Sil.hpred list = + match sg1, typ2 with + | [], _ -> [] + | sigma1,[] -> sigma1 + | hpred1:: sigma1', typing2:: typings2' -> + begin + match hpred_typing_lhs_compare hpred1 typing2 with + | 0 -> hpred_star_typing hpred1 typing2 :: star sigma1' typings2' + | n when n < 0 -> hpred1 :: star sigma1' typ2 + | _ -> star sg1 typings2' + end in + try star sigma1 typings2 + with exn when exn_not_timeout exn -> + L.d_str "cannot star "; + Prop.d_sigma sigma1; L.d_str " and "; Prover.d_typings typings2; + L.d_ln (); + raise (Prop.Cannot_star (try assert false with Assert_failure x -> x)) + end + else sigma1 + +(** [prop_footprint_add_pi_sigma_starfld_sigma prop pi sigma missing_fld] extends the footprint of [prop] with [pi,sigma] and extends the fields of |-> with [missing_fld] *) +let prop_footprint_add_pi_sigma_starfld_sigma (prop : 'a Prop.t) pi_new sigma_new missing_fld missing_typ : Prop.normal Prop.t option = + let rec extend_sigma current_sigma new_sigma = match new_sigma with + | [] -> Some current_sigma + | hpred :: new_sigma' -> + let fav = Prop.sigma_fav [hpred] in + (* TODO (t4893479): make this check less angelic *) + if Sil.fav_exists fav + (fun id -> not (Ident.is_footprint id) && not !Config.angelic_execution) + then begin + L.d_warning "found hpred with non-footprint variable, dropping the spec"; L.d_ln (); Sil.d_hpred hpred; L.d_ln (); + None + end + else extend_sigma (hpred :: current_sigma) new_sigma' in + let rec extend_pi current_pi new_pi = match new_pi with + | [] -> current_pi + | a :: new_pi' -> + let fav = Prop.pi_fav [a] in + if Sil.fav_exists fav (fun id -> not (Ident.is_footprint id)) + then begin + L.d_warning "dropping atom with non-footprint variable"; L.d_ln (); Sil.d_atom a; L.d_ln (); + extend_pi current_pi new_pi' + end + else extend_pi (a :: current_pi) new_pi' in + let foot_pi' = extend_pi (Prop.get_pi_footprint prop) pi_new in + match extend_sigma (Prop.get_sigma_footprint prop) sigma_new with + | None -> None + | Some sigma' -> + let foot_sigma' = sigma_star_fld sigma' missing_fld in + let foot_sigma'' = sigma_star_typ foot_sigma' missing_typ in + let pi' = pi_new @ Prop.get_pi prop in + let prop' = Prop.replace_sigma_footprint foot_sigma'' (Prop.replace_pi_footprint foot_pi' prop) in + let prop'' = Prop.replace_pi pi' prop' in + Some (Prop.normalize prop'') + +(** Check if the attribute change is a mismatch between a kind of allocation and a different kind of deallocation *) +let check_attr_dealloc_mismatch att_old att_new = match att_old, att_new with + | Sil.Aresource ({ Sil.ra_kind = Sil.Racquire; Sil.ra_res = Sil.Rmemory mk_old } as ra_old), + Sil.Aresource ({ Sil.ra_kind = Sil.Rrelease; Sil.ra_res = Sil.Rmemory mk_new } as ra_new) + when Sil.mem_kind_compare mk_old mk_new <> 0 -> + let desc = Errdesc.explain_allocation_mismatch ra_old ra_new in + raise (Exceptions.Deallocation_mismatch (desc, try assert false with Assert_failure x -> x)) + | _ -> () + +(** [prop_copy_footprint p1 p2] copies the footprint and pure part of [p1] into [p2] *) +let prop_copy_footprint_pure p1 p2 = + let p2' = Prop.replace_sigma_footprint (Prop.get_sigma_footprint p1) (Prop.replace_pi_footprint (Prop.get_pi_footprint p1) p2) in + let pi2 = Prop.get_pi p2' in + let pi2_attr, pi2_noattr = list_partition Prop.atom_is_attribute pi2 in + let res_noattr = Prop.replace_pi (Prop.get_pure p1 @ pi2_noattr) p2' in + let replace_attr prop atom = (* call replace_atom_attribute which deals with existing attibutes *) + Prop.replace_atom_attribute check_attr_dealloc_mismatch prop atom in + list_fold_left replace_attr (Prop.normalize res_noattr) pi2_attr + +(** check if an expression is an exception *) +let exp_is_exn = function + | Sil.Const Sil.Cexn _ -> true + | _ -> false + +(** check if a prop is an exception *) +let prop_is_exn pdesc prop = + let ret_pvar = Sil.Lvar (Cfg.Procdesc.get_ret_var pdesc) in + let is_exn = function + | Sil.Hpointsto (e1, Sil.Eexp(e2, _), _) when Sil.exp_equal e1 ret_pvar -> + exp_is_exn e2 + | _ -> false in + list_exists is_exn (Prop.get_sigma prop) + +(** when prop is an exception, return the exception name *) +let prop_get_exn_name pdesc prop = + let ret_pvar = Sil.Lvar (Cfg.Procdesc.get_ret_var pdesc) in + let exn_name = ref (Mangled.from_string "") in + let find_exn_name e = + let do_hpred = function + | Sil.Hpointsto (e1, _, Sil.Sizeof(Sil.Tstruct (_, _, _, Some name, _, _, _), _)) when Sil.exp_equal e1 e -> + exn_name := name + | _ -> () in + list_iter do_hpred (Prop.get_sigma prop) in + let find_ret () = + let do_hpred = function + | Sil.Hpointsto (e1, Sil.Eexp(Sil.Const (Sil.Cexn e2), _), _) when Sil.exp_equal e1 ret_pvar -> + find_exn_name e2 + | _ -> () in + list_iter do_hpred (Prop.get_sigma prop) in + find_ret (); + !exn_name + +(** search in prop for some assignment of global errors *) +let lookup_global_errors prop = + let rec search_error = function + | [] -> None + | Sil.Hpointsto (Sil.Lvar var, Sil.Eexp (Sil.Const (Sil.Cstr str), _), _) :: tl + when Sil.pvar_equal var Sil.global_error -> Some (Mangled.from_string str) + | _ :: tl -> search_error tl in + search_error (Prop.get_sigma prop) + +(** set a prop to an exception sexp *) +let prop_set_exn pdesc prop se_exn = + let ret_pvar = Sil.Lvar (Cfg.Procdesc.get_ret_var pdesc) in + let map_hpred = function + | Sil.Hpointsto (e, _, t) when Sil.exp_equal e ret_pvar -> + Sil.Hpointsto(e, se_exn, t) + | hpred -> hpred in + let sigma' = list_map map_hpred (Prop.get_sigma prop) in + Prop.normalize (Prop.replace_sigma sigma' prop) + +(** Include a subtrace for a procedure call if the callee is not a model. *) +let include_subtrace callee_pname = + Specs.get_origin callee_pname <> Specs.Models + +(** combine the spec's post with a splitting and actual precondition *) +let combine + cfg ret_ids (posts: ('a Prop.t * Paths.Path.t) list) + actual_pre path_pre split + caller_pdesc callee_pname loc = + let caller_pname = Cfg.Procdesc.get_proc_name caller_pdesc in + let new_footprint_pi = Prop.pi_sub split.sub split.missing_pi in + let new_footprint_sigma = Prop.sigma_sub split.sub split.missing_sigma in + let new_frame_fld = Prop.sigma_sub split.sub split.frame_fld in + let new_frame_typ = list_map (fun (e, te) -> Sil.exp_sub split.sub e, Sil.exp_sub split.sub te) split.frame_typ in + let new_missing_typ = list_map (fun (e, te) -> Sil.exp_sub split.sub e, Sil.exp_sub split.sub te) split.missing_typ in + let new_missing_fld = + let sigma = Prop.sigma_sub split.sub split.missing_fld in + let filter hpred = + if not (hpred_has_only_footprint_vars hpred) then + begin + L.d_warning "Missing fields hpred has non-footprint vars: "; Sil.d_hpred hpred; L.d_ln (); + false + end + else match hpred with + | Sil.Hpointsto(Sil.Var id, _, _) -> true + | Sil.Hpointsto(Sil.Lvar pvar, _, _) -> Sil.pvar_is_global pvar + | _ -> + L.d_warning "Missing fields in complex pred: "; Sil.d_hpred hpred; L.d_ln (); + false in + list_filter filter sigma in + let instantiated_frame = Prop.sigma_sub split.sub split.frame in + let instantiated_post = + let posts' = + if !Config.footprint && posts = [] + then (* in case of divergence, produce a prop *) + (* with updated footprint and inconsistent current *) + [(Prop.replace_pi [Sil.Aneq (Sil.exp_zero, Sil.exp_zero)] Prop.prop_emp, path_pre)] + else + list_map + (fun (p, path_post) -> + (p, + Paths.Path.add_call + (include_subtrace callee_pname) + path_pre + callee_pname + path_post)) + posts in + list_map + (fun (p, path) -> + (post_process_post + caller_pname callee_pname loc actual_pre (Prop.prop_sub split.sub p, path))) + posts' in + L.d_increase_indent 1; + L.d_strln "New footprint:"; Prop.d_pi_sigma new_footprint_pi new_footprint_sigma; L.d_ln (); + L.d_strln "Frame fld:"; Prop.d_sigma new_frame_fld; L.d_ln (); + if new_frame_typ <> [] then L.d_strln "Frame typ:"; Prover.d_typings new_frame_typ; L.d_ln (); + L.d_strln "Missing fld:"; Prop.d_sigma new_missing_fld; L.d_ln (); + if new_frame_typ <> [] then L.d_strln "Missing typ:"; Prover.d_typings new_missing_typ; L.d_ln (); + L.d_strln "Instantiated frame:"; Prop.d_sigma instantiated_frame; L.d_ln (); + L.d_strln "Instantiated post:"; Propgraph.d_proplist Prop.prop_emp (list_map fst instantiated_post); + L.d_decrease_indent 1; L.d_ln (); + let compute_result post_p = + let post_p' = + let post_sigma = sigma_star_fld (Prop.get_sigma post_p) new_frame_fld in + let post_sigma' = sigma_star_typ post_sigma new_frame_typ in + Prop.replace_sigma post_sigma' post_p in + let post_p1 = Prop.prop_sigma_star (prop_copy_footprint_pure actual_pre post_p') instantiated_frame in + + let handle_null_case_analysis sigma = + let id_assigned_to_null id = + let filter = function + | Sil.Aeq (Sil.Var id', Sil.Const (Sil.Cint i)) -> + Ident.equal id id' && Sil.Int.isnull i + | _ -> false in + list_exists filter new_footprint_pi in + let f (e, inst_opt) = match e, inst_opt with + | Sil.Var id, Some inst when id_assigned_to_null id -> + let inst' = Sil.inst_set_null_case_flag inst in + (e, Some inst') + | _ -> (e, inst_opt) in + Sil.hpred_list_expmap f sigma in + + let post_p2 = + let post_p1_sigma = Prop.get_sigma post_p1 in + let post_p1_sigma' = handle_null_case_analysis post_p1_sigma in + let post_p1' = Prop.replace_sigma post_p1_sigma' post_p1 in + Prop.normalize (Prop.replace_pi (Prop.get_pi post_p1 @ new_footprint_pi) post_p1') in + + let post_p3 = (** replace [result|callee] with an aux variable dedicated to this proc *) + let callee_pdesc = + match Cfg.Procdesc.find_from_name cfg callee_pname with + | Some pd -> pd + | None -> + L.d_strln ("proc_desc not_found for " ^ Procname.to_string callee_pname); + assert false in + let callee_ret_pvar = Sil.Lvar (Sil.pvar_to_callee callee_pname (Cfg.Procdesc.get_ret_var callee_pdesc)) in + match Prop.prop_iter_create post_p2 with + | None -> post_p2 + | Some iter -> + let filter = function + | Sil.Hpointsto (e, se, t) when Sil.exp_equal e callee_ret_pvar -> Some () + | _ -> None in + match Prop.prop_iter_find iter filter with + | None -> post_p2 + | Some iter' -> + match fst (Prop.prop_iter_current iter') with + | Sil.Hpointsto (e, Sil.Eexp (e', inst), t) when exp_is_exn e' -> (* resuls is an exception: set in caller *) + let p = Prop.prop_iter_remove_curr_then_to_prop iter' in + prop_set_exn caller_pdesc p (Sil.Eexp (e', inst)) + | Sil.Hpointsto (e, Sil.Eexp (e', inst), t) when list_length ret_ids = 1 -> + let p = Prop.prop_iter_remove_curr_then_to_prop iter' in + Prop.conjoin_eq e' (Sil.Var (list_hd ret_ids)) p + | Sil.Hpointsto (e, Sil.Estruct (ftl, _), t) + when list_length ftl = list_length ret_ids -> + let rec do_ftl_ids p = function + | [], [] -> p + | (f, Sil.Eexp (e', inst')):: ftl', ret_id:: ret_ids' -> + let p' = Prop.conjoin_eq e' (Sil.Var ret_id) p in + do_ftl_ids p' (ftl', ret_ids') + | _ -> p in + let p = Prop.prop_iter_remove_curr_then_to_prop iter' in + do_ftl_ids p (ftl, ret_ids) + | Sil.Hpointsto (e, _, t) -> (** returning nothing or unexpected sexp, turning into nondet *) + Prop.prop_iter_remove_curr_then_to_prop iter' + | _ -> assert false in + let post_p4 = + if !Config.footprint + then + prop_footprint_add_pi_sigma_starfld_sigma post_p3 new_footprint_pi new_footprint_sigma new_missing_fld new_missing_typ + else Some post_p3 in + post_p4 in + let _results = list_map (fun (p, path) -> (compute_result p, path)) instantiated_post in + if list_exists (fun (x, _) -> x = None) _results then (* at least one combine failed *) + None + else + let results = list_map (function (Some x, path) -> (x, path) | (None, _) -> assert false) _results in + print_results actual_pre (list_map fst results); + Some results + +(** Construct the actual precondition: add to the current state a copy +of the (callee's) formal parameters instantiated with the actual +parameters. *) +let mk_actual_precondition prop actual_params formal_params = + let formals_actuals = + let rec comb fpars apars = match fpars, apars with + | f:: fpars', a:: apars' -> (f, a) :: comb fpars' apars' + | [], _ -> + if apars != [] then + (let str = "more actual pars than formal pars in fun call (" ^ string_of_int (list_length actual_params) ^ " vs " ^ string_of_int (list_length formal_params) ^ ")" in + L.d_warning str; L.d_ln ()); + [] + | _:: _,[] -> raise (Exceptions.Wrong_argument_number (try assert false with Assert_failure x -> x)) in + comb formal_params actual_params in + let mk_instantiation (formal_var, (actual_e, actual_t)) = + Prop.mk_ptsto (Sil.Lvar formal_var) (Sil.Eexp (actual_e, Sil.inst_actual_precondition)) (Sil.Sizeof (actual_t, Sil.Subtype.exact)) in + let instantiated_formals = list_map mk_instantiation formals_actuals in + let actual_pre = Prop.prop_sigma_star prop instantiated_formals in + Prop.normalize actual_pre + +(** Check if actual_pre * missing_footprint |- false *) +let inconsistent_actualpre_missing actual_pre split_opt = + match split_opt with + | Some split -> + let norm_missing_pi = Prop.pi_sub split.sub split.missing_pi in + let norm_missing_sigma = Prop.sigma_sub split.sub split.missing_sigma in + let prop'= Prop.normalize (Prop.prop_sigma_star actual_pre norm_missing_sigma) in + let prop''= list_fold_left Prop.prop_atom_and prop' norm_missing_pi in + Prover.check_inconsistency prop'' + | None -> false + +(* get the taint/untaint info from the pure part*) +let rec get_taint_untaint pi = + match pi with + | [] -> ([],[]) + | Sil.Aneq (e1, e2):: pi' -> + let p = Prop.replace_pi pi Prop.prop_emp in + (match Prop.get_taint_attribute p e1, Prop.get_taint_attribute p e2 with + | Some(Sil.Ataint), _ -> let (t', u') = get_taint_untaint pi' in (e1:: t', u') + | Some(Sil.Auntaint), _ -> let (t', u') = get_taint_untaint pi' in (t', e1:: u') + | _, Some(Sil.Ataint) -> let (t', u') = get_taint_untaint pi' in (e2:: t', u') + | _ , Some(Sil.Auntaint) -> let (t', u') = get_taint_untaint pi' in (t', e2:: u') + | _, _ -> get_taint_untaint pi') + | _ :: pi' -> get_taint_untaint pi' + +(* perform the taint analysis check *) +let do_taint_check caller_pname actual_pre missing_pi missing_sigma sub1 sub2 = + let rec intersection_taint_untaint taint untaint = (* note: return the first element in the intersection*) + match taint with + | [] -> None + | e:: taint' -> if (list_exists (fun e' -> Sil.exp_equal e e') untaint) then (Some e) + else intersection_taint_untaint taint' untaint in + let augmented_actual_pre = Prop.replace_pi ((Prop.get_pi actual_pre) @ missing_pi) actual_pre in + let augmented_actual_pre = Prop.replace_sigma ((Prop.get_sigma actual_pre) @ missing_sigma) augmented_actual_pre in + let sub2_augmented_actual_pre = Prop.prop_sub sub2 augmented_actual_pre in + let taint2, untaint2 = get_taint_untaint (Prop.get_pi sub2_augmented_actual_pre) in + L.d_str "^^^^AUGMENTED ACTUAL PRE2: "; Prop.d_prop sub2_augmented_actual_pre; L.d_ln(); + L.d_str "^^^^TAINT2: "; Sil.d_exp_list taint2; L.d_ln (); + L.d_str "^^^^UNTAINT2: "; Sil.d_exp_list untaint2; L.d_ln (); + match intersection_taint_untaint taint2 untaint2 with + | None -> L.d_str "^^^^^^NO TAINT ERROR" + | Some e -> begin + L.d_str "^^^^^ERROR in TAINT ANALYSIS: "; + let e' = match Errdesc.find_pvar_with_exp sub2_augmented_actual_pre e with + | Some (pv, _) -> Sil.Lvar pv + | None -> e in + let err_desc = Errdesc.explain_tainted_value_reaching_sensitive_function e' (State.get_loc ()) in + let exn = + Exceptions.Tainted_value_reaching_sensitive_function + (err_desc, try assert false with Assert_failure x -> x) in + Reporting.log_warning caller_pname exn + end + +let class_cast_exn pname_opt texp1 texp2 exp ml_location = + let desc = Errdesc.explain_class_cast_exception pname_opt texp1 texp2 exp (State.get_node ()) (State.get_loc ()) in + Exceptions.Class_cast_exception (desc, ml_location) + +let raise_cast_exception ml_location pname_opt texp1 texp2 exp = + let exn = class_cast_exn pname_opt texp1 texp2 exp ml_location in + raise exn + +let get_check_exn check callee_pname loc ml_location = match check with + | Prover.Bounds_check -> + let desc = Localise.desc_precondition_not_met (Some Localise.Pnm_bounds) callee_pname loc in + Exceptions.Precondition_not_met (desc, ml_location) + | Prover.Class_cast_check (texp1, texp2, exp) -> + class_cast_exn (Some callee_pname) texp1 texp2 exp ml_location + +(** Perform symbolic execution for a single spec *) +let exe_spec + tenv cfg ret_ids (n, nspecs) caller_pdesc callee_pname loc prop path_pre + (spec : Prop.exposed Specs.spec) actual_params formal_params : abduction_res = + let caller_pname = Cfg.Procdesc.get_proc_name caller_pdesc in + let posts = + match ret_ids with + | [ret_id] when !Config.idempotent_getters && !Sil.curr_language = Sil.Java -> + (* if we have seen a previous call to the same function, only use specs whose return value + is consistent with constraints on the return value of the previous call w.r.t to nullness. + meant to eliminate false NPE warnings from the common "if (get() != null) get().something()" + pattern *) + let last_call_ret_non_null = + list_exists + (fun (exp, attr) -> + match attr with + | Sil.Aretval pname when Procname.equal callee_pname pname -> + Prover.check_disequal prop exp Sil.exp_zero + | _ -> false) + (Prop.get_all_attributes prop) in + if last_call_ret_non_null then + let returns_null prop = + list_exists + (function + | Sil.Hpointsto (Sil.Lvar pvar, Sil.Eexp (e, _), _) when Sil.pvar_is_return pvar -> + Prover.check_equal (Prop.normalize prop) e Sil.exp_zero + | _ -> false) + (Prop.get_sigma prop) in + list_filter (fun (prop, _) -> not (returns_null prop)) spec.Specs.posts + else spec.Specs.posts + | _ -> spec.Specs.posts in + let actual_pre = mk_actual_precondition prop actual_params formal_params in + let spec_pre = Specs.Jprop.to_prop spec.Specs.pre in + L.d_strln ("EXECUTING SPEC " ^ string_of_int n ^ "/" ^ string_of_int nspecs); + L.d_strln "ACTUAL PRECONDITION ="; + L.d_increase_indent 1; Prop.d_prop actual_pre; L.d_decrease_indent 1; L.d_ln (); + L.d_strln "SPEC ="; + L.d_increase_indent 1; Specs.d_spec spec; L.d_decrease_indent 1; L.d_ln (); + SymOp.pay(); (* pay one symop *) + match Prover.check_implication_for_footprint caller_pname tenv actual_pre spec_pre with + | Prover.ImplFail checks -> Invalid_res (Prover_checks checks) + | Prover.ImplOK (checks, sub1, sub2, frame, missing_pi, missing_sigma, frame_fld, missing_fld, frame_typ, missing_typ) -> + let log_check_exn check = + let exn = get_check_exn check callee_pname loc (try assert false with Assert_failure x -> x) in + Reporting.log_warning caller_pname exn in + let do_split () = + let split = process_splitting actual_pre sub1 sub2 frame missing_pi missing_sigma frame_fld missing_fld frame_typ missing_typ in + d_splitting split; L.d_ln (); + let norm_missing_pi = Prop.pi_sub split.sub split.missing_pi in + let norm_missing_sigma = Prop.sigma_sub split.sub split.missing_sigma in + (split, norm_missing_pi, norm_missing_sigma) in + let report_valid_res split norm_missing_pi norm_missing_sigma = + match combine + cfg ret_ids posts + actual_pre path_pre split + caller_pdesc callee_pname loc with + | None -> Invalid_res Cannot_combine + | Some results -> + let inconsistent_results, consistent_results = + list_partition (fun (p, _) -> Prover.check_inconsistency p) results in + let incons_pre_missing = inconsistent_actualpre_missing actual_pre (Some split) in + Valid_res { incons_pre_missing = incons_pre_missing; + vr_pi = norm_missing_pi; + vr_sigma = norm_missing_sigma; + vr_cons_res = consistent_results; + vr_incons_res = inconsistent_results } in + begin + list_iter log_check_exn checks; + if (!Config.taint_analysis && !Config.developer_mode) then + do_taint_check caller_pname actual_pre missing_pi missing_sigma sub1 sub2; + let subbed_pre = (Prop.prop_sub sub1 actual_pre) in + match check_dereferences callee_pname subbed_pre sub2 spec_pre formal_params with + | Some (Deref_undef _, _) when !Config.angelic_execution -> + let (split, norm_missing_pi, norm_missing_sigma) = do_split () in + report_valid_res split norm_missing_pi norm_missing_sigma + | Some (deref_error, desc) -> + let rec join_paths = function + | [] -> None + | (_, p):: l -> + (match join_paths l with + | None -> Some p + | Some p' -> Some (Paths.Path.join p p')) in + let pjoin = join_paths posts in (* join the paths from the posts *) + Invalid_res (Dereference_error (deref_error, desc, pjoin)) + | None -> + let (split, norm_missing_pi, norm_missing_sigma) = do_split () in + (* check if a missing_fld hpred is about a hidden field *) + let hpred_missing_hidden = function + | Sil.Hpointsto (_, Sil.Estruct ([(fld, _)], _), _) -> Ident.fieldname_is_hidden fld + | _ -> false in + (* missing fields minus hidden fields *) + let missing_fld_nohidden = + list_filter (fun hp -> not (hpred_missing_hidden hp)) missing_fld in + if !Config.footprint = false && norm_missing_sigma != [] then + begin + L.d_strln "Implication error: missing_sigma not empty in re-execution"; + Invalid_res Missing_sigma_not_empty + end + else if !Config.footprint = false && missing_fld_nohidden != [] then + begin + L.d_strln "Implication error: missing_fld not empty in re-execution"; + Invalid_res Missing_fld_not_empty + end + else report_valid_res split norm_missing_pi norm_missing_sigma + end + +let remove_constant_string_class prop = + let filter = function + | Sil.Hpointsto (Sil.Const (Sil.Cstr _ | Sil.Cclass _), _, _) -> false + | _ -> true in + let sigma = list_filter filter (Prop.get_sigma prop) in + let sigmafp = list_filter filter (Prop.get_sigma_footprint prop) in + let prop' = Prop.replace_sigma_footprint sigmafp (Prop.replace_sigma sigma prop) in + Prop.normalize prop' + +(** existentially quantify the path identifier generated by the prover to keep track of expansions of lhs paths +and remove pointsto's whose lhs is a constant string *) +let quantify_path_idents_remove_constant_strings (prop: Prop.normal Prop.t) : Prop.normal Prop.t = + let fav = Prop.prop_fav prop in + Sil.fav_filter_ident fav Ident.is_path; + remove_constant_string_class (Prop.exist_quantify fav prop) + +(** Strengthen the footprint by adding pure facts from the current part *) +let prop_pure_to_footprint (p: 'a Prop.t) : Prop.normal Prop.t = + let is_footprint_atom_not_attribute a = + not (Prop.atom_is_attribute a) + && + let a_fav = Sil.atom_fav a in + Sil.fav_for_all a_fav Ident.is_footprint in + let pure = Prop.get_pure p in + let new_footprint_atoms = list_filter is_footprint_atom_not_attribute pure in + if new_footprint_atoms == [] + then p + else (** add pure fact to footprint *) + Prop.normalize (Prop.replace_pi_footprint (Prop.get_pi_footprint p @ new_footprint_atoms) p) + +(** check whether 0|->- occurs in sigma *) +let sigma_has_null_pointer sigma = + let hpred_null_pointer = function + | Sil.Hpointsto (e, _, _) -> + Sil.exp_equal e Sil.exp_zero + | _ -> false in + list_exists hpred_null_pointer sigma + +(** post-process the raw result of a function call *) +let exe_call_postprocess tenv ret_ids trace_call callee_pname loc initial_prop results = + let filter_valid_res = function + | Invalid_res _ -> false + | Valid_res _ -> true in + let valid_res0, invalid_res0 = + list_partition filter_valid_res results in + let valid_res = + list_map (function Valid_res cr -> cr | Invalid_res _ -> assert false) valid_res0 in + let invalid_res = + list_map (function Valid_res cr -> assert false | Invalid_res ir -> ir) invalid_res0 in + let valid_res_miss_pi, valid_res_no_miss_pi = + list_partition (fun vr -> vr.vr_pi != []) valid_res in + let valid_res_incons_pre_missing, valid_res_cons_pre_missing = + list_partition (fun vr -> vr.incons_pre_missing) valid_res in + let deref_errors = list_filter (function Dereference_error _ -> true | _ -> false) invalid_res in + let print_pi pi = + L.d_str "pi: "; Prop.d_pi pi; L.d_ln () in + let call_desc kind_opt = Localise.desc_precondition_not_met kind_opt callee_pname loc in + let res_with_path_idents = + if !Config.footprint then + begin + if valid_res_cons_pre_missing == [] then (* no valid results where actual pre and missing are consistent *) + begin + if deref_errors <> [] then (* dereference error detected *) + let extend_path path_opt path_pos_opt = match path_opt with + | None -> () + | Some path_post -> + let old_path, _ = State.get_path () in + let new_path = Paths.Path.add_call (include_subtrace callee_pname) old_path callee_pname path_post in + State.set_path new_path path_pos_opt in + match list_hd deref_errors with + | Dereference_error (Deref_minusone, desc, path_opt) -> + trace_call Specs.CallStats.CR_not_met; + extend_path path_opt None; + raise (Exceptions.Dangling_pointer_dereference (Some Sil.DAminusone, desc, try assert false with Assert_failure x -> x)) + | Dereference_error (Deref_null pos, desc, path_opt) -> + trace_call Specs.CallStats.CR_not_met; + extend_path path_opt (Some pos); + if Localise.is_parameter_not_null_checked_desc desc then + raise (Exceptions.Parameter_not_null_checked (desc, try assert false with Assert_failure x -> x)) + else if Localise.is_field_not_null_checked_desc desc then + raise (Exceptions.Field_not_null_checked (desc, try assert false with Assert_failure x -> x)) + else raise (Exceptions.Null_dereference (desc, try assert false with Assert_failure x -> x)) + | Dereference_error (Deref_freed ra, desc, path_opt) -> + trace_call Specs.CallStats.CR_not_met; + extend_path path_opt None; + raise (Exceptions.Use_after_free (desc, try assert false with Assert_failure x -> x)) + | Dereference_error (Deref_undef (s, loc, pos), desc, path_opt) -> + trace_call Specs.CallStats.CR_not_met; + extend_path path_opt (Some pos); + raise (Exceptions.Skip_pointer_dereference (desc, try assert false with Assert_failure x -> x)) + | Prover_checks _ | Cannot_combine | Missing_sigma_not_empty | Missing_fld_not_empty -> + trace_call Specs.CallStats.CR_not_met; + assert false + else (* no dereference error detected *) + let desc = + if list_exists (function Cannot_combine -> true | _ -> false) invalid_res then + call_desc (Some Localise.Pnm_dangling) + else if list_exists (function + | Prover_checks (check :: _) -> + trace_call Specs.CallStats.CR_not_met; + let exn = get_check_exn check callee_pname loc (try assert false with Assert_failure x -> x) in + raise exn + | _ -> false) invalid_res then + call_desc (Some Localise.Pnm_bounds) + else call_desc None in + trace_call Specs.CallStats.CR_not_met; + raise (Exceptions.Precondition_not_met (desc, try assert false with Assert_failure x -> x)) + end + else (* combine the valid results, and store diverging states *) + let process_valid_res vr = + let save_diverging_states () = + if not vr.incons_pre_missing && vr.vr_cons_res = [] then (* no consistent results on one spec: divergence *) + let incons_res = list_map (fun (p, path) -> (prop_pure_to_footprint p, path)) vr.vr_incons_res in + State.add_diverging_states (Paths.PathSet.from_renamed_list incons_res) in + save_diverging_states (); + vr.vr_cons_res in + list_map (fun (p, path) -> (prop_pure_to_footprint p, path)) (list_flatten (list_map process_valid_res valid_res)) + end + else if valid_res_no_miss_pi != [] then + list_flatten (list_map (fun vr -> vr.vr_cons_res) valid_res_no_miss_pi) + else if valid_res_miss_pi == [] then + raise (Exceptions.Precondition_not_met (call_desc None, try assert false with Assert_failure x -> x)) + else + begin + L.d_strln "Missing pure facts for the function call:"; + list_iter print_pi (list_map (fun vr -> vr.vr_pi) valid_res_miss_pi); + match Prover.find_minimum_pure_cover (list_map (fun vr -> (vr.vr_pi, vr.vr_cons_res)) valid_res_miss_pi) with + | None -> + trace_call Specs.CallStats.CR_not_met; + raise (Exceptions.Precondition_not_met (call_desc None, try assert false with Assert_failure x -> x)) + | Some cover -> + L.d_strln "Found minimum cover"; + list_iter print_pi (list_map fst cover); + list_flatten (list_map snd cover) + end in + trace_call Specs.CallStats.CR_success; + let res = + list_map + (fun (p, path) -> (quantify_path_idents_remove_constant_strings p, path)) + res_with_path_idents in + let should_add_ret_attr _ = + let is_likely_getter pn = list_length (Procname.java_get_parameters pn) = 0 in + !Config.idempotent_getters && !Sil.curr_language = Sil.Java && is_likely_getter callee_pname in + match ret_ids with + | [ret_id] when should_add_ret_attr ()-> + (* add attribute to remember what function call a return id came from *) + let ret_var = Sil.Var ret_id in + let mark_id_as_retval (p, path) = + (* check if the retval already has an important resource that should not be overwritten *) + let has_important_resource_attr = + match Prop.get_resource_undef_attribute p ret_var with + | Some (Sil.Aresource ({ Sil.ra_res = Sil.Rfile; })) -> true + | _ -> false in + if has_important_resource_attr then p, path + else + let check_attr_change att_old att_new = () in + let att_retval = Sil.Aretval callee_pname in + Prop.add_or_replace_exp_attribute check_attr_change p ret_var att_retval, path in + list_map mark_id_as_retval res + | _ -> res + +(** Execute the function call and return the list of results with return value *) +let exe_function_call tenv cfg ret_ids caller_pdesc callee_pname loc actual_params prop path = + let caller_pname = Cfg.Procdesc.get_proc_name caller_pdesc in + let trace_call res = + match Specs.get_summary caller_pname with + | None -> () + | Some summary -> + Specs.CallStats.trace + summary.Specs.stats.Specs.call_stats callee_pname loc res !Config.footprint in + let spec_list, formal_params = spec_find_rename trace_call callee_pname in + let nspecs = list_length spec_list in + L.d_strln ("Found " ^ string_of_int nspecs ^ " specs for function " ^ Procname.to_string callee_pname); + L.d_strln ("START EXECUTING SPECS FOR " ^ Procname.to_string callee_pname ^ " from state"); + Prop.d_prop prop; L.d_ln (); + let exe_one_spec (n, spec) = exe_spec tenv cfg ret_ids (n, nspecs) caller_pdesc callee_pname loc prop path spec actual_params formal_params in + let results = list_map exe_one_spec spec_list in + exe_call_postprocess tenv ret_ids trace_call callee_pname loc prop results diff --git a/infer/src/backend/tabulation.mli b/infer/src/backend/tabulation.mli new file mode 100644 index 000000000..21df001e6 --- /dev/null +++ b/infer/src/backend/tabulation.mli @@ -0,0 +1,38 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Interprocedural footprint analysis *) + +(** Frame and anti-frame *) +type splitting + +(** Remove constant string or class from a prop *) +val remove_constant_string_class : 'a Prop.t -> Prop.normal Prop.t + +(** Check if the attribute change is a mismatch between a kind of allocation and a different kind of deallocation *) +val check_attr_dealloc_mismatch : Sil.attribute -> Sil.attribute -> unit + +(** Check whether a sexp contains a dereference without null check, and return the line number and path position *) +val find_dereference_without_null_check_in_sexp : Sil.strexp -> (int * Sil.path_pos) option + +(** raise a cast exception *) +val raise_cast_exception : +Utils.ml_location -> Procname.t option -> Sil.exp -> Sil.exp -> Sil.exp -> 'a + +(** check if a prop is an exception *) +val prop_is_exn : Cfg.Procdesc.t -> 'a Prop.t -> bool + +(** when prop is an exception, return the exception name *) +val prop_get_exn_name : Cfg.Procdesc.t -> 'a Prop.t -> Mangled.t + +(** search in prop contains an error state *) +val lookup_global_errors : 'a Prop.t -> Mangled.t option + +(** Dump a splitting *) +val d_splitting : splitting -> unit + +(** Execute the function call and return the list of results with return value *) +val exe_function_call: Sil.tenv -> Cfg.cfg -> Ident.t list -> Cfg.Procdesc.t -> Procname.t -> Sil.location -> (Sil.exp * Sil.typ) list -> Prop.normal Prop.t -> Paths.Path.t -> (Prop.normal Prop.t * Paths.Path.t) list diff --git a/infer/src/backend/type_prop.ml b/infer/src/backend/type_prop.ml new file mode 100644 index 000000000..a213a47fa --- /dev/null +++ b/infer/src/backend/type_prop.ml @@ -0,0 +1,784 @@ +(* * Copyright (c) 2009-2013 Monoidics ltd. * Copyright (c) 2013- *) +(* Facebook. * All rights reserved. *) + +(* Module for implementing an algorithm for propagating dynamic types. *) + +module L = Logging +open Utils + +let initial_methods = ref Procname.Set.empty + +(* Signature of a module that can be passed as argument to the functor *) +(* Control_Flow below. *) +module type TODO_MAP = +sig + type t + type t' + type ret_t + type map_value + module Set : Set.S with type elt = t + module Map : Map.S with type key = t' + type map = map_value Map.t + type context + type field_context + val collect_items : Exe_env.t -> Cfg.cfg -> Sil.tenv -> t -> map -> + context -> field_context -> context * field_context * map * ret_t list + val to_t : ret_t -> t + val save_items_to_set : bool + val t_to_string : t -> string + val t'_to_string : t' -> string + val map_value_to_string : map_value -> string + val choose_elem : Set.t -> t option -> t +end + +(* Functor for implementating the following generic algorithm: map : Node *) +(* -> Domain TODO subsetof Node Algorithm: 1. Start with an initial TODO *) +(* set. 2. Choose a node (remove it from TODO), update the map. 3. While *) +(* updating the map, add nodes for which the map changed back to TODO. 4. *) +(* Until the set is empty. *) +module Control_flow = +functor (TM : TODO_MAP) -> + struct + + let set_to_string set = + let aux value = print_string ("\n item: \n"^(TM.t_to_string value)) in + TM.Set.iter aux set + + (* The invariant holds: old_element notin todo *) + let rec update_todo exe_env cfg0 tenv old_elem todo (map, items) context field_context = + (* print_endline "\ntodo set: \n"; (set_to_string todo); *) + let element = TM.choose_elem todo old_elem in + let todo = TM.Set.remove element todo in + let context, field_context, map, new_set_items = + TM.collect_items exe_env cfg0 tenv element map context field_context in + let add_to_todo set_item todo = TM.Set.add set_item todo in + let new_set_items' = items @ new_set_items in + let todo' = + if (TM.save_items_to_set) then + let new_set_items'' = list_map TM.to_t new_set_items' in + list_fold_right add_to_todo new_set_items'' todo + else todo in + let items = + if (TM.save_items_to_set) then [] + else new_set_items in + if (TM.Set.is_empty todo') then (context, field_context, map, items) + else update_todo exe_env cfg0 tenv (Some element) todo' (map, items) context field_context + + end + +let get_formals cfg procname = + let pdesc = match Cfg.Procdesc.find_from_name cfg procname with + | Some pdesc -> pdesc + | None -> assert false in + let formals = Cfg.Procdesc.get_formals pdesc in + formals + +(* Module for defining the map to be updated: in this case it is a map *) +(* from procedure names to a set of types for each of the procedure's *) +(* arguments. *) +module Type_map = +struct + type key = Procname.t + + let key_to_string procname = + if Procname.is_constructor procname + then (Procname.java_get_simple_class procname)^"()" + else (Procname.java_get_simple_class procname)^"."^(Procname.java_get_method procname) + + module Map = Procname.Map + + type type_signature = (string * Sil.typ) list + + let rec type_to_string typ = + match typ with + | Sil.Tptr (typ , _) -> type_to_string typ + | Sil.Tstruct (_, _, Sil.Class, Some mangled, _, _, _) + | Sil.Tvar ( Sil.TN_csu (Sil.Class, (mangled))) -> Mangled.to_string mangled + | _ -> Sil.typ_to_string typ + + let string_typ_to_string (s, typ) = + if (s = "this") then None + else Some (s^" -> "^(type_to_string typ)) + + let rec type_signature_to_string list = + match list with + | [] -> "" + | [s, typ] -> + (match string_typ_to_string (s, typ) with + | Some s -> s + | None -> "") + | (s, typ):: rest -> + match string_typ_to_string (s, typ) with + | Some s -> s^", "^(type_signature_to_string rest) + | None -> (type_signature_to_string rest) + + let pair_compare = Utils.pair_compare Pervasives.compare Sil.typ_compare + + module TypeSet = Set.Make(struct + type t = type_signature + let compare = Utils.list_compare pair_compare + end) + + let map_value_to_string set = + let elem_to_string typ s = + let st = type_signature_to_string typ in + if s = "" then "["^st^"]" else (s^" and ["^st^"]") in + (TypeSet.fold elem_to_string set "") + + type map = TypeSet.t Map.t + + type map_value = TypeSet.t + + let find_dyn_types procname map = + try + (Map.find procname map) + with + Not_found -> TypeSet.empty + + let get_set_from_map procname typ_bundle map = + let set = find_dyn_types procname map in + let ext_set = TypeSet.add typ_bundle set in + ext_set + + let add_to_map procname_arg typ map = + let ext_set = get_set_from_map procname_arg typ map in + (Map.add procname_arg ext_set map) + + let add_set_to_map procname_arg set map = + (Map.add procname_arg set map) +end + +(* Module for defining a context to be used in the type propagation *) +(* algorithm. A context is a map from variable names to a stack of types. *) +(* The stack is used to model the types that are added in the different *) +(* blocks. Each item of the stack contains a type for the variable and *) +(* either a content type if the variable is an array or types for paths *) +(* starting from the variable. *) +module Context_map = +struct + type key = Sil.pvar + + let key_to_string key = + Mangled.to_string (Sil.pvar_get_name key) + + module Map = Map.Make (struct + type t = key + let compare = Sil.pvar_compare end) + + type var_kind = + | VarArray of Sil.typ + | VarBasic + + let path_equal p1 p2 = + if (list_length p1) != (list_length p2) then false + else list_for_all2 (fun el1 el2 -> Ident.fieldname_equal el1 el2) p1 p2 + + let typ_to_var_kind typ = + match typ with + | Sil.Tarray (typ, _) + | Sil.Tptr(Sil.Tarray (typ, _), _) -> VarArray typ + | _ -> VarBasic + + let var_kind_to_string (var_kind : var_kind) = + match var_kind with + | VarBasic -> "basic" + | VarArray typ -> "array - content: "^(Sil.typ_to_string typ) + + type level = int + + type map_value = { + var_level : level; + type_stack : (Sil.typ * var_kind * level) Stack.t + } + + type map = map_value Map.t + + let print_stack stack = + let aux (typ, var_kind, level) = + print_endline ( + (string_of_int level)^":"^ + (Sil.typ_to_string typ)^"-"^ + (var_kind_to_string var_kind)) in + Stack.iter aux stack + + let print_map_value map_value = + print_int map_value.var_level; + print_string ":"; + print_stack map_value.type_stack + + let print_map map = + let aux key value = + print_string ((key_to_string key)^"->"); + print_map_value value in + (Map.iter aux map) + + (* Updates the type of a path or adds the path with its type if it *) + (* wasn't there. *) + let update_var_kind path new_typ var_kind = + match var_kind, path with + | VarArray typ, [] -> VarArray new_typ + | _ -> assert false + + (* Returns the type of a path that is in the context, in case that the *) + (* path appears in the context. Otherwise finds the type in the tenv. *) + let get_type_var_kind tenv var_type path var_kind = + match var_kind, path with + | VarArray typ, [] -> typ + | _ -> assert false + + (* Adds a new type for a variable. It replaces the top of the stack if *) + (* the level if the same as the current level, or it adds a new item to *) + (* the stack if it is a new level. *) + let add_type var new_typ curr_level context = + try + let map_value = Map.find var context in + let stack = map_value.type_stack in + let (typ, var_kind, curr_level) = + if map_value.var_level = curr_level then Stack.pop stack + else Stack.top stack in + let _ = Stack.push (new_typ, var_kind, curr_level) stack in + let new_map_value = { map_value with type_stack = stack } in + Map.add var new_map_value context + with Not_found -> + let var_kind = typ_to_var_kind new_typ in + let stack = Stack.create () in + let _ = Stack.push (new_typ, var_kind, curr_level) stack in + let map_value = { var_level = curr_level; type_stack = stack } in + Map.add var map_value context + + (* Adds a type to a path starting from a variable. It replaces the top *) + (* of the stack if the level if the same as the current level, or it *) + (* adds a new item to the stack if it is a new level. *) + let add_type_content var path new_typ curr_level context = + try + let map_value = Map.find var context in + let stack = map_value.type_stack in + let (typ, var_kind, curr_level) = + if map_value.var_level = curr_level then Stack.pop stack + else Stack.top stack in + (* print_string ((key_to_string var)^"->"); print_endline *) + (* (var_kind_to_string var_kind); print_string "the path is "; *) + (* print_endline (Utils.list_to_string Ident.fieldname_to_string *) + (* path); *) + let var_kind = update_var_kind path new_typ var_kind in + let _ = Stack.push (typ, var_kind, curr_level) stack in + let new_map_value = { map_value with type_stack = stack } in + Map.add var new_map_value context + with Not_found -> assert false + + (* Adds a method's parameters to the context *) + let add_params_to_context pname type_signature context = + let aux context (name, typ) = + let varname = Mangled.from_string name in + let pvar = Sil.mk_pvar varname pname in + add_type pvar typ 0 context in + list_fold_left aux context type_signature + + (* Returns the top type of a variable in the context *) + let get_type pvar context = + let map_value = + try Map.find pvar context + with Not_found -> assert false in + match Stack.top map_value.type_stack with + | (typ, var_kind, level) -> + typ + + (* Returns the type of a path starting from a variable in the context *) + let get_type_content tenv pvar path context = + let map_value = + try Map.find pvar context + with Not_found -> assert false in + match Stack.top map_value.type_stack with + | (typ, var_kind, level) -> + (* print_string ((key_to_string pvar)^"->"); print_endline ("typ is *) + (* "^(Sil.typ_to_string typ)); print_endline (var_kind_to_string *) + (* var_kind); print_string "the path is "; print_endline *) + (* (Utils.list_to_string Ident.fieldname_to_string path); *) + get_type_var_kind tenv typ path var_kind + +end + +let defined_methods = ref Procname.Set.empty + +let initial_node = ref (Cfg.Node.dummy ()) + +let rec super tenv t = + match t with + | Sil.Tstruct (_, _, Sil.Class, Some c2, (Sil.Class, super):: rest, _, _) -> + Sil.tenv_lookup tenv (Sil.TN_csu (Sil.Class, super)) + | Sil.Tarray (dom_type, _) -> None + | Sil.Tptr (dom_type, p) -> + let super_dom_type = super tenv dom_type in + (match super_dom_type with + | None -> None + | Some super -> Some (Sil.Tptr (super, p))) + | _ -> None + +let rec lub tenv t1 t2 = + let t1 = Sil.expand_type tenv t1 in + let t2 = Sil.expand_type tenv t2 in + if (Sil.typ_equal t1 t2) then t1 + else if (Prover.check_subtype tenv t1 t2) then t2 + else if (Prover.check_subtype tenv t2 t1) then t1 + else + let st1 = (super tenv t1) in + let st2 = (super tenv t2) in + match st1, st2 with + | Some st1, Some st2 -> lub tenv st1 st2 + | _ -> t1 + +module Field_context = +struct + + module Map = Map.Make (struct + type t = Ident.fieldname + let compare = Ident.fieldname_compare end) + + type map = Sil.typ Map.t + + let field_context_to_string field_context = + let aux key value s = + (Ident.fieldname_to_string key)^"->"^(Sil.typ_to_string value)^"\n" in + Map.fold aux field_context "" + + let add_type tenv field typ field_context = + let old_typ = + try + Map.find field field_context + with Not_found -> typ in + let new_typ = lub tenv old_typ typ in + Map.add field new_typ field_context + +end + +(* Module for one instance of the TODO set: a set of cfg nodes. *) +module Node_TM = +struct + type t = Cfg.Node.t + + let t_to_string node = Cfg.Node.get_description Utils.pe_text node + + type t' = Type_map.key + + let t'_to_string = Type_map.key_to_string + + type ret_t = Procname.t + + let save_items_to_set = false + + let to_t p = assert false + + type context = Context_map.map + + type field_context = Field_context.map + + module Set = Cfg.NodeSet + + module IdContext = Map.Make (struct + type t = Ident.t + let compare = Ident.compare end) + + type id_map_value = + | Exp of Sil.exp + | Typ of Sil.typ + + (* Local context for the type propagation inside one node. Because the *) + (* ids from one node are not visible in another node, we write their *) + (* types or the expressions they are identified with in a separate local *) + (* context. *) + type id_context = id_map_value IdContext.t + + let id_context_to_string id_context = + let aux key value s = + let value_to_string value = + match value with + | Exp exp -> (Sil.exp_to_string exp) + | Typ typ -> Sil.typ_to_string typ in + (Ident.to_string key)^"->"^(value_to_string value)^"\n" in + IdContext.fold aux id_context "" + + (* Returns the type of constants. Some cases are still TODO. *) + let get_const_type const = + match const with + | Sil.Cint i -> Sil.Tint Sil.IInt + | Sil.Cfloat fl -> Sil.Tfloat Sil.FFloat + | Sil.Cfun fn -> assert false + | Sil.Cstr str -> + Sil.Tptr ( + Sil.Tvar ( Sil.TN_csu (Sil.Class, (Mangled.from_string ( "java.lang.String")))), + Sil.Pk_pointer) + | Sil.Cattribute atr -> assert false + | Sil.Cexn e -> assert false + | Sil.Cclass cl -> assert false + | Sil.Cptr_to_fld _ -> assert false + | Sil.Ctuple _ -> assert false + + let get_id_exptyp id id_context = + try IdContext.find id id_context + with Not_found -> (print_endline (Ident.to_string id)); assert false + + let rec retrieve_type tenv field typ = + match typ with + | Sil.Tptr (ityp, _) -> retrieve_type tenv field ityp + | _ -> + let ityp = Sil.expand_type tenv typ in + match ityp with + | Sil.Tstruct (fields, sftal, csu, nameo, supers, def_mthds, iann) -> + let (_, typ, _) = + try ((list_find (fun (f, t, _) -> Ident.fieldname_equal f field)) fields) + with Not_found -> assert false in + typ + | _ -> assert false + + (* Returns a type for an expression taking into account the types of *) + (* variables in the context and the context of ids. *) + let get_type tenv exp id_context context field_context = + let rec aux exp = + match exp with + | Sil.Var id -> + (match get_id_exptyp id id_context with + | Exp exp -> aux exp + | Typ typ -> typ) + | Sil.UnOp (unop, exp, typ) -> aux exp + | Sil.BinOp (binop, exp1, exp2) -> aux exp1 + | Sil.Const const -> get_const_type const + | Sil.Cast (typ, exp) -> typ + | Sil.Lfield (e, fld, typ) -> + (try Field_context.Map.find fld field_context + with Not_found -> retrieve_type tenv fld typ) + | Sil.Lindex (Sil.Var id, i) -> + (match get_id_exptyp id id_context with + | Exp (Sil.Lvar pvar) -> + Context_map.get_type_content tenv pvar [] context + | _ -> assert false) + | Sil.Sizeof (typ, sub) -> assert false + | Sil.Lvar pvar -> + Context_map.get_type pvar context + | _ -> assert false in + aux exp + + module Map = Type_map.Map + + let map_value_to_string = Type_map.map_value_to_string + + type map = Type_map.TypeSet.t Map.t + + type map_value = Type_map.TypeSet.t + + (* Chooses the next node to be analysed. It will be the successor of the *) + (* current node. When the node doesn't have a successor it goes back the *) + (* same path it analysed already and chooses the first of the ancestors *) + (* that has a successor. *) + let choose_elem set el = + let choose_start_node () = + if Set.mem !initial_node set then !initial_node else Set.min_elt set in + let rec aux old_node = + (* print_endline "old node in aux is "; print_endline (t_to_string *) + (* old_node); *) + let backtrack () = + (* print_endline "backtracking..."; *) + let preds = Cfg.Node.get_preds old_node in + let pred = + try list_find (fun p -> not (Set.mem p set)) preds + with Not_found -> + try list_hd preds + with Failure "hd" -> Set.min_elt set in + (aux pred) in + if (Set.mem old_node set) then backtrack () + else + let succs = Cfg.Node.get_succs old_node in + let node = + try list_find (fun n -> ( Set.mem n set)) succs + with Not_found -> backtrack () in + node in + match el with + | Some old_node -> + (* print_endline "choosing an element when old_element is "; *) + (* print_endline (t_to_string old_node); *) + aux old_node + | None -> choose_start_node () + + let instr_to_string instr = + let pp fmt () = Sil.pp_instr Utils.pe_text fmt instr in + Utils.pp_to_string pp () + + (* Goes through the instructions of a node and propagates the types. *) + (* When it analyses a virtual method call it adds the current dynamic *) + (* types that the method is called with to the map. It also collects the *) + (* procedure names when their map changes so that they can be *) + (* reanalysed. *) + let collect_items exe_env cfg0 tenv node map context field_context = + (* print_endline "\n\nAnalyzing node: "; print_endline (t_to_string *) + (* node); *) + let set_ids ids rtype id_context = + match ids with + | [ret_id] -> IdContext.add ret_id (Typ rtype) id_context + | _ -> id_context in + let aux (id_context, context, field_context, map, list) instr = + (* print_string "\nAnalyzing instruction: "; print_endline *) + (* (instr_to_string instr); *) + match instr with + | Sil.Letderef (id, exp, typ, loc) -> + let id_context = IdContext.add id (Exp exp) id_context in + id_context, context, field_context, map, list + | Sil.Set (exp1, typ, exp, loc) -> + let exp_typ = get_type tenv exp id_context context field_context in + let context, field_context = + (match exp1 with + | Sil.Lvar pvar -> + (* print_endline ("trying to add variable "^(Context_map.key_to_string *) + (* pvar) ); print_endline ("with type "^(Sil.typ_to_string exp_typ)); *) + (* print_endline "Context"; Context_map.print_map context; *) + Context_map.add_type pvar exp_typ 0 context, field_context + | Sil.Lfield (e, fld, typ) -> + context, Field_context.add_type tenv fld exp_typ field_context + | Sil.Lindex (Sil.Var id, _) -> + (match get_id_exptyp id id_context with + | Exp (Sil.Lvar pvar) -> + Context_map.add_type_content pvar [] exp_typ 0 context, field_context + | _ -> assert false) + | _ -> assert false) in + id_context, context, field_context, map, list + | Sil.Call (ret_ids, Sil.Const (Sil.Cfun callee_pname), actual_params, loc, call_flags) + when not (SymExec.function_is_builtin callee_pname) -> + (* TODO: constraint for virtual calls *) + let cfg = + if (Procname.Set.mem callee_pname !defined_methods) then + Exe_env.get_cfg exe_env callee_pname + else cfg0 in + let pdesc = match Cfg.Procdesc.find_from_name cfg callee_pname with + | Some pdesc -> pdesc + | None -> assert false in + let return_type = Cfg.Procdesc.get_ret_type pdesc in + let id_context = set_ids ret_ids return_type id_context in + if (Procname.Set.mem callee_pname !defined_methods) then + let formals = Cfg.Procdesc.get_formals pdesc in + let create_typ_bundle (exp, typ) (name, typ2) = + (name, (get_type tenv exp id_context context field_context)) in + let typ_bundle = list_map2 create_typ_bundle actual_params formals in + let set = Type_map.find_dyn_types callee_pname map in + if Type_map.TypeSet.mem typ_bundle set + then id_context, context, field_context, map, list + else + let ext_set = Type_map.TypeSet.add typ_bundle set in + let map' = Type_map.add_set_to_map callee_pname ext_set map in + let list = callee_pname:: list in + id_context, context, field_context, map', list + else id_context, context, field_context, map, list + | Sil.Call (ret_ids, Sil.Const (Sil.Cfun callee_pname), [(exp, class_type)], loc, call_flags) + when Procname.equal callee_pname SymExec.ModelBuiltins.__new -> + let id_context = set_ids ret_ids class_type id_context in + id_context, context, field_context, map, list + | Sil.Call (ret_ids, Sil.Const (Sil.Cfun callee_pname), + [(array_size, array_type)], loc, call_flags) + when Procname.equal callee_pname SymExec.ModelBuiltins.__new_array -> + let id_context = set_ids ret_ids array_type id_context in + id_context, context, field_context, map, list + | Sil.Call (ret_ids, Sil.Const (Sil.Cfun callee_pname), + [(sil_ex, type_of_ex); (Sil.Sizeof (typ, _), Sil.Tvoid)], loc, call_flags) + when Procname.equal callee_pname SymExec.ModelBuiltins.__cast -> + let id_context = set_ids ret_ids typ id_context in + id_context, context, field_context, map, list + | Sil.Call (ret_ids, Sil.Const (Sil.Cfun callee_pname), + [(sil_ex, type_of_ex); (_, Sil.Tvoid)], loc, call_flags) + when Procname.equal callee_pname SymExec.ModelBuiltins.__instanceof -> + let id_context = set_ids ret_ids (Sil.Tint Sil.IBool) id_context in + id_context, context, field_context, map, list + | _ -> id_context, context, field_context, map, list in + let instrs = Cfg.Node.get_instrs node in + let id_context, context, field_context, map, items = + list_fold_left aux (IdContext.empty, context, field_context, map, []) instrs in + context, field_context, map, items + +end + +(* Module for the second instance of the TODO set: a set of procedure *) +(* names. *) +module Typeprop_node = Control_flow (Node_TM) + +module TM = +struct + + type t = Procname.t + + let t_to_string = Procname.to_string + + type t' = Type_map.key + + let t'_to_string = Type_map.key_to_string + + type ret_t = Procname.t + + module Map = Type_map.Map + + let map_value_to_string = Type_map.map_value_to_string + + type map = Type_map.TypeSet.t Map.t + + type map_value = Type_map.TypeSet.t + + module Set = Procname.Set + + let set_to_string set = + let aux value = print_string ("\n item: "^(t_to_string value)) in + Set.iter aux set + + let choose_elem set el = + let element = Set.min_elt set in + element + + let save_items_to_set = true + + let to_t p = p + + type context = Context_map.map + + type field_context = Field_context.map + + let map_to_string map = + let aux key value s = + s^(t'_to_string key)^" -> "^(map_value_to_string value)^"\n\n" in + (Map.fold aux map "") + + let get_initial_node cfg proc_name = + let pdesc = + match Cfg.Procdesc.find_from_name cfg proc_name with + | Some pdesc -> pdesc + | None -> + L.out "#### ERROR: cannot find %a ####@.@." Procname.pp proc_name; + assert false in + let start_node = Cfg.Procdesc.get_start_node pdesc in + start_node + + (* Collects all the nodes from a procedure. Ignores the exceptions nodes *) + (* for now for simplicity. *) + let collect_nodes pname initial_node = + let rec aux nodes set = + match nodes with + | [] -> set + | node:: rest -> + if (Cfg.NodeSet.mem node set) then (aux rest set) + else + let set' = Cfg.NodeSet.add node set in + let succs = Cfg.Node.get_succs node in + (* let exns = Cfg.Node.get_exn node in *) + (aux (succs(*@exns*)@rest) set') in + (aux [initial_node] Cfg.NodeSet.empty) + + (* For each of the types in the set of types assigned to the method, *) + (* execute the type propagation algorithm and collect the updated map of *) + (* types and list of new procedures that were updated and need to be *) + (* analysed again. *) + let collect_items exe_env cfg0 tenv pname map context field_context = + let cfg = Exe_env.get_cfg exe_env pname in + let tenv = Exe_env.get_tenv exe_env pname in + let init = get_initial_node cfg pname in + initial_node := init; + let nodes_todo = collect_nodes pname init in + let set = Type_map.find_dyn_types pname map in + let process_type_bundle type_bundle (map, items) = + let context = Context_map.add_params_to_context pname type_bundle context in + let context, field_context, map, items = + Typeprop_node.update_todo exe_env cfg tenv None + nodes_todo (map, items) context field_context in + map, items in + let map, items = Type_map.TypeSet.fold process_type_bundle set (map, []) in + Context_map.Map.empty, field_context, map, items + +end + +module Typeprop = Control_flow (TM) + +let map_to_string map = + let aux key value s = + let initial = + try ignore(Procname.Set.find key !initial_methods); true + with Not_found -> false in + if initial then s + else s^(TM.t'_to_string key)^" is called with types: "^(TM.map_value_to_string value)^"\n\n" in + (TM.Map.fold aux map "") + +let arg_desc = + let base_arg = + let options_to_keep = ["-results_dir"] in + let filter arg_desc = + list_filter (fun desc -> + let (option_name, _, _, _) = desc in + list_mem string_equal option_name options_to_keep) + arg_desc in + let desc = (filter Utils.base_arg_desc) in + Utils.Arg2.create_options_desc false "Parsing Options" desc in + base_arg + +let usage = + "Usage: Typeprop -results_dir out \n" + +let () = + Utils.Arg2.parse arg_desc (fun arg -> ()) usage + +(* Initialises the map of types of the methods that are never called with *) +(* the static types. *) +let initialize_map exe_env methods = + let rec init_method exe_env pname map = + let cfg = Exe_env.get_cfg exe_env pname in + let formals = get_formals cfg pname in + initial_methods := Procname.Set.add pname !initial_methods; + Type_map.add_to_map pname formals map in + let meth_list = Procname.Set.elements methods in + let map' = (list_fold_right (init_method exe_env) meth_list Type_map.Map.empty) in + map' + +(* Collects all the methods that are defined in the program. *) +let collect_methods exe_env = + let global_cg = Exe_env.get_cg exe_env in + let nodes, edges = Cg.get_nodes_and_edges global_cg in + let do_node (n, defined) defined_methods = + if defined then + Procname.Set.add n defined_methods + else defined_methods in + let do_edge (n1, n2) no_main_methods = + if Cg.node_defined global_cg n1 && Cg.node_defined global_cg n2 then + Procname.Set.add n2 no_main_methods + else no_main_methods in + let defined = list_fold_right do_node nodes Procname.Set.empty in + let no_main_methods = list_fold_right do_edge edges Procname.Set.empty in + let main_methods = Procname.Set.diff defined no_main_methods in + defined_methods := defined; + (* TM.set_to_string main_methods; *) + main_methods + +(* Performs type propagation for a program *) +let type_prop_do exe_env = + let main_methods = collect_methods exe_env in + let map = initialize_map exe_env main_methods in + let tenv = Sil.create_tenv () in + let context, field_context, map, list = + Typeprop.update_todo exe_env (Obj.magic ()) tenv None main_methods + (map, []) Context_map.Map.empty Field_context.Map.empty in + map + +(* Loads the local control graphs of a program to create a global control *) +(* graph. *) +let load_cg_files _exe_env (source_dirs : DB.source_dir list) = + let load_cg_file (_exe_env: Exe_env.initial) (source_dir : DB.source_dir) = + match Exe_env.add_cg _exe_env source_dir with + | None -> () + | Some cg -> + (*L.err "loaded %s@." (DB.source_dir_to_string source_dir) *) () in + list_iter (fun source_dir -> load_cg_file _exe_env source_dir) source_dirs; + let exe_env = Exe_env.freeze _exe_env in + exe_env + +(* Loads the control graph and executes the type propagation algorithm. *) +(* TODO: serialize and save the map after its computation. *) +let type_prop () = + let source_dirs = DB.find_source_dirs () in + let _exe_env = Exe_env.create None in + let exe_env = load_cg_files _exe_env source_dirs in + let map = type_prop_do exe_env in + print_endline "\n"; + print_endline (map_to_string map); + print_endline "\n"; + () + +let () = type_prop () diff --git a/infer/src/backend/type_prop.mli b/infer/src/backend/type_prop.mli new file mode 100644 index 000000000..45cb1d3b6 --- /dev/null +++ b/infer/src/backend/type_prop.mli @@ -0,0 +1,2 @@ + +val type_prop : unit -> unit \ No newline at end of file diff --git a/infer/src/backend/utils.ml b/infer/src/backend/utils.ml new file mode 100644 index 000000000..342cd0d38 --- /dev/null +++ b/infer/src/backend/utils.ml @@ -0,0 +1,1181 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +(** General utility functions and definition with global scope *) + +module F = Format + +(** initial time of the analysis, i.e. when this module is loaded, gotten from Unix.time *) +let initial_analysis_time = Unix.time () + +(** precise time of day at the start of the analysis *) +let initial_timeofday = Unix.gettimeofday () + +(** {2 Generic Utility Functions} *) + +(** Compare police: generic compare disabled. *) +let compare = () + +let string_equal (s1: string) (s2: string) = s1 = s2 + +let string_compare (s1: string) (s2: string) = Pervasives.compare s1 s2 + +let float_compare (f1: float) (f2: float) = Pervasives.compare f1 f2 + +let bool_compare (b1: bool) (b2: bool) = Pervasives.compare b1 b2 + +let bool_equal (b1: bool) (b2: bool) = b1 = b2 + +(** Extend and equality function to an option type. *) +let opt_equal cmp x1 x2 = match x1, x2 with + | None, None -> true + | Some _, None -> false + | None, Some _ -> false + | Some y1, Some y2 -> cmp y1 y2 + +(** Efficient comparison for integers *) +let int_compare (i: int) (j: int) = i - j + +let int_equal (i: int) (j: int) = i = j + +(** Generic comparison of pairs given a compare function for each element of the pair. *) +let pair_compare compare compare' (x1, y1) (x2, y2) = + let n = compare x1 x2 in + if n <> 0 then n else compare' y1 y2 + +(** Generic comparison of pairs given a compare function for each element of the triple *) +let triple_compare compare compare' compare'' (x1, y1, z1) (x2, y2, z2) = + let n = compare x1 x2 in + if n <> 0 then n else let n = compare' y1 y2 in + if n <> 0 then n else compare'' z1 z2 + +let list_exists = List.exists +let list_filter = List.filter +let list_find = List.find +let list_fold_left = List.fold_left +let list_fold_left2 = List.fold_left2 +let list_for_all = List.for_all +let list_for_all2 = List.for_all2 +let list_hd = List.hd +let list_iter = List.iter +let list_iter2 = List.iter2 +let list_length = List.length +let list_nth = List.nth +let list_partition = List.partition +let list_rev = List.rev +let list_rev_append = List.rev_append +let list_rev_map = List.rev_map +let list_sort = List.sort +let list_stable_sort = List.stable_sort +let list_tl = List.tl + +(** tail-recursive variant of List.fold_right *) +let list_fold_right f l a = + let g x y = f y x in + list_fold_left g a (list_rev l) + +(** tail-recursive variant of List.combine *) +let list_combine = + let rec combine acc l1 l2 = match l1, l2 with + | [], [] -> acc + | x1:: l1, x2:: l2 -> combine ((x1, x2):: acc) l1 l2 + | [], _:: _ + | _:: _, [] -> raise (Invalid_argument "list_combine") in + fun l1 l2 -> list_rev (combine [] l1 l2) + +(** tail-recursive variant of List.split *) +let list_split = + let rec split acc1 acc2 = function + | [] -> (acc1, acc2) + | (x, y):: l -> split (x:: acc1) (y:: acc2) l in + fun l -> + let acc1, acc2 = split [] [] l in + list_rev acc1, list_rev acc2 + +(** Like List.mem but without builtin equality *) +let list_mem equal x l = list_exists (equal x) l + +(** tail-recursive variant of List.flatten *) +let list_flatten = + let rec flatten acc l = match l with + | [] -> acc + | x:: l' -> flatten (list_rev_append x acc) l' in + fun l -> list_rev (flatten [] l) + +let list_flatten_options list = + list_fold_left (fun list -> function | Some x -> x:: list | None -> list) [] list + |> list_rev + +let rec list_drop_first n = function + | xs when n == 0 -> xs + | x:: xs -> list_drop_first (n - 1) xs + | [] -> [] + +let list_drop_last n list = + list_rev (list_drop_first n (list_rev list)) + +(** List police: don't use the list module to avoid non-tail recursive functions and builtin equality *) +module List = struct end + +(** Generic comparison of lists given a compare function for the elements of the list *) +let rec list_compare compare l1 l2 = + match l1, l2 with + | [],[] -> 0 + | [], _ -> - 1 + | _, [] -> 1 + | x1:: l1', x2:: l2' -> + let n = compare x1 x2 in + if n <> 0 then n else list_compare compare l1' l2' + +(** Returns (reverse input_list) *) +let rec list_rev_with_acc acc = function + | [] -> acc + | x :: xs -> list_rev_with_acc (x:: acc) xs + +(** tail-recursive variant of List.append *) +let list_append l1 l2 = + list_rev_append (list_rev l1) l2 + +(** tail-recursive variant of List.map *) +let list_map f l = + list_rev (list_rev_map f l) + +(** Remove consecutive equal elements from a list (according to the given comparison functions) *) +let list_remove_duplicates compare l = + let rec remove compare acc = function + | [] -> list_rev acc + | [x] -> list_rev (x:: acc) + | x:: ((y:: l'') as l') -> + if compare x y = 0 then remove compare acc (x:: l'') + else remove compare (x:: acc) l' in + remove compare [] l + +(** Remove consecutive equal irrelevant elements from a list (according to the given comparison and relevance functions) *) +let list_remove_irrelevant_duplicates compare relevant l = + let rec remove compare acc = function + | [] -> list_rev acc + | [x] -> list_rev (x:: acc) + | x:: ((y:: l'') as l') -> + if compare x y = 0 then begin + match relevant x, relevant y with + | false, _ -> remove compare acc l' + | true, false -> remove compare acc (x:: l'') + | true, true -> remove compare (x:: acc) l' + end + else remove compare (x:: acc) l' in + remove compare [] l + +(** The function works on sorted lists without duplicates *) +let rec list_merge_sorted_nodup compare res xs1 xs2 = + match xs1, xs2 with + | [], _ -> + list_rev_with_acc xs2 res + | _, [] -> + list_rev_with_acc xs1 res + | x1 :: xs1', x2 :: xs2' -> + let n = compare x1 x2 in + if n = 0 then + list_merge_sorted_nodup compare (x1 :: res) xs1' xs2' + else if n < 0 then + list_merge_sorted_nodup compare (x1 :: res) xs1' xs2 + else + list_merge_sorted_nodup compare (x2 :: res) xs1 xs2' + +let list_intersect compare l1 l2 = + let l1_sorted = list_sort compare l1 in + let l2_sorted = list_sort compare l2 in + let rec f l1 l2 = match l1, l2 with + | ([], _) | (_,[]) -> false + | (x1:: l1', x2:: l2') -> + let x_comparison = compare x1 x2 in + if x_comparison = 0 then true + else if x_comparison < 0 then f l1' l2 + else f l1 l2' in + f l1_sorted l2_sorted + +exception Fail + +(** Apply [f] to pairs of elements; raise [Fail] if the two lists have different lenghts. *) +let list_map2 f l1 l2 = + let rec go l1 l2 acc = + match l1, l2 with + | [],[] -> list_rev acc + | x1 :: l1', x2 :: l2' -> + let x' = f x1 x2 in + go l1' l2' (x':: acc) + | _ -> raise Fail in + go l1 l2 [] + +let list_to_string f l = + let rec aux l = + match l with + | [] -> "" + | s:: [] -> (f s) + | s:: rest -> (f s)^", "^(aux rest) in + "["^(aux l)^"]" + +(** {2 Useful Modules} *) + +(** Set of integers *) +module IntSet = + Set.Make(struct + type t = int + let compare = int_compare + end) + +(** Set of strings *) +module StringSet = Set.Make(String) + +(** Pretty print a set of strings *) +let pp_stringset fmt ss = + StringSet.iter (fun s -> F.fprintf fmt "%s " s) ss + +(** Maps from strings *) +module StringMap = Map.Make (struct + type t = string + let compare (s1: string) (s2: string) = Pervasives.compare s1 s2 end) + +(** {2 Printing} *) + +(** Kind of simple printing: default or with full types *) +type pp_simple_kind = PP_SIM_DEFAULT | PP_SIM_WITH_TYP + +(** Kind of printing *) +type printkind = PP_TEXT | PP_LATEX | PP_HTML + +(** Colors supported in printing *) +type color = Black | Blue | Green | Orange | Red + +(** map subexpressions (as Obj.t element compared by physical equality) to colors *) +type colormap = Obj.t -> color + +(** Print environment threaded through all the printing functions *) +type printenv = { + pe_opt : pp_simple_kind; (** Current option for simple printing *) + pe_kind : printkind; (** Current kind of printing *) + pe_cmap_norm : colormap; (** Current colormap for the normal part *) + pe_cmap_foot : colormap; (** Current colormap for the footprint part *) + pe_color : color; (** Current color *) + pe_obj_sub : (Obj.t -> Obj.t) option (** generic object substitution *) +} + +(** Create a colormap of a given color *) +let colormap_from_color color (o: Obj.t) = color + +(** standard colormap: black *) +let colormap_black (o: Obj.t) = Black + +(** red colormap *) +let colormap_red (o: Obj.t) = Red + +(** Default text print environment *) +let pe_text = + { pe_opt = PP_SIM_DEFAULT; + pe_kind = PP_TEXT; + pe_cmap_norm = colormap_black; + pe_cmap_foot = colormap_black; + pe_color = Black; + pe_obj_sub = None } + +(** Default html print environment *) +let pe_html color = + { pe_text with + pe_kind = PP_HTML; + pe_cmap_norm = colormap_from_color color; + pe_cmap_foot = colormap_from_color color; + pe_color = color } + +(** Default latex print environment *) +let pe_latex color = + { pe_opt = PP_SIM_DEFAULT; + pe_kind = PP_LATEX; + pe_cmap_norm = colormap_from_color color; + pe_cmap_foot = colormap_from_color color; + pe_color = color; + pe_obj_sub = None } + +(** Extend the normal colormap for the given object with the given color *) +let pe_extend_colormap pe (x: Obj.t) (c: color) = + let colormap (y: Obj.t) = + if x == y then c + else pe.pe_cmap_norm y in + { pe with pe_cmap_norm = colormap } + +(** Set the object substitution, which is supposed to preserve the type. +Currently only used for a map from (identifier) expressions to the program var containing them *) +let pe_set_obj_sub pe (sub: 'a -> 'a) = + let new_obj_sub x = + let x' = Obj.repr (sub (Obj.obj x)) in + match pe.pe_obj_sub with + | None -> x' + | Some sub' -> sub' x' in + { pe with pe_obj_sub = Some (new_obj_sub) } + +(** Reset the object substitution, so that no substitution takes place *) +let pe_reset_obj_sub pe = + { pe with pe_obj_sub = None } + +(** string representation of colors *) +let color_string = function + | Black -> "color_black" + | Blue -> "color_blue" + | Green -> "color_green" + | Orange -> "color_orange" + | Red -> "color_red" + +(** Pretty print a space-separated sequence *) +let rec pp_seq pp f = function + | [] -> () + | [x] -> F.fprintf f "%a" pp x + | x:: l -> F.fprintf f "%a %a" pp x (pp_seq pp) l + +(** Print a comma-separated sequence *) +let rec pp_comma_seq pp f = function + | [] -> () + | [x] -> F.fprintf f "%a" pp x + | x:: l -> F.fprintf f "%a,%a" pp x (pp_comma_seq pp) l + +(** Print a ;-separated sequence. *) +let rec _pp_semicolon_seq oneline pe pp f = + let pp_sep fmt () = + if oneline then F.fprintf fmt " " else F.fprintf fmt "@\n" in + function + | [] -> () + | [x] -> F.fprintf f "%a" pp x + | x:: l -> + (match pe.pe_kind with + | PP_TEXT | PP_HTML -> + F.fprintf f "%a ; %a%a" pp x pp_sep () (_pp_semicolon_seq oneline pe pp) l + | PP_LATEX -> + F.fprintf f "%a ;\\\\%a %a" pp x pp_sep () (_pp_semicolon_seq oneline pe pp) l) + +(** Print a ;-separated sequence with newlines. *) +let pp_semicolon_seq pe = _pp_semicolon_seq false pe + +(** Print a ;-separated sequence on one line. *) +let pp_semicolon_seq_oneline pe = _pp_semicolon_seq true pe + +(** Print an or-separated sequence. *) +let rec pp_or_seq pe pp f = function + | [] -> () + | [x] -> F.fprintf f "%a" pp x + | x:: l -> + (match pe.pe_kind with + | PP_TEXT -> + F.fprintf f "%a || %a" pp x (pp_semicolon_seq pe pp) l + | PP_HTML -> + F.fprintf f "%a ∨ %a" pp x (pp_semicolon_seq pe pp) l + | PP_LATEX -> + F.fprintf f "%a \\vee %a" pp x (pp_semicolon_seq pe pp) l) + +(** Produce a string from a 1-argument pretty printer function *) +let pp_to_string pp x = + let buf = Buffer.create 1 in + let fmt = Format.formatter_of_buffer buf in + Format.fprintf fmt "%a@?" pp x; + Buffer.contents buf + +(** Print the current time and date in a format similar to the "date" command *) +let pp_current_time f () = + let tm = Unix.localtime (Unix.time ()) in + F.fprintf f "%02d/%02d/%4d %02d:%02d" tm.Unix.tm_mday tm.Unix.tm_mon (tm.Unix.tm_year + 1900) tm.Unix.tm_hour tm.Unix.tm_min + +(** Print the time in seconds elapsed since the beginning of the execution of the current command. *) +let pp_elapsed_time fmt () = + let elapsed = Unix.gettimeofday () -. initial_timeofday in + Format.fprintf fmt "%f" elapsed + +(** Type of location in ml source: file,line,column *) +type ml_location = string * int * int + +(** Turn an ml location into a string *) +let ml_location_string ((file: string), (line: int), (column: int)) = + "File " ^ file ^ " Line " ^ string_of_int line ^ " Column " ^ string_of_int column + +(** Pretty print a location of ml source *) +let pp_ml_location fmt mloc = + F.fprintf fmt "%s" (ml_location_string mloc) + +let pp_ml_location_opt fmt mloco = + if !Config.developer_mode then match mloco with + | None -> () + | Some mloc -> F.fprintf fmt "(%a)" pp_ml_location mloc + +(** {2 SymOp and Timeouts: units of symbolic execution} *) + +type timeout_kind = + | TOtime (* max time exceeded *) + | TOsymops of int (* max symop's exceeded *) + | TOrecursion of int (* max recursion level exceeded *) + +(** Timeout exception *) +exception Timeout_exe of timeout_kind + +let exn_not_timeout = function + | Timeout_exe _ -> false + | _ -> true + +let symops_timeout, seconds_timeout = + (* default timeout and long timeout are the same for now, but this will change shortly *) + let default_symops_timeout = 333 in + let default_seconds_timeout = 10 in + let long_symops_timeout = 1000 in + let long_seconds_timeout = 30 in + let analyzing_models = Config.from_env_variable "ANALYZE_MODELS" in + if analyzing_models then + (* use longer timeouts when analyzing models *) + long_symops_timeout, long_seconds_timeout + else + default_symops_timeout, default_seconds_timeout + +(** number of symops to multiply by the number of iterations, after which there is a timeout *) +let symops_per_iteration = ref symops_timeout + +(** number of seconds to multiply by the number of iterations, after which there is a timeout *) +let seconds_per_iteration = ref seconds_timeout + +(** timeout value from the -iterations command line option *) +let iterations_cmdline = ref 1 + +(** Timeout in seconds for each function *) +let timeout_seconds = ref !seconds_per_iteration + +(** Timeout in SymOps *) +let timeout_symops = ref !symops_per_iteration + +(** Set the timeout values in seconds and symops, computed as a multiple of the integer parameter *) +let set_iterations n = + timeout_symops := !symops_per_iteration * n; + timeout_seconds := !seconds_per_iteration * n + +let get_timeout_seconds () = !timeout_seconds + +(** Count the number of symbolic operations *) +module SymOp = struct + (** Number of symop's *) + let symop_count = ref 0 + + (** Total number of symop's since the beginning *) + let symop_total = ref 0 + + (** Only throw timeout exception when alarm is active *) + let alarm_active = ref false + + (** last wallclock set by an alarm, if any *) + let last_wallclock = ref None + + (** handler for the wallclock timeout *) + let wallclock_timeout_handler = ref None + + (** set the handler for the wallclock timeout *) + let set_wallclock_timeout_handler handler = + wallclock_timeout_handler := Some handler + + (** Set the wallclock alarm checked at every pay() *) + let set_wallclock_alarm nsecs = + last_wallclock := Some (Unix.gettimeofday () +. float_of_int nsecs) + + (** Unset the wallclock alarm checked at every pay() *) + let unset_wallclock_alarm () = + last_wallclock := None + + (** if the wallclock alarm has expired, raise a timeout exception *) + let check_wallclock_alarm () = + match !last_wallclock, !wallclock_timeout_handler with + | Some alarm_time, Some handler when Unix.gettimeofday () >= alarm_time -> + unset_wallclock_alarm (); + handler () + | _ -> () + + (** Return the total number of symop's since the beginning *) + let get_total () = !symop_total + + (** Reset the total number of symop's *) + let reset_total () = symop_total := 0 + + (** timer at load time *) + let initial_time = Unix.gettimeofday () + + (** Time in the beginning *) + let timer = ref initial_time + + (** Count one symop *) + let pay () = + incr symop_count; incr symop_total; + if !symop_count > !timeout_symops && !alarm_active + then raise (Timeout_exe (TOsymops !symop_count)); + check_wallclock_alarm () + + (** Reset the counters *) + let reset () = + symop_count := 0; + timer := Unix.gettimeofday () + + (** Reset the counter and activate the alarm *) + let set_alarm () = + reset (); + alarm_active := true + + (** De-activate the alarm *) + let unset_alarm () = + alarm_active := false + + let report_stats f symops elapsed = + F.fprintf f "SymOp stats -- symops:%d time:%f symops/sec:%f" symops elapsed ((float_of_int symops) /. elapsed) + + (** Report the stats since the last reset *) + let report f () = + let elapsed = Unix.gettimeofday () -. !timer in + report_stats f !symop_count elapsed + + (** Report the stats since the loading of this module *) + let report_total f () = + let elapsed = Unix.gettimeofday () -. initial_time in + report_stats f !symop_total elapsed +end + +(** Check if the lhs is a substring of the rhs. *) +let string_is_prefix s1 s2 = + String.length s1 <= String.length s2 && + String.sub s2 0 (String.length s1) = s1 + +(** Check if the lhs is a postfix of the rhs. *) +let string_is_suffix s1 s2 = + let l1 = String.length s1 in + let l2 = String.length s2 in + l1 <= l2 && + String.sub s2 (l2 - l1) l1 = s1 + +(** Check if the lhs is contained in the rhs. *) +let string_contains s1 s2 = + let rexp = Str.regexp_string s1 in + try + ignore (Str.search_forward rexp s2 0); + true + with Not_found -> false + +(** Split a string across the given character, if given. (e.g. split first.second with '.').*) +let string_split_character s c = + try + let index = String.rindex s c in + let lhs = String.sub s 0 index in + let rhs = String.sub s (index + 1) ((String.length s) - (1 + index)) in + (Some lhs, rhs) + with Not_found -> (None, s) + +let string_value_or_empty_string + (string_option: string option): string = + match string_option with + | Some s -> s + | None -> "" + +(** read a source file and return a list of lines, or None in case of error *) +let read_file fname = + let res = ref [] in + let cin_ref = ref None in + let cleanup () = + match !cin_ref with + | None -> () + | Some cin -> close_in cin in + try + let cin = open_in fname in + cin_ref := Some cin; + while true do + let line = input_line cin in + res := line :: !res + done; + assert false + with + | End_of_file -> + cleanup (); + Some (list_rev !res) + | Sys_error _ -> + cleanup (); + None + +(** copy a source file, return the number of lines, or None in case of error *) +let copy_file fname_from fname_to = + let res = ref 0 in + let cin_ref = ref None in + let cout_ref = ref None in + let cleanup () = + begin match !cin_ref with + | None -> () + | Some cin -> close_in cin + end; + begin match !cout_ref with + | None -> () + | Some cout -> close_out cout + end in + try + let cin = open_in fname_from in + cin_ref := Some cin; + let cout = open_out fname_to in + cout_ref := Some cout; + while true do + let line = input_line cin in + output_string cout line; + output_char cout '\n'; + incr res + done; + assert false + with + | End_of_file -> + cleanup (); + Some !res + | Sys_error _ -> + cleanup(); + None + +module FileLOC = (** count lines of code of files and keep processed results in a cache *) + struct + let include_loc_hash = Hashtbl.create 1 + + let reset () = Hashtbl.clear include_loc_hash + + let file_get_loc fname = + try Hashtbl.find include_loc_hash fname with Not_found -> + let loc = match read_file fname with + | None -> 0 + | Some l -> list_length l in + Hashtbl.add include_loc_hash fname loc; + loc + end + +(** type for files used for printing *) +type outfile = + { fname : string; (** name of the file *) + out_c : out_channel; (** output channel *) + fmt : F.formatter (** formatter for printing *) } + +(** create an outfile for the command line *) +let create_outfile fname = + try + let out_c = open_out fname in + let fmt = F.formatter_of_out_channel out_c in + Some { fname = fname; out_c = out_c; fmt = fmt } + with Sys_error _ -> + F.fprintf F.err_formatter "error: cannot create file %s@." fname; + None + +(** operate on an outfile reference if it is not None *) +let do_outf outf_ref f = + match !outf_ref with + | None -> () + | Some outf -> + f outf + +(** close an outfile *) +let close_outf outf = + close_out outf.out_c + +(** convert a filename to absolute path and normalize by removing occurrences of "." and ".." *) +module FileNormalize = struct + let rec fname_to_list_rev fname = + if fname = "" then [] else + let base = Filename.basename fname in + let dir = Filename.dirname fname in + let does_not_split = (* make sure it terminates whatever the implementation of Filename *) + fname = base || String.length dir >= String.length fname in + if does_not_split then [fname] + else base :: fname_to_list_rev dir + + (* split a file name into a list of strings representing it as a path *) + let fname_to_list fname = + list_rev (fname_to_list_rev fname) + + (* concatenate a list of strings representing a path into a filename *) + let rec list_to_fname base path = match path with + | [] -> base + | x :: path' -> list_to_fname (Filename.concat base x) path' + + (* normalize a path where done_l is a reversed path from the root already normalized *) + (* and todo_l is the path still to normalize *) + let rec normalize done_l todo_l = match done_l, todo_l with + | _, y :: tl when y = Filename.current_dir_name -> (* path/. --> path *) + normalize done_l tl + | [root], y :: tl when y = Filename.parent_dir_name -> (* /.. --> / *) + normalize done_l tl + | x :: dl, y :: tl when y = Filename.parent_dir_name -> (* path/x/.. --> path *) + normalize dl tl + | _, y :: tl -> normalize (y :: done_l) tl + | _, [] -> list_rev done_l + + (* check if the filename contains "." or ".." *) + let fname_contains_current_parent fname = + let l = fname_to_list fname in + list_exists (fun x -> x = Filename.current_dir_name || x = Filename.parent_dir_name) l + + (* convert a filename to absolute path, if necessary, and normalize "." and ".." *) + let fname_to_absolute_normalize fname = + let is_relative = Filename.is_relative fname in + let must_normalize = fname_contains_current_parent fname in + let simple_case () = + if is_relative then Filename.concat (Unix.getcwd ()) fname + else fname in + if must_normalize then begin + let done_l, todo_l = + if is_relative then + fname_to_list_rev (Unix.getcwd ()), fname_to_list fname + else match fname_to_list fname with + | [] -> [fname], [] (* should not happen *) + | root :: l -> [root], l in + let normal_l = normalize done_l todo_l in + match normal_l with + | base :: l -> list_to_fname base l + | [] -> (* should not happen *) simple_case () + end + else simple_case () + + (* + let test () = + let test_fname fname = + let fname' = fname_to_absolute_normalize fname in + Format.fprintf Format.std_formatter "fname %s --> %s@." fname fname' in + let tests = ["."; + ".."; + "aaa.c"; + "/"; + "/.."; + "../test.c"; + "src/../././test.c"] in + List.map test_fname tests + *) +end + +(** Convert a filename to an absolute one if it is relative, and normalize "." and ".." *) +let filename_to_absolute fname = + FileNormalize.fname_to_absolute_normalize fname + +(** Convert an absolute filename to one relative to the current directory. *) +let filename_to_relative root fname = + let string_strict_subtract s1 s2 = + let n1, n2 = String.length s1, String.length s2 in + if n1 < n2 && String.sub s2 0 n1 = s1 then + String.sub s2 (n1 + 1) (n2 - (n1 + 1)) + else s2 in + let norm_root = (* norm_root is root without any trailing / *) + Filename.concat (Filename.dirname root) (Filename.basename root) in + let remainder = (* remove the path prefix to root including trailing / *) + string_strict_subtract norm_root fname in + remainder + + +let base_arg_desc = + [ + "-results_dir", + Arg.String (fun s -> Config.results_dir := s), + Some "dir", + "set the project results directory (default dir=" ^ Config.default_results_dir ^ ")"; + "-coverage", + Arg.Unit (fun () -> Config.worklist_mode:= 2), + None, + "analysis mode to maximize coverage (can take longer)"; + "-lib", + Arg.String (fun s -> Config.specs_library := filename_to_absolute s :: !Config.specs_library), + Some "dir", + "add dir to the list of directories to be searched for spec files"; + "-models", + Arg.String (fun s -> Config.add_models (filename_to_absolute s)), + Some "zip file", + "add a zip file containing the models"; + "-ziplib", + Arg.String (fun s -> Config.add_zip_library (filename_to_absolute s)), + Some "zip file", + "add a zip file containing library spec files"; + "-project_root", + Arg.String (fun s -> Config.project_root := Some (filename_to_absolute s)), + Some "dir", + "root directory of the project"; + "-infer_cache", + Arg.String (fun s -> Config.JarCache.infer_cache := Some (filename_to_absolute s)), + Some "dir", + "Select a directory to contain the infer cache"; + ] + +let reserved_arg_desc = + [ + "-absstruct", Arg.Set_int Config.abs_struct, Some "n", "abstraction level for fields of structs (default n = 1)"; + "-absval", Arg.Set_int Config.abs_val, Some "n", "abstraction level for expressions (default n = 2)"; + "-arraylevel", Arg.Set_int Config.array_level, Some "n", "the level of treating the array indexing and pointer arithmetic (default n = 0)"; + "-developer_mode", Arg.Set Config.developer_mode, None, "reserved"; + "-dotty", Arg.Set Config.write_dotty, None, "produce dotty files in the results directory"; + "-exit_node_bias", Arg.Unit (fun () -> Config.worklist_mode:= 1), None, "nodes nearest the exit node are analyzed first"; + "-html", Arg.Set Config.write_html, None, "produce hmtl output in the results directory"; + "-join_cond", Arg.Set_int Config.join_cond, Some "n", "set the strength of the final information-loss check used by the join (default n=1)"; + "-leak", Arg.Set Config.allowleak, None, "forget leaks during abstraction"; + "-max_procs", Arg.Set_int Config.max_num_proc, Some "n", "set the maximum number of processes to be used for parallel execution (default n=0)"; + "-monitor_prop_size", Arg.Set Config.monitor_prop_size, None, "monitor size of props"; + "-nelseg", Arg.Set Config.nelseg, None, "use only nonempty lsegs"; + "-noliveness", Arg.Clear Config.liveness, None, "turn the dead program variable elimination off"; + "-noprintdiff", Arg.Clear Config.print_using_diff, None, "turn off highlighting diff w.r.t. previous prop in printing"; + "-notest", Arg.Clear Config.test, None, "turn test mode off"; + "-num_cores", Arg.Set_int Config.num_cores, Some "n", "set the number of cores used in parallel by the analysis (default n=1)"; + "-only_footprint", Arg.Set Config.only_footprint, None, "skip the re-execution phase"; + "-print_types", Arg.Set Config.print_types, None, "print types in symbolic heaps"; + "-set_pp_margin", Arg.Int (fun i -> F.set_margin i), Some "n", "set right margin for the pretty printing functions"; + "-slice_fun", Arg.Set_string Config.slice_fun, None, "analyze only functions recursively called by function given as argument"; + "-spec_abs_level", Arg.Set_int Config.spec_abs_level, Some "n", "set the level of abstracting the postconditions of discovered specs (default n=1)"; + "-trace_error", Arg.Set Config.trace_error, None, "turn on tracing of error explanation"; + "-trace_join", Arg.Set Config.trace_join, None, "turn on tracing of join"; + "-trace_rearrange", Arg.Set Config.trace_rearrange, None, "turn on tracing of rearrangement"; + "-visits_bias", Arg.Unit (fun () -> Config.worklist_mode:= 2), None, "nodes visited fewer times are analyzed first"; + ] + +(**************** START MODULE Arg2 -- modified from Arg in the ocaml distribution ***************) +module Arg2 = struct + type key = string + type doc = string + type usage_msg = string + type anon_fun = (string -> unit) + + type spec = Arg.spec + + exception Bad of string + exception Help of string + + type error = + | Unknown of string + | Wrong of string * string * string (* option, actual, expected *) + | Missing of string + | Message of string + + exception Stop of error (* used internally *) + + open Printf + + let rec assoc3 x l = + match l with + | [] -> raise Not_found + | (y1, y2, y3) :: t when y1 = x -> y2 + | _ :: t -> assoc3 x t + + let make_symlist prefix sep suffix l = + match l with + | [] -> "" + | h:: t -> (list_fold_left (fun x y -> x ^ sep ^ y) (prefix ^ h) t) ^ suffix + + let print_spec buf (key, spec, doc) = + match spec with + | Arg.Symbol (l, _) -> bprintf buf " %s %s%s\n" key (make_symlist "{" "|" "}" l) + doc + | _ -> + let sep = if String.length doc != 0 && String.get doc 0 = '=' then "" else " " in + bprintf buf " %s%s%s\n" key sep doc + + let help_action () = raise (Stop (Unknown "-help")) + + let add_help speclist = + let add1 = + try ignore (assoc3 "-help" speclist); [] + with Not_found -> + ["-help", Arg.Unit help_action, " Display this list of options"] + and add2 = + try ignore (assoc3 "--help" speclist); [] + with Not_found -> + ["--help", Arg.Unit help_action, " Display this list of options"] + in + speclist @ (add1 @ add2) + + let usage_b buf speclist errmsg = + bprintf buf "%s\n" errmsg; + list_iter (print_spec buf) (add_help speclist) + + let usage speclist errmsg = + let b = Buffer.create 200 in + usage_b b speclist errmsg; + eprintf "%s" (Buffer.contents b) + let current = ref 0;; + + let parse_argv ?(current = current) argv speclist anonfun errmsg = + let l = Array.length argv in + let b = Buffer.create 200 in + let initpos = !current in + let stop error = + let progname = if initpos < l then argv.(initpos) else "(?)" in + begin match error with + | Unknown "-help" -> () + | Unknown "--help" -> () + | Unknown s -> + bprintf b "%s: unknown option `%s'.\n" progname s + | Missing s -> + bprintf b "%s: option `%s' needs an argument.\n" progname s + | Wrong (opt, arg, expected) -> + bprintf b "%s: wrong argument `%s'; option `%s' expects %s.\n" + progname arg opt expected + | Message s -> + bprintf b "%s: %s.\n" progname s + end; + usage_b b speclist errmsg; + if error = Unknown "-help" || error = Unknown "--help" + then raise (Help (Buffer.contents b)) + else raise (Bad (Buffer.contents b)) + in + incr current; + while !current < l do + let s = argv.(!current) in + if String.length s >= 1 && String.get s 0 = '-' then begin + let action = + try assoc3 s speclist + with Not_found -> stop (Unknown s) + in + begin try + let rec treat_action = function + | Arg.Unit f -> f (); + | Arg.Bool f when !current + 1 < l -> + let arg = argv.(!current + 1) in + begin try f (bool_of_string arg) + with Invalid_argument "bool_of_string" -> + raise (Stop (Wrong (s, arg, "a boolean"))) + end; + incr current; + | Arg.Set r -> r := true; + | Arg.Clear r -> r := false; + | Arg.String f when !current + 1 < l -> + f argv.(!current + 1); + incr current; + | Arg.Symbol (symb, f) when !current + 1 < l -> + let arg = argv.(!current + 1) in + if list_mem string_equal arg symb then begin + f argv.(!current + 1); + incr current; + end else begin + raise (Stop (Wrong (s, arg, "one of: " + ^ (make_symlist "" " " "" symb)))) + end + | Arg.Set_string r when !current + 1 < l -> + r := argv.(!current + 1); + incr current; + | Arg.Int f when !current + 1 < l -> + let arg = argv.(!current + 1) in + begin try f (int_of_string arg) + with Failure "int_of_string" -> + raise (Stop (Wrong (s, arg, "an integer"))) + end; + incr current; + | Arg.Set_int r when !current + 1 < l -> + let arg = argv.(!current + 1) in + begin try r := (int_of_string arg) + with Failure "int_of_string" -> + raise (Stop (Wrong (s, arg, "an integer"))) + end; + incr current; + | Arg.Float f when !current + 1 < l -> + let arg = argv.(!current + 1) in + begin try f (float_of_string arg); + with Failure "float_of_string" -> + raise (Stop (Wrong (s, arg, "a float"))) + end; + incr current; + | Arg.Set_float r when !current + 1 < l -> + let arg = argv.(!current + 1) in + begin try r := (float_of_string arg); + with Failure "float_of_string" -> + raise (Stop (Wrong (s, arg, "a float"))) + end; + incr current; + | Arg.Tuple specs -> + list_iter treat_action specs; + | Arg.Rest f -> + while !current < l - 1 do + f argv.(!current + 1); + incr current; + done; + | _ -> raise (Stop (Missing s)) + in + treat_action action + with Bad m -> stop (Message m); + | Stop e -> stop e; + end; + incr current; + end else begin + (try anonfun s with Bad m -> stop (Message m)); + incr current; + end; + done + + let parse l f msg = + try + parse_argv Sys.argv l f msg; + with + | Bad msg -> eprintf "%s" msg; exit 2; + | Help msg -> printf "%s" msg; exit 0 + + (** Custom version of Arg.aling so that keywords are on one line and documentation is on the next *) + let align arg_desc = + let do_arg (key, spec, doc) = + let first_space = + try + let index = String.index doc ' ' in + if String.get doc index = '=' then 0 else index + with Not_found -> 0 in + let len = String.length doc in + let doc1 = String.sub doc 0 first_space in + let doc2 = String.sub doc first_space (len - first_space) in + if len = 0 then (key, spec, doc) + else (key, spec, doc1 ^ "\n " ^ doc2) in + list_map do_arg arg_desc + + type aligned = (key * spec * doc) + + let to_arg_desc x = x + let from_arg_desc x = x + + (** Create a group of sorted command-line arguments *) + let create_options_desc double_minus title unsorted_desc = + let handle_double_minus (opname, spec, param_opt, text) = match param_opt with + | None -> + if double_minus then ("-"^opname, spec, " " ^ text) + else (opname, spec, " " ^ text) + | Some param -> + if double_minus then ("-"^opname, spec, "=" ^ param ^ " " ^ text) + else (opname, spec, param ^ " " ^ text) in + let unsorted_desc' = list_map handle_double_minus unsorted_desc in + let dlist = + ("", Arg.Unit (fun () -> ()), " \n " ^ title ^ "\n") :: + list_sort (fun (x, _, _) (y, _, _) -> Pervasives.compare x y) unsorted_desc' in + align dlist +end +(********** END OF MODULE Arg2 **********) + +(** Escape a string for use in a CSV or XML file: replace reserved characters with escape sequences *) +module Escape = struct + (** apply a map function for escape sequences *) + let escape_map map_fun s = + let len = String.length s in + let buf = Buffer.create len in + for i = 0 to len - 1 do + let c = String.unsafe_get s i in + match map_fun c with + | None -> Buffer.add_char buf c + | Some s' -> Buffer.add_string buf s' + done; + Buffer.contents buf + + let escape_csv s = + let map = function + | '"' -> Some "\"\"" + | c when Char.code c > 127 -> Some "?" (* non-ascii character: escape *) + | _ -> None in + escape_map map s + + let escape_xml s = + let map = function + | '"' -> (* on next line to avoid bad indentation *) + Some """ + | '>' -> Some ">" + | '<' -> Some "<" + | '&' -> Some "&" + | '%' -> Some "%" + | c when Char.code c > 127 -> (* non-ascii character: escape *) + Some ("&#" ^ string_of_int (Char.code c) ^ ";") + | _ -> None in + escape_map map s + + let escape_dotty s = + let map = function + | '"' -> Some "\\\"" + | '\\' -> Some "\\\\" + | _ -> None in + escape_map map s + + let escape_path s = + let map_csv = function + | c -> + if (Char.escaped c) = Filename.dir_sep + then Some "_" + else None in + escape_map map_csv s +end + + +(** flags for a procedure, these can be set programmatically by __infer_set_flag: see frontend.ml *) +type proc_flags = (string, string) Hashtbl.t + +let proc_flags_empty () : proc_flags = Hashtbl.create 1 + +let proc_flag_iterations = "iterations" +let proc_flag_skip = "skip" +let proc_flag_ignore_return = "ignore_return" + +let proc_flags_add proc_flags key value = + Hashtbl.replace proc_flags key value + +let proc_flags_find proc_flags key = + Hashtbl.find proc_flags key + +let join_strings sep = function + | [] -> "" + | hd:: tl -> + list_fold_left (fun str p -> str ^ sep ^ p) hd tl + +let next compare = + fun x y n -> + if n <> 0 then n + else compare x y + + +let directory_fold f init path = + let collect current_dir (accu, dirs) path = + let full_path = (Filename.concat current_dir path) in + try + if Sys.is_directory full_path then + (accu, full_path:: dirs) + else + (f accu full_path, dirs) + with Sys_error _ -> + (accu, dirs) in + let rec loop accu dirs = + match dirs with + | [] -> accu + | d:: tl -> + let (new_accu, new_dirs) = Array.fold_left (collect d) (accu, tl) (Sys.readdir d) in + loop new_accu new_dirs in + if Sys.is_directory path then + loop init [path] + else + f init path + + +let directory_iter f path = + let apply current_dir dirs path = + let full_path = (Filename.concat current_dir path) in + try + if Sys.is_directory full_path then + full_path:: dirs + else + let () = f full_path in + dirs + with Sys_error _ -> + dirs in + let rec loop dirs = + match dirs with + | [] -> () + | d:: tl -> + let new_dirs = Array.fold_left (apply d) tl (Sys.readdir d) in + loop new_dirs in + if Sys.is_directory path then + loop [path] + else + f path + +type analyzer = Infer | Eradicate | Checkers | Tracing + +let analyzers = [Infer; Eradicate; Checkers; Tracing] + +let string_of_analyzer = function + | Infer -> "infer" + | Eradicate -> "eradicate" + | Checkers -> "checkers" + | Tracing -> "tracing" + +exception Unknown_analyzer + +let analyzer_of_string = function + | "infer" -> Infer + | "eradicate" -> Eradicate + | "checkers" -> Checkers + | "tracing" -> Tracing + | _ -> raise Unknown_analyzer diff --git a/infer/src/backend/utils.mli b/infer/src/backend/utils.mli new file mode 100644 index 000000000..8e98e97ed --- /dev/null +++ b/infer/src/backend/utils.mli @@ -0,0 +1,434 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** General utility functions *) + +(** {2 Generic Utility Functions} *) + +(** Compare police: generic compare disabled. *) +val compare : unit + +(** Comparison for booleans *) +val bool_compare : bool -> bool -> int + +(** Equality for booleans *) +val bool_equal : bool -> bool -> bool + +(** Efficient comparison for integers *) +val int_compare : int -> int -> int + +(** Equality for integers *) +val int_equal : int -> int -> bool + +(** Extend and equality function to an option type. *) +val opt_equal : ('a -> 'a -> bool) -> 'a option -> 'a option -> bool + +(** Generic comparison of pairs given a compare function for each element of the pair. *) +val pair_compare : ('a -> 'b -> int) -> ('c -> 'd -> int) -> ('a * 'c) -> ('b * 'd) -> int + +(** Generic comparison of pairs given a compare function for each element of the triple. *) +val triple_compare : ('a -> 'b -> int) -> ('c -> 'd -> int) -> ('e -> 'f -> int) -> ('a * 'c * 'e) -> ('b * 'd * 'f) -> int + +(** Generic comparison of lists given a compare function for the elements of the list *) +val list_compare : ('a -> 'b -> int) -> 'a list -> 'b list -> int + +(** Comparison for strings *) +val string_compare : string -> string -> int + +(** Equality for strings *) +val string_equal : string -> string -> bool + +(** Comparison for floats *) +val float_compare : float -> float -> int + +(** tail-recursive variant of List.append *) +val list_append : 'a list -> 'a list -> 'a list + +(** tail-recursive variant of List.combine *) +val list_combine : 'a list -> 'b list -> ('a * 'b) list + +val list_exists : ('a -> bool) -> 'a list -> bool +val list_filter : ('a -> bool) -> 'a list -> 'a list + +(** tail-recursive variant of List.flatten *) +val list_flatten : 'a list list -> 'a list + +(** Remove all None elements from the list. *) +val list_flatten_options : ('a option) list -> 'a list + +val list_find : ('a -> bool) -> 'a list -> 'a +val list_fold_left : ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a +val list_fold_left2 : ('a -> 'b -> 'c -> 'a) -> 'a -> 'b list -> 'c list -> 'a +val list_for_all : ('a -> bool) -> 'a list -> bool +val list_for_all2 : ('a -> 'b -> bool) -> 'a list -> 'b list -> bool +val list_hd : 'a list -> 'a +val list_iter : ('a -> unit) -> 'a list -> unit +val list_iter2 : ('a -> 'b -> unit) -> 'a list -> 'b list -> unit +val list_length : 'a list -> int + +(** tail-recursive variant of List.fold_right *) +val list_fold_right : ('a -> 'b -> 'b) -> 'a list -> 'b -> 'b + +(** tail-recursive variant of List.map *) +val list_map : ('a -> 'b) -> 'a list -> 'b list + +(** Like List.mem but without builtin equality *) +val list_mem : ('a -> 'b -> bool) -> 'a -> 'b list -> bool + +val list_nth : 'a list -> int -> 'a +val list_partition : ('a -> bool) -> 'a list -> 'a list * 'a list +val list_rev : 'a list -> 'a list +val list_rev_append : 'a list -> 'a list -> 'a list +val list_rev_map : ('a -> 'b) -> 'a list -> 'b list +val list_sort : ('a -> 'a -> int) -> 'a list -> 'a list + +(** tail-recursive variant of List.split *) +val list_split : ('a * 'b) list -> 'a list * 'b list + +val list_stable_sort : ('a -> 'a -> int) -> 'a list -> 'a list +val list_tl : 'a list -> 'a list + +(* Drops the first n elements from a list. *) +val list_drop_first : int -> 'a list -> 'a list + +(* Drops the last n elements from a list. *) +val list_drop_last : int -> 'a list -> 'a list + +(** List police: don't use the list module to avoid non-tail-recursive functions and builtin equality *) +module List : sig end + +(** Returns (reverse input_list)[@]acc *) +val list_rev_with_acc : 'a list -> 'a list -> 'a list + +(** Remove consecutive equal elements from a list (according to the given comparison functions) *) +val list_remove_duplicates : ('a -> 'a -> int) -> 'a list -> 'a list + +(** Remove consecutive equal irrelevant elements from a list (according to the given comparison and relevance functions) *) +val list_remove_irrelevant_duplicates : ('a -> 'a -> int) -> ('a -> bool) -> 'a list -> 'a list + +(** The function works on sorted lists without duplicates *) +val list_merge_sorted_nodup : ('a -> 'a -> int) -> 'a list -> 'a list -> 'a list -> 'a list + +(** Returns whether there is an intersection in the elements of the two lists. +The compare function is required to sort the lists. *) +val list_intersect : ('a -> 'a -> int) -> 'a list -> 'a list -> bool + +exception Fail + +(** Apply [f] to pairs of elements; raise [Fail] if the two lists have different lenghts. *) +val list_map2 : ('a -> 'b -> 'c) -> 'a list -> 'b list -> 'c list + +val list_to_string : ('a -> string) -> 'a list -> string + +(** {2 Useful Modules} *) + +(** Set of integers *) +module IntSet : Set.S with type elt = int + +(** Set of strings *) +module StringSet : Set.S with type elt = string + +(** Pretty print a set of strings *) +val pp_stringset : Format.formatter -> StringSet.t -> unit + +(** Maps from strings *) +module StringMap : Map.S with type key = string + +(** {2 Printing} *) + +(** Type of location in ml source: file,line,column *) +type ml_location = string * int * int + +(** Turn an ml location into a string *) +val ml_location_string : ml_location -> string + +(** Pretty print a location of ml source *) +val pp_ml_location_opt : Format.formatter -> ml_location option -> unit + +(** Colors supported in printing *) +type color = Black | Blue | Green | Orange | Red + +(** map subexpressions (as Obj.t element compared by physical equality) to colors *) +type colormap = Obj.t -> color + +(** Kind of simple printing: default or with full types *) +type pp_simple_kind = PP_SIM_DEFAULT | PP_SIM_WITH_TYP + +(** Kind of printing *) +type printkind = PP_TEXT | PP_LATEX | PP_HTML + +(** Print environment threaded through all the printing functions *) +type printenv = { + pe_opt : pp_simple_kind; (** Current option for simple printing *) + pe_kind : printkind; (** Current kind of printing *) + pe_cmap_norm : colormap; (** Current colormap for the normal part *) + pe_cmap_foot : colormap; (** Current colormap for the footprint part *) + pe_color : color; (** Current color *) + pe_obj_sub : (Obj.t -> Obj.t) option (** generic object substitution *) +} + +(** Reset the object substitution, so that no substitution takes place *) +val pe_reset_obj_sub : printenv -> printenv + +(** Set the object substitution, which is supposed to preserve the type. +Currently only used for a map from (identifier) expressions to the program var containing them *) +val pe_set_obj_sub : printenv -> ('a -> 'a) -> printenv + +(** standard colormap: black *) +val colormap_black : colormap + +(** red colormap *) +val colormap_red : colormap + +(** Extend the normal colormap for the given object with the given color *) +val pe_extend_colormap : printenv -> Obj.t -> color -> printenv + +(** Default text print environment *) +val pe_text : printenv + +(** Default html print environment *) +val pe_html : color -> printenv + +(** Default latex print environment *) +val pe_latex : color -> printenv + +(** string representation of colors *) +val color_string : color -> string + +(** Pretty print a space-separated sequence *) +val pp_seq : (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a list -> unit + +(** Pretty print a comma-separated sequence *) +val pp_comma_seq : (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a list -> unit + +(** Pretty print a ;-separated sequence *) +val pp_semicolon_seq : printenv -> (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a list -> unit + +(** Pretty print a ;-separated sequence on one line *) +val pp_semicolon_seq_oneline : printenv -> (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a list -> unit + +(** Pretty print a or-separated sequence *) +val pp_or_seq : printenv -> (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a list -> unit + +(** Produce a string from a 1-argument pretty printer function *) +val pp_to_string : (Format.formatter -> 'a -> unit) -> 'a -> string + +(** Print the current time and date in a format similar to the "date" command *) +val pp_current_time : Format.formatter -> unit -> unit + +(** Print the time in seconds elapsed since the beginning of the execution of the current command. *) +val pp_elapsed_time : Format.formatter -> unit -> unit + +(** {2 SymOp and Timeouts: units of symbolic execution} *) + +(** initial time of the analysis, i.e. when this module is loaded, gotten from Unix.time *) +val initial_analysis_time : float + +(** number of symops to multiply by the number of iterations, after which there is a timeout *) +val symops_per_iteration : int ref + +(** number of seconds to multiply by the number of iterations, after which there is a timeout *) +val seconds_per_iteration : int ref + +(** timeout value from the -iterations command line option *) +val iterations_cmdline : int ref + +(** Timeout in seconds for each function *) +val get_timeout_seconds : unit -> int + +(** Set the timeout values in seconds and symops, computed as a multiple of the integer parameter *) +val set_iterations : int -> unit + +type timeout_kind = + | TOtime (* max time exceeded *) + | TOsymops of int (* max symop's exceeded *) + | TOrecursion of int (* max recursion level exceeded *) + +(** Timeout exception *) +exception Timeout_exe of timeout_kind + +(** check that the exception is not a timeout exception *) +val exn_not_timeout : exn -> bool + +(** Count the number of symbolic operations *) +module SymOp : sig +(** Count one symop *) + val pay : unit -> unit + + (** Reset the counter and activate the alarm *) + val set_alarm : unit -> unit + + (** De-activate the alarm *) + val unset_alarm : unit -> unit + + (** set the handler for the wallclock timeout *) + val set_wallclock_timeout_handler : (unit -> unit) -> unit + + (** Set the wallclock alarm checked at every pay() *) + val set_wallclock_alarm : int -> unit + + (** Unset the wallclock alarm checked at every pay() *) + val unset_wallclock_alarm : unit -> unit + + (** if the wallclock alarm has expired, raise a timeout exception *) + val check_wallclock_alarm : unit -> unit + + (** Return the total number of symop's since the beginning *) + val get_total : unit -> int + + (** Reset the total number of symop's *) + val reset_total : unit -> unit + + (** Report the stats since the last reset *) + val report : Format.formatter -> unit -> unit + + (** Report the stats since the loading of this module *) + val report_total : Format.formatter -> unit -> unit +end + +(** Modified version of Arg module from the ocaml distribution *) +module Arg2 : sig + type spec = Arg.spec + + type key = string + type doc = string + type usage_msg = string + type anon_fun = (string -> unit) + + val current : int ref + + (** type of aligned commend-line options *) + type aligned + + val align : (key * spec * doc) list -> aligned list + + val parse : aligned list -> anon_fun -> usage_msg -> unit + + val usage : aligned list -> usage_msg -> unit + + val to_arg_desc : aligned -> (key * spec * doc) + val from_arg_desc : (key * spec * doc) -> aligned + + (** [create_options_desc double_minus unsorted_desc title] creates a group of sorted command-line arguments. + [double_minus] is a booleand indicating whether the [-- option = nn] format or [- option n] format is to be used. + [title] is the title of this group of options. + It expects a list [opname, desc, param_opt, text] where + [opname] is the name of the option + [desc] is the Arg.spec + [param_opt] is the optional parameter to [opname] + [text] is the description of the option *) + val create_options_desc : bool -> string -> (string * Arg.spec * string option * string) list -> aligned list + +end + +(** Check if the lhs is a substring of the rhs. *) +val string_is_prefix : string -> string -> bool + +(** Check if the lhs is a suffix of the rhs. *) +val string_is_suffix : string -> string -> bool + +(** Check if the lhs is contained in the rhs. *) +val string_contains : string -> string -> bool + +(** Split a string across the given character, if given. (e.g. split first.second with '.').*) +val string_split_character : string -> char -> string option * string + +(** The value of a string option or the empty string.: *) +val string_value_or_empty_string : string option -> string + +(** copy a source file, return the number of lines, or None in case of error *) +val copy_file : string -> string -> int option + +(** read a source file and return a list of lines, or None in case of error *) +val read_file : string -> string list option + +(** Convert a filename to an absolute one if it is relative, and normalize "." and ".." *) +val filename_to_absolute : string -> string + +(** Convert an absolute filename to one relative to a root directory *) +val filename_to_relative : string -> string -> string + +module FileLOC : (** count lines of code of files and keep processed results in a cache *) +sig + val reset: unit -> unit (** reset the cache *) + val file_get_loc : string -> int (** get the LOC of the file *) +end + +(** type for files used for printing *) +type outfile = + { fname : string; (** name of the file *) + out_c : out_channel; (** output channel *) + fmt : Format.formatter (** formatter for printing *) } + +(** create an outfile for the command line, the boolean indicates whether to do demangling when closing the file *) +val create_outfile : string -> outfile option + +(** operate on an outfile reference if it is not None *) +val do_outf : outfile option ref -> (outfile -> unit) -> unit + +(** close an outfile *) +val close_outf : outfile -> unit + +(** Basic command-line arguments *) +val base_arg_desc : (string * Arg.spec * string option * string) list + +(** Reserved command-line arguments *) +val reserved_arg_desc : (string * Arg.spec * string option * string) list + +(** Escape a string for use in a CSV or XML file: replace reserved characters with escape sequences *) +module Escape : sig +(** escape a string specifying the per character escaping function *) + val escape_map : (char -> string option) -> string -> string + val escape_dotty : string -> string (** escape a string to be used in a dotty file *) + val escape_csv : string -> string (** escape a string to be used in a csv file *) + val escape_path : string -> string (** escape a path replacing the directory separator with an underscore *) + val escape_xml : string -> string (** escape a string to be used in an xml file *) +end + + +(** flags for a procedure, these can be set programmatically by __infer_set_flag: see frontend.ml *) +type proc_flags = (string, string) Hashtbl.t + +(** keys for proc_flags *) +val proc_flag_iterations : string (** key to specify procedure-specific iterations *) +val proc_flag_skip : string (** key to specify that a function should be treated as a skip function *) +val proc_flag_ignore_return : string (** key to specify that it is OK to ignore the return value *) + +(** empty proc flags *) +val proc_flags_empty : unit -> proc_flags + +(** add a key value pair to a proc flags *) +val proc_flags_add : proc_flags -> string -> string -> unit + +(** find a value for a key in the proc flags *) +val proc_flags_find : proc_flags -> string -> string + +(** [join_strings sep parts] contatenates the elements of [parts] using [sep] as separator *) +val join_strings : string -> string list -> string + +(** [next compare] transforms the comparison function [compare] to another function taking +the outcome of another comparison as last parameter and only performs this comparison if this value +is different from 0. Useful to combine comparison functions using the operator |>. The outcome of +the expression [Int.compare x y |> next Set.compare s t] is: [Int.compare x y] if this value is +not [0], skipping the evaluation of [Set.compare s t] in such case; or [Set.compare s t] in case +[Int.compare x y] is [0] *) +val next : ('a -> 'a -> int) -> ('a -> 'a -> int -> int) + +(** Functional fold function over all the file of a directory *) +val directory_fold : ('a -> string -> 'a) -> 'a -> string -> 'a + +(** Functional iter function over all the file of a directory *) +val directory_iter : (string -> unit) -> string -> unit + +(** Various kind of analyzers *) +type analyzer = Infer | Eradicate | Checkers | Tracing + +(** List of analyzers *) +val analyzers: analyzer list + +val string_of_analyzer: analyzer -> string + +val analyzer_of_string: string -> analyzer diff --git a/infer/src/backend/version.ml.in b/infer/src/backend/version.ml.in new file mode 100644 index 000000000..81b5d4cee --- /dev/null +++ b/infer/src/backend/version.ml.in @@ -0,0 +1,24 @@ +(* + * Copyright (c) 2009-2013 Monoidics ltd. + * Copyright (c) 2013- Facebook. + * All rights reserved. + *) + +let major = @MAJOR@ +let minor = @MINOR@ +let patch = @PATCH@ +let commit = "@GIT_COMMIT@" +let branch = "@GIT_BRANCH@" +let tag = "@GIT_TAG@" + +let versionString = + if tag = "" then "git-" ^ commit + else tag + +let versionJson = String.concat "\n" [ + "{"; "\"major\": " ^ (string_of_int major) ^ ", "; + "\"minor\": " ^ (string_of_int minor) ^ ", "; + "\"patch\": " ^ (string_of_int patch) ^ ", "; + "\"commit\": \"" ^ commit ^ "\", "; + "\"branch\": \"" ^ branch ^ "\", "; + "\"tag\": \"" ^ tag ^ "\""; "}" ] diff --git a/infer/src/checkers/annotations.ml b/infer/src/checkers/annotations.ml new file mode 100644 index 000000000..0fccf43c2 --- /dev/null +++ b/infer/src/checkers/annotations.ml @@ -0,0 +1,254 @@ +(* +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +module F = Format +module L = Logging +open Utils + +(** Annotations. *) + +(** Method signature with annotations. *) +type annotated_signature = + { ret : Sil.item_annotation * Sil.typ; (** Annotated return type. *) + params: (string * Sil.item_annotation * Sil.typ) list } (** Annotated parameters. *) + +let param_equal (s1, ia1, t1) (s2, ia2, t2) = + string_equal s1 s2 && + Sil.item_annotation_compare ia1 ia2 = 0 && + Sil.typ_equal t1 t2 + +let equal as1 as2 = + let ia1, t1 = as1.ret + and ia2, t2 = as2.ret in + Sil.item_annotation_compare ia1 ia2 = 0 && + Sil.typ_equal t1 t2 && + list_for_all2 param_equal as1.params as2.params + +let visibleForTesting = "com.google.common.annotations.VisibleForTesting" +let javaxNullable = "javax.annotation.Nullable" +let javaxNonnull = "javax.annotation.Nonnull" +let suppressLint = "android.annotation.SuppressLint" + + +let get_field_type_and_annotation fn = function + | Sil.Tptr (Sil.Tstruct (ftal, sftal, _, _, _, _, _), _) + | Sil.Tstruct (ftal, sftal, _, _, _, _, _) -> + (try + let (_, t, a) = list_find (fun (f, t, a) -> Sil.fld_equal f fn) (ftal @ sftal) in + Some (t, a) + with Not_found -> None) + | _ -> None + +let ia_iter f = + let ann_iter (a, b) = f a in + list_iter ann_iter + +let ma_iter f ((ia, ial) : Sil.method_annotation) = + list_iter (ia_iter f) (ia:: ial) + +let ma_has_annotation_with + (ma: Sil.method_annotation) + (predicate: Sil.annotation -> bool): bool = + let found = ref false in + ma_iter + (fun a -> if predicate a then found := true) + ma; + !found + +let ia_has_annotation_with + (ia: Sil.item_annotation) + (predicate: Sil.annotation -> bool): bool = + let found = ref false in + ia_iter + (fun a -> if predicate a then found := true) + ia; + !found + +(** Check if there is an annotation which ends with the given name *) +let ia_ends_with ia ann_name = + let found = ref false in + let filter s = + let sl = String.length s in + let al = String.length ann_name in + sl >= al && String.sub s (sl - al) al = ann_name in + ia_iter (fun a -> if filter a.Sil.class_name then found := true) ia; + !found + +let ia_contains ia ann_name = + let found = ref false in + ia_iter (fun a -> if ann_name = a.Sil.class_name then found := true) ia; + !found + +let ia_get ia ann_name = + let found = ref None in + ia_iter (fun a -> if ann_name = a.Sil.class_name then found := Some a) ia; + !found + +let ma_contains ma ann_names = + let found = ref false in + ma_iter (fun a -> if list_exists (string_equal a.Sil.class_name) ann_names then found := true) ma; + !found + +let initializer_ = "com.facebook.infer.annotation.Initializer" +let inject = "Inject" +let mutable_ = "Mutable" +let nullable = "Nullable" +let nonnull = "Nonnull" +let notnull = "NotNull" +let present = "Present" +let strict = "com.facebook.infer.annotation.Strict" +let verify_annotation = "com.facebook.infer.annotation.Verify" + +let ia_is_nullable ia = + ia_ends_with ia nullable + +let ia_is_present ia = + ia_ends_with ia present + +let ia_is_nonnull ia = + ia_ends_with ia nonnull || ia_ends_with ia notnull + +let ia_is_initializer ia = + ia_contains ia initializer_ + +let ia_is_inject ia = + ia_ends_with ia inject + +let ia_is_mutable ia = + ia_ends_with ia mutable_ + +let ia_get_strict ia = + ia_get ia strict + +let ia_is_verify ia = + ia_contains ia verify_annotation + +type annotation = + | Nullable + | Present + +let ia_is ann ia = match ann with + | Nullable -> ia_is_nullable ia + | Present -> ia_is_present ia + +type get_method_annotation = Procname.t -> Cfg.Procdesc.t -> Sil.method_annotation + +(** Get a method signature with annotations from a proc_name and proc_desc, +or search in the .specs file if it is not defined in the proc_desc. *) +let get_annotated_signature get_method_annotation proc_desc proc_name : annotated_signature = + let method_annotation = get_method_annotation proc_name proc_desc in + let formals = Cfg.Procdesc.get_formals proc_desc in + let ret_type = Cfg.Procdesc.get_ret_type proc_desc in + let (ia, ial) = method_annotation in + let natl = + let rec extract ial parl = match ial, parl with + | ia :: ial', (name, typ) :: parl' -> (name, ia, typ) :: extract ial' parl' + | [], (name, typ) :: parl' -> (name, Sil.item_annotation_empty, typ) :: extract [] parl' + | [], [] -> [] + | _ :: _, [] -> assert false in + list_rev (extract (list_rev ial) (list_rev formals)) in + let annotated_signature = { ret = (ia, ret_type); params = natl } in + annotated_signature + +(** Check if the annotated signature is for a wrapper of an anonymous inner class method. +These wrappers have the same name as the original method, every type is Object, and the parameters +are called x0, x1, x2. *) +let annotated_signature_is_anonymous_inner_class_wrapper ann_sig proc_name = + let check_ret (ia, t) = + Sil.item_annotation_is_empty ia && PatternMatch.type_is_object t in + let x_param_found = ref false in + let name_is_x_number name = + let len = String.length name in + len >= 2 && + String.sub name 0 1 = "x" && + let s = String.sub name 1 (len - 1) in + let is_int = + try + ignore (int_of_string s); + x_param_found := true; + true + with Failure _ -> false in + is_int in + let check_param (name, ia, t) = + if name = "this" then true + else + name_is_x_number name && + Sil.item_annotation_is_empty ia && + PatternMatch.type_is_object t in + Procname.java_is_anonymous_inner_class proc_name + && check_ret ann_sig.ret + && list_for_all check_param ann_sig.params + && !x_param_found + +(** Check if the given parameter has a Nullable annotation in the given signature *) +let param_is_nullable pvar ann_sig = + let pvar_str = Mangled.to_string (Sil.pvar_get_name pvar) in + list_exists + (fun (param_str, annot, _) -> param_str = pvar_str && ia_is_nullable annot) + ann_sig.params + +(** Pretty print a method signature with annotations. *) +let pp_annotated_signature proc_name fmt annotated_signature = + let pp_ia fmt ia = if ia <> [] then F.fprintf fmt "%a " Sil.pp_item_annotation ia in + let pp_annotated_param fmt (s, ia, t) = + F.fprintf fmt " %a%a %s" pp_ia ia (Sil.pp_typ_full pe_text) t s in + let ia, ret_type = annotated_signature.ret in + F.fprintf fmt "%a%a %s (%a )" + pp_ia ia + (Sil.pp_typ_full pe_text) ret_type + (Procname.to_simplified_string proc_name) + (pp_comma_seq pp_annotated_param) annotated_signature.params + +let mk_ann_str s = { Sil.class_name = s; Sil.parameters = [] } +let mk_ann = function + | Nullable -> mk_ann_str nullable + | Present -> mk_ann_str present +let mk_ia ann ia = + if ia_is ann ia then ia + else (mk_ann ann, true) :: ia +let mark_ia ann ia x = + if x then mk_ia ann ia else ia + +let mk_ia_strict ia = + if ia_get_strict ia <> None then ia + else (mk_ann_str strict, true) :: ia +let mark_ia_strict ia x = + if x then mk_ia_strict ia else ia + +(** Mark the annotated signature with the given annotation map. *) +let annotated_signature_mark proc_name ann asig (b, bs) = + let ia, t = asig.ret in + let ret' = mark_ia ann ia b, t in + let mark_param (s, ia, t) x = + let ia' = if x then mk_ia ann ia else ia in + (s, ia', t) in + let params' = + let fail () = + L.stdout + "INTERNAL ERROR: annotation for procedure %s has wrong number of arguments@." + (Procname.to_unique_id proc_name); + L.stdout " ANNOTATED SIGNATURE: %a@." (pp_annotated_signature proc_name) asig; + assert false in + let rec combine l1 l2 = match l1, l2 with + | ("this", ia, t):: l1', l2' -> + ("this", ia, t) :: combine l1' l2' + | (s, ia, t):: l1', x:: l2' -> + mark_param (s, ia, t) x :: combine l1' l2' + | [], _:: _ -> fail () + | _:: _, [] -> fail () + | [], [] -> [] in + combine asig.params bs in + { ret = ret'; params = params'} + +(** Mark the return of the annotated signature with the given annotation. *) +let annotated_signature_mark_return proc_name ann asig = + let ia, t = asig.ret in + let ret' = mark_ia ann ia true, t in + { asig with ret = ret'} + +(** Mark the return of the annotated signature @Strict. *) +let annotated_signature_mark_return_strict proc_name asig = + let ia, t = asig.ret in + let ret' = mark_ia_strict ia true, t in + { asig with ret = ret'} diff --git a/infer/src/checkers/annotations.mli b/infer/src/checkers/annotations.mli new file mode 100644 index 000000000..1e679aca8 --- /dev/null +++ b/infer/src/checkers/annotations.mli @@ -0,0 +1,77 @@ +(* +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +(** Annotations. *) + +val suppressLint : string + +type annotation = + | Nullable + | Present + +(** Method signature with annotations. *) +type annotated_signature = + { ret : Sil.item_annotation * Sil.typ; (** Annotated return type. *) + params: (string * Sil.item_annotation * Sil.typ) list } (** Annotated parameters. *) + +(** Check if the annotated signature is for a wrapper of an anonymous inner class method. +These wrappers have the same name as the original method, every type is Object, and the parameters +are called x0, x1, x2. *) +val annotated_signature_is_anonymous_inner_class_wrapper : annotated_signature -> Procname.t -> bool + +(** Check if the given parameter has a Nullable annotation in the given signature *) +val param_is_nullable : Sil.pvar -> annotated_signature -> bool + +(** Mark the annotated signature with the given annotation map. *) +val annotated_signature_mark : +Procname.t -> annotation -> annotated_signature -> bool * bool list -> annotated_signature + +(** Mark the return of the annotated signature with the given annotation. *) +val annotated_signature_mark_return : +Procname.t -> annotation -> annotated_signature -> annotated_signature + +(** Mark the return of the annotated signature @Strict. *) +val annotated_signature_mark_return_strict : +Procname.t -> annotated_signature -> annotated_signature + +val equal : annotated_signature -> annotated_signature -> bool + +type get_method_annotation = Procname.t -> Cfg.Procdesc.t -> Sil.method_annotation + +(** Get the annotated signature of the procedure *) +val get_annotated_signature : +get_method_annotation -> Cfg.Procdesc.t -> Procname.t -> annotated_signature + +(** Return the type of the field [fn] and its annotation, None if [typ] has no field named [fn] *) +val get_field_type_and_annotation : +Ident.fieldname -> Sil.typ -> (Sil.typ * Sil.item_annotation) option + +val ia_contains : Sil.item_annotation -> string -> bool + +val ia_has_annotation_with : Sil.item_annotation -> (Sil.annotation -> bool) -> bool + +val ia_get_strict : Sil.item_annotation -> Sil.annotation option + +val ia_is_initializer : Sil.item_annotation -> bool +val ia_is_inject : Sil.item_annotation -> bool +val ia_is_mutable : Sil.item_annotation -> bool +val ia_is_nullable : Sil.item_annotation -> bool +val ia_is_nonnull : Sil.item_annotation -> bool +val ia_is_present : Sil.item_annotation -> bool +val ia_is_verify : Sil.item_annotation -> bool + +val ia_iter : (Sil.annotation -> unit) -> Sil.item_annotation -> unit + +val ma_contains : Sil.method_annotation -> string list -> bool + +val ma_has_annotation_with : Sil.method_annotation -> (Sil.annotation -> bool) -> bool + +val ma_iter : (Sil.annotation -> unit) -> Sil.method_annotation -> unit + +(** Add the annotation to the item_annotation. *) +val mk_ia : annotation -> Sil.item_annotation -> Sil.item_annotation + +val pp_annotated_signature : Procname.t -> Format.formatter -> annotated_signature -> unit + +val visibleForTesting : string diff --git a/infer/src/checkers/callbackChecker.ml b/infer/src/checkers/callbackChecker.ml new file mode 100644 index 000000000..40241c23f --- /dev/null +++ b/infer/src/checkers/callbackChecker.ml @@ -0,0 +1,109 @@ +(* +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +(** Make sure callbacks are always unregistered. drive the point home by reporting possible NPE's *) + +module L = Logging +module F = Format +module P = Printf +module IdSet = Ident.IdentSet +module FldSet = Ident.FieldSet +open Utils + +(** Automatically create a harness method to exercise code under test *) + +(** return the set of instance fields that are assigned to a null literal in [procdesc] *) +let get_fields_nullified procdesc = + (* walk through the instructions and look for instance fields that are assigned to null *) + let collect_nullified_flds (nullified_flds, this_ids) _ = function + | Sil.Set (Sil.Lfield (Sil.Var lhs, fld, _), typ, rhs, loc) + when Sil.exp_is_null_literal rhs && IdSet.mem lhs this_ids -> + (FldSet.add fld nullified_flds, this_ids) + | Sil.Letderef (id, rhs, _, _) when Sil.exp_is_this rhs -> + (nullified_flds, IdSet.add id this_ids) + | _ -> (nullified_flds, this_ids) in + let (nullified_flds, _) = + Cfg.Procdesc.fold_instrs collect_nullified_flds (FldSet.empty, IdSet.empty) procdesc in + nullified_flds + +(** set of instance fields belonging to the current file that are assigned to null literals *) +let fields_nullified = ref FldSet.empty + +(** set of callbacks registered in the current file *) +let registered_callback_procs = ref Procname.Set.empty + +let android_lifecycle_typs = ref [] + +(** resolve the list of android lifecycle type strings in [tenv] *) +let get_or_create_lifecycle_typs tenv = match !android_lifecycle_typs with + | [] -> + let lifecycle_typs = list_fold_left (fun typs (pkg, clazz, methods) -> + let qualified_name = Mangled.from_package_class pkg clazz in + match AndroidFramework.get_lifecycle_for_framework_typ_opt + qualified_name methods tenv with + | Some (framework_typ, _) -> framework_typ :: typs + | None -> typs + ) [] AndroidFramework.get_lifecycles in + android_lifecycle_typs := lifecycle_typs; + lifecycle_typs + | typs -> typs + +let do_eradicate_check all_procs get_procdesc idenv tenv proc_name proc_desc = + Eradicate.callback_eradicate all_procs get_procdesc idenv tenv proc_name proc_desc + +let num_methods_checked = ref 0 + +let done_checking num_methods = + incr num_methods_checked; + !num_methods_checked = num_methods + +(** ask Eradicate to check each of the procs in [registered_callback_procs] (and their transitive +* callees) in a context where each of the fields in [fields_nullifed] is marked as @Nullable *) +let do_eradicate_check all_procs get_procdesc idenv tenv = + (* tell Eradicate to treat each of the fields nullified in on_destroy as nullable *) + FldSet.iter (fun fld -> Models.Inference.field_add_nullable_annotation fld) !fields_nullified; + Procname.Set.iter + (fun proc_name -> + match get_procdesc proc_name with + | Some proc_desc -> + do_eradicate_check all_procs get_procdesc idenv tenv proc_name proc_desc + | None -> ()) + !registered_callback_procs + +(** if [procname] belongs to an Android lifecycle type, save the set of callbacks registered in +* [procname]. in addition, if [procname] is a special "destroy" /"cleanup" method, save the set of +* fields that are nullified *) +let callback_checker_main all_procs get_procdesc idenv tenv proc_name proc_desc = + match Sil.get_typ (Mangled.from_string (Procname.java_get_class proc_name)) None tenv with + | Some (Sil.Tstruct(_, _, csu, Some class_name, _, methods, _) as typ) -> + let lifecycle_typs = get_or_create_lifecycle_typs tenv in + let proc_belongs_to_lifecycle_typ = list_exists + (fun lifecycle_typ -> AndroidFramework.typ_is_lifecycle_typ typ lifecycle_typ tenv) + lifecycle_typs in + if proc_belongs_to_lifecycle_typ then + (* TODO (tt4959422): get all of the callbacks registered by callees as well *) + let registered_callback_typs = + AndroidFramework.get_callbacks_registered_by_proc proc_desc tenv in + (* find the callbacks registered by this procedure and update the list *) + let registered_callback_procs' = list_fold_left + (fun callback_procs callback_typ -> + match callback_typ with + | Sil.Tptr (Sil.Tstruct(_, _, Sil.Class, Some class_name, _, methods, _), _) -> + list_fold_left + (fun callback_procs callback_proc -> + if Procname.is_constructor callback_proc then callback_procs + else Procname.Set.add callback_proc callback_procs) + callback_procs + methods + | typ -> callback_procs) + !registered_callback_procs + registered_callback_typs in + registered_callback_procs := registered_callback_procs'; + let _ = if AndroidFramework.is_destroy_method proc_name then + (* compute the set of fields nullified by this procedure *) + (* TODO (t4959422): get fields that are nullified in callees of the destroy method *) + fields_nullified := FldSet.union (get_fields_nullified proc_desc) !fields_nullified in + if done_checking (list_length methods) then + do_eradicate_check all_procs get_procdesc idenv tenv + | _ -> () diff --git a/infer/src/checkers/callbackChecker.mli b/infer/src/checkers/callbackChecker.mli new file mode 100644 index 000000000..be4cabf01 --- /dev/null +++ b/infer/src/checkers/callbackChecker.mli @@ -0,0 +1,10 @@ +(* +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +(** Make sure callbacks are always unregistered. drive the point home by reporting possible NPE's *) + +(** return the set of instance fields that are assigned to a null literal in [procdesc] *) +val get_fields_nullified : Cfg.Procdesc.t -> Ident.FieldSet.t + +val callback_checker_main : Callbacks.proc_callback_t diff --git a/infer/src/checkers/checkDeadCode.ml b/infer/src/checkers/checkDeadCode.ml new file mode 100644 index 000000000..6240cf0f4 --- /dev/null +++ b/infer/src/checkers/checkDeadCode.ml @@ -0,0 +1,114 @@ +(* +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +module L = Logging +module F = Format +open Utils +open Dataflow + +(** Simple check for dead code. *) + +let verbose = false + +module State = struct + type t = + { + visited : Cfg.NodeSet.t; + } + + let initial = + { + visited = Cfg.NodeSet.empty; + } + + let equal t1 t2 = + Cfg.NodeSet.equal t1.visited t2.visited + + let join t1 t2 = + { + visited = Cfg.NodeSet.union t1.visited t2.visited + } + + let add_visited node t = + { + visited = Cfg.NodeSet.add node t.visited; + } + + let get_visited t = + t.visited + + let pp fmt t = + F.fprintf fmt "visited: %a" + (pp_seq Cfg.Node.pp) (Cfg.NodeSet.elements t.visited) + + let num_visited t = + Cfg.NodeSet.cardinal t.visited +end + +let do_node node (s : State.t) : (State.t list) * (State.t list) = + let s' = State.add_visited node s in + if verbose then L.stderr " N:%a (#visited: %a)@." + Cfg.Node.pp node + State.pp s'; + [s'], [s'] + + +(** Report an error. *) +let report_error description pn pd loc = + if verbose then L.stderr "ERROR: %s@." description; + Checkers.ST.report_error pn pd "CHECKERS_DEAD_CODE" loc description + + +(** Check the final state at the end of the analysis. *) +let check_final_state proc_name proc_desc exit_node final_s = + let proc_nodes = Cfg.Procdesc.get_nodes proc_desc in + let tot_nodes = list_length proc_nodes in + let tot_visited = State.num_visited final_s in + if verbose then L.stderr "TOT nodes: %d (visited: %n)@." tot_nodes tot_visited; + if tot_nodes <> tot_visited then + begin + let not_visited = + list_filter (fun n -> not (Cfg.NodeSet.mem n (State.get_visited final_s))) proc_nodes in + let do_node n = + let loc = Cfg.Node.get_loc n in + let description = Format.sprintf "Node not visited: %d" (Cfg.Node.get_id n) in + let report = match Cfg.Node.get_kind n with + | Cfg.Node.Join_node -> false + | k when k = Cfg.Node.exn_sink_kind -> false + | _ -> true in + if report + then report_error description proc_name proc_desc loc in + list_iter do_node not_visited + end + +(** Simple check for dead code. *) +let callback_check_dead_code + (all_procs : Procname.t list) + (get_proc_desc: Procname.t -> Cfg.Procdesc.t option) + (idenv: Idenv.t) + (tenv: Sil.tenv) + (proc_name: Procname.t) + (proc_desc : Cfg.Procdesc.t) : unit = + + let module DFDead = MakeDF(struct + type t = State.t + let equal = State.equal + let join = State.join + let do_node = do_node + let proc_throws pn = DontKnow + end) in + + let do_check () = + begin + if verbose then L.stderr "@.--@.PROC: %a@." Procname.pp proc_name; + let transitions = DFDead.run proc_desc State.initial in + let exit_node = Cfg.Procdesc.get_exit_node proc_desc in + match transitions exit_node with + | DFDead.Transition (pre_final_s, _, _) -> + let final_s = State.add_visited exit_node pre_final_s in + check_final_state proc_name proc_desc exit_node final_s + | DFDead.Dead_state -> () + end in + + do_check () diff --git a/infer/src/checkers/checkDeadCode.mli b/infer/src/checkers/checkDeadCode.mli new file mode 100644 index 000000000..d81abe2dc --- /dev/null +++ b/infer/src/checkers/checkDeadCode.mli @@ -0,0 +1,6 @@ +(* +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +(** Simple check for dead code. *) +val callback_check_dead_code: Callbacks.proc_callback_t diff --git a/infer/src/checkers/checkers.ml b/infer/src/checkers/checkers.ml new file mode 100644 index 000000000..746a7bb4b --- /dev/null +++ b/infer/src/checkers/checkers.ml @@ -0,0 +1,498 @@ +(* +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +(** Module for user-defined checkers. *) + +module L = Logging +module F = Format +open Utils + +let verbose = ref true + +(** Convenience functions for chechers to print information *) +module PP = struct + (** Print a range of lines of the source file in [loc], including [nbefore] lines before loc + and [nafter] lines after [loc] *) + let pp_loc_range linereader nbefore nafter fmt loc = + let printline n = match Printer.LineReader.from_loc linereader { loc with Sil.line = n } with + | Some s -> F.fprintf fmt "%s%s@\n" (if n = loc.Sil.line then "-->" else " ") s + | _ -> () in + F.fprintf fmt "%s:%d@\n" (DB.source_file_to_string loc.Sil.file) loc.Sil.line; + for n = loc.Sil.line - nbefore to loc.Sil.line + nafter do printline n done +end (* PP *) + + +(** State that persists in the .specs files. *) +module ST = struct + let add summary key value = + proc_flags_add summary.Specs.proc_flags key value + + let pname_add proc_name key value = + let summary = Specs.get_summary_unsafe proc_name in + add summary key value + + let files_open = ref Procname.Set.empty + + let pname_find proc_name key = + if Procname.Set.mem proc_name !files_open then + let summary = Specs.get_summary_unsafe proc_name in + proc_flags_find summary.Specs.proc_flags key + else begin + match Specs.get_summary proc_name with + | None -> raise Not_found + | Some summary -> + begin + files_open := Procname.Set.add proc_name !files_open; + proc_flags_find summary.Specs.proc_flags key + end + end + + let store_summary proc_name = + Option.may + (fun summary -> + try Specs.store_summary proc_name summary with Sys_error s -> L.err "%s@." s) + (Specs.get_summary proc_name) + + let report_error + proc_name + proc_desc + kind + loc + ?(advice = None) + ?(field_name = None) + ?(origin_loc = None) + ?(exception_kind = fun k d -> Exceptions.Checkers (k, d)) + ?(always_report = false) + description = + let localized_description = Localise.custom_desc_with_advice + description + (Option.default "" advice) + [("always_report", string_of_bool always_report)] in + let exn = exception_kind kind localized_description in + + (* Errors can be suppressed with annotations. An error of kind CHECKER_ERROR_NAME can be + suppressed with the following annotations: + - @android.annotation.SuppressLint("checker-error-name") + - @some.PrefixErrorName + where the kind matching is case - insensitive and ignores '-' and '_' characters. *) + let suppressed = + let annotation_matches a = + let normalize str = + Str.global_replace (Str.regexp "[_-]") "" (String.lowercase str) in + let drop_prefix str = + Str.replace_first (Str.regexp "^[A-Za-z]+_") "" str in + let normalized_equal s1 s2 = + string_equal (normalize s1) (normalize s2) in + + let is_parameter_suppressed = + list_mem string_equal a.Sil.class_name [Annotations.suppressLint] && + list_mem normalized_equal kind a.Sil.parameters in + let is_annotation_suppressed = + string_is_suffix (normalize (drop_prefix kind)) (normalize a.Sil.class_name) in + + is_parameter_suppressed || is_annotation_suppressed in + + let is_method_suppressed = + Annotations.ma_has_annotation_with + (Specs.proc_get_method_annotation proc_name proc_desc) + annotation_matches in + + let is_field_suppressed = + match field_name, PatternMatch.get_this_type proc_desc with + | Some field_name, Some t -> begin + match (Annotations.get_field_type_and_annotation field_name t) with + | Some (_, ia) -> Annotations.ia_has_annotation_with ia annotation_matches + | None -> false + end + | _ -> false in + + let is_class_suppressed = + match (PatternMatch.get_this_type proc_desc) with + | Some t -> begin + match (PatternMatch.type_get_annotation t) with + | Some ia -> Annotations.ia_has_annotation_with ia annotation_matches + | None -> false + end + | None -> false in + + is_method_suppressed || is_field_suppressed || is_class_suppressed in + + let trace = + let make_trace_element loc description = + [{ + Errlog.lt_level = 0; + Errlog.lt_loc = loc; + Errlog.lt_description = description; + Errlog.lt_node_tags = [] + }] in + let origin_elements = + match origin_loc with + | Some oloc -> make_trace_element oloc "origin" + | None -> [] in + origin_elements @ (make_trace_element loc description) in + + if not suppressed then + begin + if !verbose then + begin + let file = DB.source_file_to_string loc.Sil.file in + L.stdout "%s: %s: %s@." + kind + file + (Procname.to_string proc_name); + L.stdout "%s@." description + end; + Reporting.log_error proc_name ~loc: (Some loc) ~ltr: (Some trace) exn + end +end + +let report_calls_and_accesses callback node instr = + let proc_desc = Cfg.Node.get_proc_desc node in + let proc_name = Cfg.Procdesc.get_proc_name proc_desc in + let callee = Procname.to_string proc_name in + match PatternMatch.get_java_field_access_signature instr with + | Some (bt, fn, ft) -> + ST.report_error + proc_name + proc_desc + (callback ^ "_CALLBACK") + (Cfg.Procdesc.get_loc proc_desc) + (Format.sprintf "field access %s.%s:%s in %s@." bt fn ft callee) + | None -> + match PatternMatch.get_java_method_call_formal_signature instr with + | Some (bt, fn, ats, rt) -> + ST.report_error + proc_name + proc_desc + (callback ^ "_CALLBACK") + (Cfg.Procdesc.get_loc proc_desc) + (Format.sprintf "method call %s.%s(%s):%s in %s@." bt fn "..." rt callee) + | None -> () + +(** Report all field accesses and method calls of a procedure. *) +let callback_check_access all_procs get_proc_desc idenv tenv proc_name proc_desc = + Cfg.Procdesc.iter_instrs (report_calls_and_accesses "PROC") proc_desc + +(** Report all field accesses and method calls of a class. *) +let callback_check_cluster_access all_procs get_proc_desc proc_definitions = + list_iter + (Option.may (fun d -> Cfg.Procdesc.iter_instrs (report_calls_and_accesses "CLUSTER") d)) + (list_map get_proc_desc all_procs) + +(** Looks for writeToParcel methods and checks whether read is in reverse *) +let callback_check_write_to_parcel all_procs get_proc_desc idenv tenv proc_name proc_desc = + let verbose = ref false in + + let is_write_to_parcel this_expr this_type = + let method_match () = Procname.java_get_method proc_name = "writeToParcel" in + let expr_match () = Sil.exp_is_this this_expr in + let type_match () = PatternMatch.is_direct_subtype_of this_type "android.os.Parcelable" in + method_match () && expr_match () && type_match () in + + let is_parcel_constructor proc_name = + Procname.is_constructor proc_name && + PatternMatch.has_formal_method_argument_type_names proc_desc proc_name ["android.os.Parcel"] in + + let parcel_constructors = function + | Sil.Tptr (Sil.Tstruct (_, _, _, _, _, methods, _), _) -> + list_filter is_parcel_constructor methods + | _ -> [] in + + let check r_name r_desc w_name w_desc = + + let is_serialization_node node = + match Cfg.Node.get_callees node with + | [] -> false + | [proc_name] -> + let class_name = Procname.java_get_class proc_name in + let method_name = Procname.java_get_method proc_name in + (try + class_name = "android.os.Parcel" && (String.sub method_name 0 5 = "write" || String.sub method_name 0 4 = "read") + with Invalid_argument _ -> false) + | _ -> assert false in + + let is_inverse rc wc = + let rn = Procname.java_get_method rc in + let wn = Procname.java_get_method wc in + let postfix_length = String.length wn - 5 in (* covers writeList <-> readArrayList etc. *) + try + String.sub rn (String.length rn - postfix_length) postfix_length = String.sub wn 5 postfix_length + with Invalid_argument _ -> false in + + let node_to_call_desc node = + match Cfg.Node.get_callees node with + | [desc] -> desc + | _ -> assert false in + + let r_call_descs = list_map node_to_call_desc (list_filter is_serialization_node (Cfg.Procdesc.get_sliced_slope r_desc is_serialization_node)) in + let w_call_descs = list_map node_to_call_desc (list_filter is_serialization_node (Cfg.Procdesc.get_sliced_slope w_desc is_serialization_node)) in + + let rec check_match = function + | rc:: rcs, wc:: wcs -> + if not (is_inverse rc wc) then + L.stdout "Serialization missmatch in %a for %a and %a@." Procname.pp proc_name Procname.pp rc Procname.pp wc + else + check_match (rcs, wcs) + | rc:: rcs, [] -> + L.stdout "Missing write in %a: for %a@." Procname.pp proc_name Procname.pp rc + | _, wc:: wcs -> + L.stdout "Missing read in %a: for %a@." Procname.pp proc_name Procname.pp wc + | _ -> () in + + check_match (r_call_descs, w_call_descs) in + + let do_instr node instr = match instr with + | Sil.Call (ret_ids, Sil.Const (Sil.Cfun pn), (_this_exp, this_type):: args, loc, cf) -> + let this_exp = Idenv.expand_expr idenv _this_exp in + if is_write_to_parcel this_exp this_type then begin + if !verbose then L.stdout "Serialization check for %a@." Procname.pp proc_name; + try + match parcel_constructors this_type with + | x :: xs -> + (match get_proc_desc x with + | Some x_proc_desc -> + check x x_proc_desc proc_name proc_desc + | None -> raise Not_found) + | _ -> L.stdout "No parcel constructor found for %a@." Procname.pp proc_name + with Not_found -> if !verbose then L.stdout "Methods not available@." + end + | _ -> () in + Cfg.Procdesc.iter_instrs do_instr proc_desc + +(** Monitor calls to Preconditions.checkNotNull and detect inconsistent uses. *) +let callback_monitor_nullcheck all_procs get_proc_desc idenv tenv proc_name proc_desc = + let verbose = ref false in + + let class_formal_names = lazy ( + let formals = Cfg.Procdesc.get_formals proc_desc in + let class_formals = + let is_class_type = function + | "this", Sil.Tptr _ -> false (* no need to null check 'this' *) + | _, Sil.Tstruct _ -> true + | _, Sil.Tptr (Sil.Tstruct _, _) -> true + | _ -> false in + list_filter is_class_type formals in + list_map (fun (s, _) -> Mangled.from_string s) class_formals) in + let equal_formal_param exp formal_name = match exp with + | Sil.Lvar pvar -> + let name = Sil.pvar_get_name pvar in + Mangled.equal name formal_name + | _ -> false in + + let is_formal_param exp = + list_exists (equal_formal_param exp) (Lazy.force class_formal_names) in + + let is_nullcheck pn = + PatternMatch.java_proc_name_with_class_method + pn "com.google.common.base.Preconditions" "checkNotNull" in + + let checks_to_formals = ref Sil.ExpSet.empty in + + let handle_check_of_formal e = + let repeated = Sil.ExpSet.mem e !checks_to_formals in + if repeated && !verbose then L.stdout "Repeated Null Check of Formal: %a@." (Sil.pp_exp pe_text) e + else begin + checks_to_formals := Sil.ExpSet.add e !checks_to_formals; + if !verbose then L.stdout "Null Check of Formal: %a@." (Sil.pp_exp pe_text) e + end in + + let summary_checks_of_formals () = + let formal_names = Lazy.force class_formal_names in + let nchecks = Sil.ExpSet.cardinal !checks_to_formals in + let nformals = list_length formal_names in + if (nchecks > 0 && nchecks < nformals) then + begin + let was_not_found formal_name = + not (Sil.ExpSet.exists (fun exp -> equal_formal_param exp formal_name) !checks_to_formals) in + let missing = list_filter was_not_found formal_names in + let loc = Cfg.Procdesc.get_loc proc_desc in + let pp_file_loc fmt () = F.fprintf fmt "%s:%d" (DB.source_file_to_string loc.Sil.file) loc.Sil.line in + L.stdout "Null Checks of Formal Parameters: %d out of %d parameters checked (missing checks on: %a)[%a]@." nchecks nformals (pp_seq Mangled.pp) missing pp_file_loc (); + + let linereader = Printer.LineReader.create () in + L.stdout "%a@." (PP.pp_loc_range linereader 10 10) loc + end in + + let do_instr node instr = match instr with + | Sil.Call (ret_ids, Sil.Const (Sil.Cfun pn), (_arg1, t1):: arg_ts, loc, cf) when is_nullcheck pn -> + let arg1 = Idenv.expand_expr idenv _arg1 in + if is_formal_param arg1 then handle_check_of_formal arg1; + if !verbose then L.stdout "call in %s %s: %a with first arg: %a@." (Procname.java_get_class proc_name) (Procname.java_get_method proc_name) (Sil.pp_instr pe_text) instr (Sil.pp_exp pe_text) arg1 + | _ -> () in + Cfg.Procdesc.iter_instrs do_instr proc_desc; + summary_checks_of_formals () + +(** Test persistent state. *) +let callback_test_state all_procs get_proc_desc idenv tenv proc_name proc_desc = + ST.pname_add proc_name "somekey" "somevalue" + +(** Check the uses of VisibleForTesting *) +let callback_checkVisibleForTesting all_procs get_proc_desc idenv tenv proc_name proc_desc = + let ma = Specs.proc_get_method_annotation proc_name proc_desc in + if Annotations.ma_contains ma [Annotations.visibleForTesting] then + begin + let loc = Cfg.Procdesc.get_loc proc_desc in + let linereader = Printer.LineReader.create () in + L.stdout "%a@." (PP.pp_loc_range linereader 10 10) loc + end + +(** Check for readValue and readValueAs json deserialization *) +let callback_find_deserialization all_procs get_proc_desc idenv tenv proc_name proc_desc = + let verbose = true in + + let ret_const_key = "return_const" in + + let reverse_find_instr f node = + (** this is not really sound but for the moment a sufficient approximation *) + let has_instr node = + try ignore(list_find f (Cfg.Node.get_instrs node)); true + with Not_found -> false in + let preds = Cfg.Node.get_generated_slope node (fun n -> Cfg.Node.get_sliced_preds n has_instr) in + let instrs = list_flatten (list_map (fun n -> list_rev (Cfg.Node.get_instrs n)) preds) in + try + Some (list_find f instrs) + with Not_found -> None in + + let get_return_const proc_name' = + try + ST.pname_find proc_name' ret_const_key + with Not_found -> + match get_proc_desc proc_name' with + Some proc_desc' -> + let is_return_instr = function + | Sil.Set (Sil.Lvar p, _, _, _) + when Sil.pvar_equal p (Cfg.Procdesc.get_ret_var proc_desc') -> true + | _ -> false in + (match reverse_find_instr is_return_instr (Cfg.Procdesc.get_exit_node proc_desc') with + | Some (Sil.Set (_, _, Sil.Const (Sil.Cclass n), _)) -> Ident.name_to_string n + | _ -> "<" ^ (Procname.to_string proc_name') ^ ">") + | None -> "?" in + + let get_actual_arguments node instr = match instr with + | Sil.Call (ret_ids, Sil.Const (Sil.Cfun pn), (te, tt):: args, loc, cf) -> (try + let find_const exp typ = + let expanded = Idenv.expand_expr idenv exp in + match expanded with + | Sil.Const (Sil.Cclass n) -> Ident.name_to_string n + | Sil.Lvar p -> ( + let is_call_instr set call = match set, call with + | Sil.Set (_, _, Sil.Var (i1), _), Sil.Call (i2::[], _, _, _, _) when Ident.equal i1 i2 -> true + | _ -> false in + let is_set_instr = function + | Sil.Set (e1, t, e2, l) when Sil.exp_equal expanded e1 -> true + | _ -> false in + match reverse_find_instr is_set_instr node with (** Look for ivar := tmp *) + | Some s -> ( + match reverse_find_instr (is_call_instr s) node with (** Look for tmp := foo() *) + | Some (Sil.Call (_, Sil.Const (Sil.Cfun pn), _, l, _)) -> get_return_const pn + | _ -> "?") + | _ -> "?") + | _ -> "?" in + let arg_name (exp, typ) = find_const exp typ in + Some (list_map arg_name args) + with _ -> None) + | _ -> None in + + let process_result instr result = + if verbose then ( + let linereader = Printer.LineReader.create () in + L.stdout "%a@." (PP.pp_loc_range linereader 2 2) (Sil.instr_get_loc instr); + ); + match result with + | str when (Str.string_match (Str.regexp "<\\(.*\\)>") str 0) -> ( + let missing_proc_name = Str.matched_group 1 str in + L.stdout "Deserialization of %s requires 2nd phase: " str; + L.stdout "missing: %s@." missing_proc_name) + | "?" -> L.stdout "Unable to resolve deserialization\n\n@." + | _ -> L.stdout "Deserialization of %s\n\n@." result in + + let do_instr node instr = + match PatternMatch.get_java_method_call_formal_signature instr with + | Some (_, "readValue", _, _) -> ( + match get_actual_arguments node instr with + | Some [_; cl] -> process_result instr cl + | _ -> process_result instr "?") + | Some (_, "readValueAs", _, _) -> ( + match get_actual_arguments node instr with + | Some [cl] -> process_result instr cl + | _ -> process_result instr "?") + | _ -> () in + + let store_return () = + let ret_const = get_return_const proc_name in + ST.pname_add proc_name ret_const_key ret_const in + + store_return (); + Cfg.Procdesc.iter_instrs do_instr proc_desc + +(** Check field accesses. *) +let callback_check_field_access all_procs get_proc_desc idenv tenv proc_name proc_desc = + let rec do_exp is_read = function + | Sil.Var _ -> () + | Sil.UnOp (_, e, _) -> + do_exp is_read e + | Sil.BinOp (_, e1, e2) -> + do_exp is_read e1; + do_exp is_read e2 + | Sil.Const _ -> () + | Sil.Cast (_, e) -> + do_exp is_read e + | Sil.Lvar _ -> () + | Sil.Lfield (e, fn, t) -> + if not (Ident.java_fieldname_is_outer_instance fn) then + L.stdout "field %s %s@." (Ident.fieldname_to_string fn) (if is_read then "reading" else "writing"); + do_exp is_read e + | Sil.Lindex (e1, e2) -> + do_exp is_read e1; + do_exp is_read e2 + | Sil.Sizeof _ -> () in + let do_read_exp = do_exp true in + let do_write_exp = do_exp false in + let do_instr node = function + | Sil.Letderef (_, e, _, _) -> + do_read_exp e + | Sil.Set (e1, _, e2, _) -> + do_write_exp e1; + do_read_exp e2 + | Sil.Prune (e, _, _, _) -> + do_read_exp e + | Sil.Call (_, e, etl, _, _) -> + do_read_exp e; + list_iter (fun (e, _) -> do_read_exp e) etl + | Sil.Nullify _ + | Sil.Abstract _ + | Sil.Remove_temps _ + | Sil.Stackop _ + | Sil.Declare_locals _ + | Sil.Goto_node _ -> + () in + Cfg.Procdesc.iter_instrs do_instr proc_desc + +(** Print c method calls. *) +let callback_print_c_method_calls all_procs get_proc_desc idenv tenv proc_name proc_desc = + let do_instr node = function + | Sil.Call (ret_ids, Sil.Const (Sil.Cfun pn), (e, t):: args, loc, cf) + when Procname.is_objc pn -> + let receiver = match Errdesc.exp_rv_dexp node e with + | Some de -> Sil.dexp_to_string de + | None -> "?" in + let description = + Printf.sprintf "['%s' %s]" receiver (Procname.to_string pn) in + ST.report_error + proc_name + proc_desc + "CHECKERS_PRINT_OBJC_METHOD_CALLS" + loc + description + | Sil.Call (_, Sil.Const (Sil.Cfun pn), _, loc, _) -> + let description = + Printf.sprintf "call to %s" (Procname.to_string pn) in + ST.report_error + proc_name + proc_desc + "CHECKERS_PRINT_C_CALL" + loc + description + | _ -> () in + Cfg.Procdesc.iter_instrs do_instr proc_desc diff --git a/infer/src/checkers/checkers.mli b/infer/src/checkers/checkers.mli new file mode 100644 index 000000000..12e72ebf8 --- /dev/null +++ b/infer/src/checkers/checkers.mli @@ -0,0 +1,48 @@ +(* +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +(** Module for user-defined checkers. *) + + +(** State that persists in the .specs files. *) +module ST : sig +(** Add a key/value pair. *) + val pname_add : Procname.t -> string -> string -> unit + (** Find the value associated to the key. Raise Not_found if it does not exist. *) + val pname_find: Procname.t -> string -> string + + (** Report an error. *) + val report_error: + Procname.t -> + Cfg.Procdesc.t -> + string -> + Sil.location -> + ?advice: string option -> + ?field_name: Ident.fieldname option -> + ?origin_loc: Sil.location option -> + ?exception_kind: (string -> Localise.error_desc -> exn) -> + ?always_report: bool -> + string -> + unit + + (** Store the summary to a .specs file. *) + val store_summary : Procname.t -> unit + +end (* ST *) + +module PP : sig +(** Print a range of lines of the source file in [loc], including [nbefore] lines before loc +and [nafter] lines after [loc] *) + val pp_loc_range : Printer.LineReader.t -> int -> int -> Format.formatter -> Sil.location -> unit +end (* PP *) + +val callback_check_access : Callbacks.proc_callback_t +val callback_check_cluster_access : Callbacks.cluster_callback_t +val callback_monitor_nullcheck : Callbacks.proc_callback_t +val callback_test_state : Callbacks.proc_callback_t +val callback_checkVisibleForTesting : Callbacks.proc_callback_t +val callback_check_write_to_parcel : Callbacks.proc_callback_t +val callback_find_deserialization : Callbacks.proc_callback_t +val callback_check_field_access : Callbacks.proc_callback_t +val callback_print_c_method_calls : Callbacks.proc_callback_t diff --git a/infer/src/checkers/codeQuery.ml b/infer/src/checkers/codeQuery.ml new file mode 100644 index 000000000..39da18c9e --- /dev/null +++ b/infer/src/checkers/codeQuery.ml @@ -0,0 +1,203 @@ +(* +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +(** Module for code queries. *) + +module L = Logging +module F = Format +open Utils + +let verbose = false +let query = ref None + +let parse_query s = + let buf = Lexing.from_string s in + try + match CodequeryParser.query CodequeryLexer.token buf with + | None -> + L.stdout "empty rule@."; + assert false + | Some query -> + if verbose then L.stdout "%a@." CodeQueryAst.pp_query query; + query + with + | Parsing.Parse_error -> + L.stdout "@.parsing stops on line %d: \n\n%s@." !CodequeryLexer.line_number buf.Lexing.lex_buffer; + assert false + +let query_ast = + lazy (match !query with None -> parse_query "x(y)" | Some s -> parse_query s) + +module Err = struct + (** Update the summary with stats from the checker. *) + let update_summary proc_name proc_desc = + let old_summ = Specs.get_summary_unsafe proc_name in + let nodes = list_map (fun n -> Cfg.Node.get_id n) (Cfg.Procdesc.get_nodes proc_desc) in + let specs = + let spec = + { Specs.pre = Specs.Jprop.Prop (1, Prop.prop_emp); + Specs.posts = []; + Specs.visited = Specs.Visitedset.empty + } in + [(Specs.spec_normalize spec)] in + let new_summ = { old_summ with + Specs.loc = Cfg.Procdesc.get_loc proc_desc; + Specs.nodes = nodes; + Specs.payload = Specs.PrePosts specs } in + Specs.add_summary proc_name new_summ + + let add_error_to_spec proc_name s node loc = + Checkers.ST.pname_add proc_name "codequery" "active"; (* force creation of .specs files *) + State.set_node node; + let exn = Exceptions.Codequery (Localise.verbatim_desc s) in + Reporting.log_error proc_name ~loc: (Some loc) exn +end + +(** Matcher for rules. *) +module Match = struct + type value = + | Vfun of Procname.t + | Vval of Sil.exp + + let pp_value fmt = function + | Vval e -> F.fprintf fmt "%a" (Sil.pp_exp pe_text) e + | Vfun pn -> F.fprintf fmt "%s" (Procname.to_string pn) + + let value_equal v1 v2 = match v1, v2 with + | Vval e1, Vval e2 -> Sil.exp_equal e1 e2 + | Vval _, _ -> false + | _, Vval _ -> false + | Vfun pn1, Vfun pn2 -> Procname.equal pn1 pn2 + + let init_env () = Hashtbl.create 1 + + let env_copy env = Hashtbl.copy env + + let env_add env id value = + try + let value' = Hashtbl.find env id in + value_equal value value' + with Not_found -> + Hashtbl.add env id value; + true + let pp_env fmt env = + let pp_item id value = + F.fprintf fmt "%s=%a " id pp_value value in + Hashtbl.iter pp_item env + + let exp_match env ae value = match ae, value with + | CodeQueryAst.Null, Vval e -> Sil.exp_equal e Sil.exp_zero + | CodeQueryAst.Null, _ -> false + | CodeQueryAst.ConstString s, (Vfun pn) -> string_contains s (Procname.to_string pn) + | CodeQueryAst.ConstString s, _ -> false + | CodeQueryAst.Ident id, x -> + env_add env id x + + let rec exp_list_match env ael vl = match ael, vl with + | [], [] -> true + | [], _:: _ -> false + | _:: _, [] -> false + | ae:: ael', v:: vl' -> + if exp_match env ae v then exp_list_match env ael' vl' + else false + + let opt_match match_elem env x_opt y = match x_opt with + | None -> true + | Some x -> match_elem env x y + + let binop_match op1 op2 = match op1, op2 with + | Sil.Eq, "==" -> true + | Sil.Ne, "!=" -> true + | _ -> false + + let rec cond_match env idenv cond (ae1, op, ae2) = match cond with + | Sil.BinOp (bop, _e1, _e2) -> + let e1 = Idenv.expand_expr idenv _e1 in + let e2 = Idenv.expand_expr idenv _e2 in + binop_match bop op && exp_match env ae1 (Vval e1) && exp_match env ae2 (Vval e2) + | Sil.UnOp (Sil.LNot, (Sil.BinOp (Sil.Eq, e1, e2)), _) -> + cond_match env idenv (Sil.BinOp (Sil.Ne, e1, e2)) (ae1, op, ae2) + | Sil.UnOp (Sil.LNot, (Sil.BinOp (Sil.Ne, e1, e2)), _) -> + cond_match env idenv (Sil.BinOp (Sil.Eq, e1, e2)) (ae1, op, ae2) + | _ -> false + + (** Iterate over the instructions of the linearly succ nodes. *) + let rec iter_succ_nodes node iter = + match Cfg.Node.get_succs node with + | [node'] -> + let instrs = Cfg.Node.get_instrs node in + list_iter (fun instr -> iter (node', instr)) instrs; + iter_succ_nodes node' iter + | [] -> () + | _:: _ -> () + + let linereader = Printer.LineReader.create () + + let print_action env action proc_name node loc = match action with + | CodeQueryAst.Noaction -> + L.stdout "%a@." pp_env env + | CodeQueryAst.Source source_range_opt -> + let x, y = match source_range_opt with + | None -> 10, 10 + | Some (x, y) -> x, y in + L.stdout "%a@." (Checkers.PP.pp_loc_range linereader x y) loc + | CodeQueryAst.Error s_opt -> + let err_name = match s_opt with + | None -> "codequery" + | Some s -> s in + Err.add_error_to_spec proc_name err_name node loc + + let rec match_query show env idenv node caller_pn (rule, action) proc_name node instr = + match rule, instr with + | CodeQueryAst.Call (ae1, ae2), Sil.Call (_, Sil.Const (Sil.Cfun pn), _, loc, _) -> + if exp_match env ae1 (Vfun caller_pn) && exp_match env ae2 (Vfun pn) then + begin + if show then print_action env action proc_name node loc; + true + end + else false + | CodeQueryAst.Call _, _ -> false + | CodeQueryAst.MethodCall (ae1, ae2, ael_opt), Sil.Call (_, Sil.Const (Sil.Cfun pn), (_e1, t1):: params, loc, { Sil.cf_virtual = true }) -> + let e1 = Idenv.expand_expr idenv _e1 in + let vl = list_map (function _e, t -> Vval (Idenv.expand_expr idenv _e)) params in + if exp_match env ae1 (Vval e1) && exp_match env ae2 (Vfun pn) && opt_match exp_list_match env ael_opt vl then + begin + if show then print_action env action proc_name node loc; + true + end + else false + | CodeQueryAst.MethodCall _, _ -> false + | CodeQueryAst.If (ae1, op, ae2, body_rule), Sil.Prune (cond, loc, true_branch, ik) -> + if true_branch && cond_match env idenv cond (ae1, op, ae2) then + begin + let found = ref false in + let iter (node', instr') = + let env' = env_copy env in + if not !found && match_query false env' idenv node' caller_pn (body_rule, action) proc_name node instr' + then found := true in + iter_succ_nodes node iter; + let line_contains_null () = + match Printer.LineReader.from_loc linereader loc with + | None -> false + | Some s -> string_contains "null" s in + if !found && line_contains_null () (* TODO: this replaces lack of typing where null and 0 and false are the same *) then + begin + L.stdout "conditional %a@." (Sil.pp_exp pe_text) cond; + print_action env action proc_name node loc + end; + !found + end + else false + | CodeQueryAst.If _, _ -> false + +end + +let code_query_callback all_procs get_proc_desc idenv tenv proc_name proc_desc = + let do_instr node instr = + let env = Match.init_env () in + let _found = Match.match_query true env idenv node proc_name (Lazy.force query_ast) proc_name node instr in + () in + if verbose then L.stdout "code_query_callback on %a@." Procname.pp proc_name; + Cfg.Procdesc.iter_instrs do_instr proc_desc; + Err.update_summary proc_name proc_desc diff --git a/infer/src/checkers/codeQuery.mli b/infer/src/checkers/codeQuery.mli new file mode 100644 index 000000000..64d56c38e --- /dev/null +++ b/infer/src/checkers/codeQuery.mli @@ -0,0 +1,9 @@ +(* +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +(** Module for code queries. *) + +val code_query_callback : Callbacks.proc_callback_t + +val query : string option ref \ No newline at end of file diff --git a/infer/src/checkers/codeQueryAst.ml b/infer/src/checkers/codeQueryAst.ml new file mode 100644 index 000000000..3b2aadc57 --- /dev/null +++ b/infer/src/checkers/codeQueryAst.ml @@ -0,0 +1,51 @@ +(* +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +(** Abstract synyax tree for code queries. *) + +module L = Logging +module F = Format +open Utils + +type expr = + | Null + | Ident of string + | ConstString of string + +type rule = + | Call of expr * expr + | MethodCall of expr * expr * (expr list option) + | If of expr * string * expr * rule + +type action = + | Noaction + | Source of (int * int) option (* (x,y) represents x lines before and y lines after the source location *) + | Error of string option (* print an error *) + +type query = + rule * action + +let pp_action fmt = function + | Noaction -> () + | Source None -> F.fprintf fmt "@source" + | Source (Some(x, y)) -> F.fprintf fmt "@source(%d,%d)" x y + | Error None -> F.fprintf fmt "@error" + | Error (Some s) -> F.fprintf fmt "@error(%s)" s + +let pp_expr fmt = function + | Null -> F.fprintf fmt "null" + | Ident s -> F.fprintf fmt "%s" s + | ConstString s -> F.fprintf fmt "%s" s + +let pp_expr_list_option fmt = function + | None -> F.fprintf fmt "*" + | Some el -> pp_comma_seq pp_expr fmt el + +let rec pp_rule fmt = function + | Call (ae1, ae2) -> F.fprintf fmt "%a(%a)" pp_expr ae1 pp_expr ae2 + | MethodCall (ae1, ae2, elo) -> F.fprintf fmt "%a.%a(%a)" pp_expr ae1 pp_expr ae2 pp_expr_list_option elo + | If (ae1, s, ae2, rule) -> F.fprintf fmt "if(%a %s %a) ... %a" pp_expr ae1 s pp_expr ae2 pp_rule rule + +let pp_query fmt (rule, action) = + F.fprintf fmt "%a; %a" pp_rule rule pp_action action \ No newline at end of file diff --git a/infer/src/checkers/codequeryLexer.mll b/infer/src/checkers/codequeryLexer.mll new file mode 100644 index 000000000..e69b227e9 --- /dev/null +++ b/infer/src/checkers/codequeryLexer.mll @@ -0,0 +1,75 @@ +(* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*) +{ + +open CodequeryParser + +let line_number = ref 1 +let parentheses = ref 0 + +let parsed_tokens = Buffer.create 1 +let log lexbuf = Buffer.add_char parsed_tokens ' '; Buffer.add_string parsed_tokens (Lexing.lexeme lexbuf) +let reset_log () = Buffer.reset parsed_tokens +let get_log () = Buffer.contents parsed_tokens + +} + +let lowerletter = ['a'-'z'] +let upperletter = ['A'-'Z'] + +let underscore = '_' +let minus = '-' +let letter = lowerletter | upperletter + +let digit = ['0'-'9'] +let number = digit* | digit+ '.' digit* | digit* '.' digit+ + +let identifier = (letter | underscore) (letter | underscore | digit)* + +let const_string_digit = letter | underscore | digit | ' ' | '.' | ',' | '(' | ')' | '[' | ']' +let const_string = '{' const_string_digit* '}' + +let single_quote = '\'' + +let space = [' ' '\t'] +let newline = '\n' + +rule token = parse + | space { log lexbuf; token lexbuf } + | newline { log lexbuf; incr line_number; token lexbuf } + | '.' { log lexbuf; DOT } + | ';' { log lexbuf; SEMICOLON } + | ':' { log lexbuf; COLON } + | ',' { log lexbuf; COMMA } + | '`' { log lexbuf; REV_QUOTE } + | '\'' { log lexbuf; SINGLE_QUOTE } + | '\"' { log lexbuf; DOUBLE_QUOTE } + | '%' { log lexbuf; PERCENT } + | '&' { log lexbuf; AMPERSAND } + | '!' { log lexbuf; EXCLAMATION } + | '=' { log lexbuf; EQUAL } + | "==" { log lexbuf; EQUALEQUAL } + | "!=" { log lexbuf; EXCLAMATIONEQUAL } + | '-' { log lexbuf; MINUS } + | '+' { log lexbuf; PLUS } + | '<' { log lexbuf; LEFT_CHEVRON } + | '>' { log lexbuf; RIGHT_CHEVRON } + | '(' { log lexbuf; incr parentheses; LEFT_PARENTHESIS } + | ')' { log lexbuf; decr parentheses; RIGHT_PARENTHESIS } + | '[' { log lexbuf; LEFT_SQBRACKET } + | ']' { log lexbuf; RIGHT_SQBRACKET } + | '*' { log lexbuf; STAR } + | '|' { log lexbuf; PIPE } + | '/' { log lexbuf; SLASH } + | '\\' { log lexbuf; BACKSLASH } + | "if" { log lexbuf; IF } + | "..." { log lexbuf; DOTDOTDOT } + | "@source" { log lexbuf; SOURCE } + | "@error" { log lexbuf; ERROR } + | number as n { log lexbuf; NUMBER n } + | identifier as s { log lexbuf; IDENT (s) } + | const_string as s { log lexbuf; CONST_STRING (String.sub s 1 (String.length s - 2)) } + | eof { EOF } + diff --git a/infer/src/checkers/codequeryParser.mly b/infer/src/checkers/codequeryParser.mly new file mode 100644 index 000000000..dd579cab3 --- /dev/null +++ b/infer/src/checkers/codequeryParser.mly @@ -0,0 +1,76 @@ +/* +* Copyright (c) 2013 - Facebook. All rights reserved. +*/ + +%{ + +%} + +%token DOT SEMICOLON COLON COMMA SINGLE_QUOTE DOUBLE_QUOTE REV_QUOTE +%token PERCENT AMPERSAND EXCLAMATION EQUAL MINUS PLUS EQUALEQUAL EXCLAMATIONEQUAL +%token LEFT_CHEVRON RIGHT_CHEVRON LEFT_PARENTHESIS RIGHT_PARENTHESIS LEFT_SQBRACKET RIGHT_SQBRACKET +%token STAR PIPE SLASH BACKSLASH +%token IF DOTDOTDOT SOURCE ERROR + +%token HEXA +%token NUMBER +%token IDENT +%token CONST_STRING +%token ATTRIBUTE + +%token EOF + +%start query +%type query + +%% + +ident: + | IDENT { $1 } +; + +expr: + | ident { if $1 = "null" then CodeQueryAst.Null else CodeQueryAst.Ident $1 } + | CONST_STRING { CodeQueryAst.ConstString $1 } +; + +condition: + | expr EQUALEQUAL expr { ($1, "==", $3) } + | expr EXCLAMATIONEQUAL expr { ($1, "!=", $3) } +; + +param_element: + | expr { $1 } +; + +param_element_list_nonempty: + | param_element { [$1] } + | param_element COMMA param_element_list_nonempty { $1 :: $3 } +; +param_element_list: + | { [] } + | param_element_list_nonempty { $1 } +; + +call_params: + | STAR { None } + | param_element_list { Some $1 } +; + +rule: + | expr LEFT_PARENTHESIS expr RIGHT_PARENTHESIS { CodeQueryAst.Call ($1, $3) } + | expr DOT expr LEFT_PARENTHESIS call_params RIGHT_PARENTHESIS { CodeQueryAst.MethodCall($1, $3, $5) } + | IF LEFT_PARENTHESIS condition RIGHT_PARENTHESIS DOTDOTDOT rule { let x, y, z = $3 in CodeQueryAst.If (x, y, z, $6) } +; + +action: + | { CodeQueryAst.Noaction } + | SOURCE SEMICOLON { CodeQueryAst.Source None } + | SOURCE LEFT_PARENTHESIS NUMBER COMMA NUMBER RIGHT_PARENTHESIS SEMICOLON { CodeQueryAst.Source (Some (int_of_string $3, int_of_string $5)) } + | ERROR SEMICOLON { CodeQueryAst.Error None } + | ERROR LEFT_PARENTHESIS ident RIGHT_PARENTHESIS SEMICOLON { CodeQueryAst.Error (Some $3) } + +query: + | rule SEMICOLON action { Some ($1,$3) } + | EOF { None } +; diff --git a/infer/src/checkers/constantPropagation.ml b/infer/src/checkers/constantPropagation.ml new file mode 100644 index 000000000..11936b932 --- /dev/null +++ b/infer/src/checkers/constantPropagation.ml @@ -0,0 +1,130 @@ +(* +* Copyright (c) 2014 - Facebook. All rights reserved. +*) + +open Utils + +module L = Logging + +type t + +module ConstantMap = Map.Make(struct + type t = string + let compare = string_compare +end) + +let string_widening_limit = 1000 +let verbose = false + +(* Merge two constant maps by adding keys as necessary *) +let merge_values key c1_opt c2_opt = + match c1_opt, c2_opt with + | Some (Some c1), Some (Some c2) when Sil.const_equal c1 c2 -> Some (Some c1) + | Some c, None + | None, Some c -> Some c + | _ -> Some None + +(** Dataflow struct *) +module ConstantFlow = Dataflow.MakeDF(struct + type t = (Sil.const option) ConstantMap.t + + let pp fmt constants = + let print_kv k = function + | Some v -> Format.fprintf fmt " %s -> %a@." k (Sil.pp_const pe_text) v + | _ -> Format.fprintf fmt " %s -> None@." k in + Format.fprintf fmt "[@."; + ConstantMap.iter print_kv constants; + Format.fprintf fmt "]@." + + (* Item-wise equality where values are equal iff + - both are None + - both are a constant and equal wrt. Sil.const_equal *) + let equal m n = ConstantMap.equal (opt_equal Sil.const_equal) m n + + let join = ConstantMap.merge merge_values + + let proc_throws pn = Dataflow.DontKnow + + let do_node node constants = + + let do_instr constants instr = + try + let update key value constants = + ConstantMap.merge + merge_values + constants + (ConstantMap.add key value ConstantMap.empty) in + + match instr with + | Sil.Letderef (i, Sil.Lvar p, _, _) -> (* tmp = var *) + let lvar = Ident.to_string i in + let rvar = Sil.pvar_to_string p in + update lvar (ConstantMap.find rvar constants) constants + + | Sil.Set (Sil.Lvar p, _, Sil.Const c, _) -> (* var = const *) + update (Sil.pvar_to_string p) (Some c) constants + + | Sil.Set (Sil.Lvar p, _, Sil.Var i, _) -> (* var = tmp *) + let lvar = Sil.pvar_to_string p in + let rvar = Ident.to_string i in + update lvar (ConstantMap.find rvar constants) constants + + (* Handle propagation of string with StringBuilder. Does not handle null case *) + | Sil.Call (_, Sil.Const (Sil.Cfun pn), (Sil.Var sb, _):: [], _, _) + when Procname.java_get_class pn = "java.lang.StringBuilder" + && Procname.java_get_method pn = "" -> (* StringBuilder. *) + update (Ident.to_string sb) (Some (Sil.Cstr "")) constants + + | Sil.Call (i:: [], Sil.Const (Sil.Cfun pn), (Sil.Var i1, _):: [], _, _) + when Procname.java_get_class pn = "java.lang.StringBuilder" + && Procname.java_get_method pn = "toString" -> (* StringBuilder.toString *) + let lvar = Ident.to_string i in + let rvar = Ident.to_string i1 in + update lvar (ConstantMap.find rvar constants) constants + + | Sil.Call (i:: [], Sil.Const (Sil.Cfun pn), (Sil.Var i1, _):: (Sil.Var i2, _):: [], _, _) + when Procname.java_get_class pn = "java.lang.StringBuilder" + && Procname.java_get_method pn = "append" -> (* StringBuilder.append *) + let lvar = Ident.to_string i in + let rvar1 = Ident.to_string i1 in + let rvar2 = Ident.to_string i2 in + (match ConstantMap.find rvar1 constants, ConstantMap.find rvar2 constants with + | Some (Sil.Cstr s1), Some (Sil.Cstr s2) -> + begin + let s = s1 ^ s2 in + let u = + if String.length s < string_widening_limit then + Some (Sil.Cstr s) + else + None in + update lvar u constants + end + | _ -> constants) + + | _ -> constants + with Not_found -> constants in + + if verbose then + begin + L.stdout "Node %i:" (Cfg.Node.get_id node); + L.stdout "%a" pp constants; + list_iter + (fun instr -> L.stdout "%a@." (Sil.pp_instr pe_text) instr) + (Cfg.Node.get_instrs node) + end; + let constants = + list_fold_left + do_instr + constants + (Cfg.Node.get_instrs node) in + if verbose then L.stdout "%a\n@." pp constants; + [constants], [constants] +end) + +let run proc_desc = + let transitions = ConstantFlow.run proc_desc ConstantMap.empty in + let get_constants node = + match transitions node with + | ConstantFlow.Transition (_, post_states, _) -> ConstantFlow.join post_states ConstantMap.empty + | ConstantFlow.Dead_state -> ConstantMap.empty in + get_constants diff --git a/infer/src/checkers/constantPropagation.mli b/infer/src/checkers/constantPropagation.mli new file mode 100644 index 000000000..52ce4dcb0 --- /dev/null +++ b/infer/src/checkers/constantPropagation.mli @@ -0,0 +1,9 @@ +(* +* Copyright (c) 2014 - Facebook. All rights reserved. +*) + +module ConstantMap: Map.S with type key = string + +module ConstantFlow: Dataflow.DF with type state = (Sil.const option) ConstantMap.t + +val run: Cfg.Procdesc.t -> (Cfg.Node.t -> ConstantFlow.state) diff --git a/infer/src/checkers/dataflow.ml b/infer/src/checkers/dataflow.ml new file mode 100644 index 000000000..8436ab154 --- /dev/null +++ b/infer/src/checkers/dataflow.ml @@ -0,0 +1,180 @@ +(* +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +open Utils + +module L = Logging + +type throws = + | DontKnow (** May or may not throw an exception. *) + | Throws (** Definitely throws an exception. *) + | DoesNotThrow (** Does not throw an exception. *) + +(** Module type used to define the state component for a dataflow algorithm. *) +module type DFStateType = sig + type t (** Type for state. *) + val equal : t -> t -> bool (** Equality between states. *) + val join : t -> t -> t (** Join two states (the old one is the first parameter). *) + + (** Perform a state transition on a node. *) + val do_node : Cfg.Node.t -> t -> (t list) * (t list) + + val proc_throws : Procname.t -> throws (** Can proc throw an exception? *) +end + +(** Type for the dataflow API. *) +module type DF = sig + type t + type state + type transition = + | Dead_state + | Transition of state * state list * state list + + val join : state list -> state -> state + val run : Cfg.Procdesc.t -> state -> (Cfg.Node.t -> transition) +end + +(** Determine if the node can throw an exception. *) +let node_throws node (proc_throws : Procname.t -> throws) : throws = + let instr_throws instr = + let pvar_is_return pvar = + let pdesc = Cfg.Node.get_proc_desc node in + let ret_pvar = Cfg.Procdesc.get_ret_var pdesc in + Sil.pvar_equal pvar ret_pvar in + match instr with + | Sil.Set (Sil.Lvar pvar, typ, Sil.Const (Sil.Cexn _), loc) when pvar_is_return pvar -> + (* assignment to return variable is an artifact of a throw instruction *) + Throws + | Sil.Call (_, Sil.Const (Sil.Cfun callee_pn), args, loc, _) + when SymExec.function_is_builtin callee_pn -> + if Procname.equal callee_pn SymExec.ModelBuiltins.__cast + then DontKnow + else DoesNotThrow + | Sil.Call (_, Sil.Const (Sil.Cfun callee_pn), args, loc, _) -> + proc_throws callee_pn + | _ -> + DoesNotThrow in + + let res = ref DoesNotThrow in + let update_res throws = match !res, throws with + | DontKnow, DontKnow -> res := DontKnow + | Throws, _ + | _, Throws -> res := Throws + | DoesNotThrow, t + | t, DoesNotThrow -> res := t in + let do_instr instr = update_res (instr_throws instr) in + + list_iter do_instr (Cfg.Node.get_instrs node); + !res + +(** Create an instance of the dataflow algorithm given a state parameter. *) +module MakeDF(St: DFStateType) : DF with type state = St.t = struct + module S = Cfg.NodeSet + module H = Cfg.NodeHash + module N = Cfg.Node + + type worklist = S.t + type statemap = St.t H.t + type statelistmap = (St.t list) H.t + type t = { + mutable worklist: worklist; + pre_states : statemap; + post_states : statelistmap; + exn_states : statelistmap; + proc_desc : Cfg.Procdesc.t + } + type state = St.t + type transition = + | Dead_state + | Transition of state * state list * state list + + let join states initial_state = + list_fold_left + St.join + initial_state + states + + (** Propagate [new_state] to all the nodes immediately reachable. *) + let propagate t node states_succ states_exn (throws : throws) = + let propagate_to_dest new_state dest_node = + let push_state s = + H.replace t.pre_states dest_node s; + t.worklist <- S.add dest_node t.worklist in + try + let dest_state = H.find t.pre_states dest_node in + let dest_joined = St.join dest_state new_state in + if not (St.equal dest_state dest_joined) then + push_state dest_joined + with Not_found -> push_state new_state in + + let succ_nodes = Cfg.Node.get_succs node in + let exn_nodes = Cfg.Node.get_exn node in + if throws <> Throws then + list_iter + (fun s -> list_iter (propagate_to_dest s) succ_nodes) + states_succ; + if throws <> DoesNotThrow then + list_iter + (fun s -> list_iter (propagate_to_dest s) exn_nodes) + states_exn; + + H.replace t.post_states node states_succ; + H.replace t.exn_states node states_exn + + (** Run the worklist-based dataflow algorithm. *) + let run proc_desc state = + + let t = + let start_node = Cfg.Procdesc.get_start_node proc_desc in + let init_set = S.singleton start_node in + let init_statemap = + let m = H.create 1 in + H.replace m start_node state; m in + { + worklist = init_set; + pre_states = init_statemap; + post_states = H.create 0; + exn_states = H.create 0; + proc_desc = proc_desc + } in + + let () = + while (not (S.is_empty t.worklist)) do + let node = S.min_elt t.worklist in + t.worklist <- S.remove node t.worklist; + try + let state = H.find t.pre_states node in + let states_succ, states_exn = St.do_node node state in + propagate t node states_succ states_exn (node_throws node St.proc_throws) + with Not_found -> () + done in + + let transitions node = + try + Transition + (H.find t.pre_states node, H.find t.post_states node, H.find t.exn_states node) + with Not_found -> Dead_state in + + transitions + +end (* MakeDF *) + +(** Example dataflow callback: compute the the distance from a node to the start node. *) +let callback_test_dataflow all_procs get_proc_desc idenv tenv proc_name proc_desc = + let verbose = false in + let module DFCount = MakeDF(struct + type t = int + let equal = int_equal + let join n m = if n = 0 then m else n + let do_node n s = + if verbose then L.stdout "visiting node %a with state %d@." Cfg.Node.pp n s; + [s + 1], [s + 1] + let proc_throws pn = DoesNotThrow + end) in + let transitions = DFCount.run proc_desc 0 in + let do_node node = + match transitions node with + | DFCount.Transition (pre_state, _, _) -> () + | DFCount.Dead_state -> () in + list_iter do_node (Cfg.Procdesc.get_nodes proc_desc) diff --git a/infer/src/checkers/dataflow.mli b/infer/src/checkers/dataflow.mli new file mode 100644 index 000000000..199beab4f --- /dev/null +++ b/infer/src/checkers/dataflow.mli @@ -0,0 +1,38 @@ +(* +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +type throws = + | DontKnow (** May or may not throw an exception. *) + | Throws (** Definitely throws an exception. *) + | DoesNotThrow (** Does not throw an exception. *) + +(** Module type used to define the state component for a dataflow algorithm. *) +module type DFStateType = sig + type t (** Type for state. *) + val equal : t -> t -> bool (** Equality between states. *) + val join : t -> t -> t (** Join two states (the old one is the first parameter). *) + + (** Perform a state transition on a node. *) + val do_node : Cfg.Node.t -> t -> (t list) * (t list) + + val proc_throws : Procname.t -> throws (** Can proc throw an exception? *) +end + +(** Type for the dataflow API. *) +module type DF = sig + type t + type state + type transition = + | Dead_state + | Transition of state * state list * state list + val join : state list -> state -> state + (** Run the dataflow analysis on a procedure starting from the given state. + Returns a function to lookup the results of the analysis on every node *) + val run : Cfg.Procdesc.t -> state -> (Cfg.Node.t -> transition) +end + +(** Functor to create an instance of a dataflow analysis. *) +module MakeDF(St: DFStateType) : DF with type state = St.t + +val callback_test_dataflow : Callbacks.proc_callback_t diff --git a/infer/src/checkers/eradicate.ml b/infer/src/checkers/eradicate.ml new file mode 100644 index 000000000..06636dcb1 --- /dev/null +++ b/infer/src/checkers/eradicate.ml @@ -0,0 +1,389 @@ +(* +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +(** Eradicate NPEs. *) + +module L = Logging +module F = Format +open Utils +open Dataflow + +(* ERADICATE CHECKER. TODOS:*) +(* 1) add support for constructors for anonymous inner classes (currently not checked) *) + +(* print initial and final typestates *) +let verbose = Config.from_env_variable "ERADICATE_TYPINGS" + +(* print step-by-step tracing information *) +let trace = Config.from_env_variable "ERADICATE_TRACE" + +let check_field_initialization = true (* check that nonnullable fields are initialized in constructors *) + +type parameters = TypeState.parameters + + +(** Type for a module that provides a main callback function *) +module type CallBackT = +sig + val callback : + TypeCheck.checks -> Procname.t list -> TypeCheck.get_proc_desc -> + Idenv.t -> Sil.tenv -> Procname.t -> + Cfg.Procdesc.t -> unit +end (* CallBackT *) + +(** Extension to the type checker. *) +module type ExtensionT = sig + type extension + val ext : extension TypeState.ext + val mkpayload : extension TypeState.t option -> Specs.payload +end + +(** Create a module with the toplevel callback. *) +module MkCallback + (Extension : ExtensionT) +: CallBackT = +struct + (** Update the summary with stats from the checker. *) + let update_summary proc_name proc_desc final_typestate_opt = + let old_summ = Specs.get_summary_unsafe proc_name in + let nodes = list_map (fun n -> Cfg.Node.get_id n) (Cfg.Procdesc.get_nodes proc_desc) in + let method_annotation = + (Specs.proc_get_attributes proc_name proc_desc).Sil.method_annotation in + let new_summ = + { + old_summ with + Specs.loc = Cfg.Procdesc.get_loc proc_desc; + Specs.nodes = nodes; + Specs.payload = Extension.mkpayload final_typestate_opt; + Specs.attributes = + { + old_summ.Specs.attributes with + Sil.method_annotation = method_annotation + }; + } in + Specs.add_summary proc_name new_summ + + let callback1 + find_canonical_duplicate calls_this checks get_proc_desc idenv tenv curr_pname + curr_pdesc annotated_signature linereader proc_loc : bool * 'a TypeState.t option = + let mk_pvar s = Sil.mk_pvar (Mangled.from_string s) curr_pname in + let add_formal typestate (s, ia, typ) = + let pvar = mk_pvar s in + let ta = + let origin = TypeOrigin.Formal s in + TypeAnnotation.from_item_annotation ia origin in + TypeState.add_pvar pvar (typ, ta, []) typestate in + let get_initial_typestate () = + let typestate_empty = TypeState.empty Extension.ext in + list_fold_left add_formal typestate_empty annotated_signature.Annotations.params in + + (** Check the nullable flag computed for the return value and report inconsistencies. *) + let check_return find_canonical_duplicate exit_node final_typestate ret_ia ret_type loc : unit = + let ret_pvar = Cfg.Procdesc.get_ret_var curr_pdesc in + let ret_range = TypeState.lookup_pvar ret_pvar final_typestate in + let typ_found_opt = match ret_range with + | Some (typ_found, _, _) -> Some typ_found + | None -> None in + let ret_implicitly_nullable = + string_equal (PatternMatch.get_type_name ret_type) "java.lang.Void" in + State.set_node exit_node; + + if checks.TypeCheck.check_ret_type <> [] then + list_iter + (fun f -> f curr_pname curr_pdesc ret_type typ_found_opt loc) + checks.TypeCheck.check_ret_type; + if checks.TypeCheck.eradicate then + EradicateChecks.check_return_annotation + find_canonical_duplicate curr_pname curr_pdesc exit_node ret_range + ret_ia ret_implicitly_nullable loc in + + let do_before_dataflow initial_typestate = + if verbose then + L.stdout "Initial Typestate@\n%a@." + (TypeState.pp Extension.ext) initial_typestate in + + let do_after_dataflow find_canonical_duplicate final_typestate = + let exit_node = Cfg.Procdesc.get_exit_node curr_pdesc in + let ia, ret_type = annotated_signature.Annotations.ret in + check_return find_canonical_duplicate exit_node final_typestate ia ret_type proc_loc in + + let module DFTypeCheck = MakeDF(struct + type t = Extension.extension TypeState.t + let initial = TypeState.empty Extension.ext + let equal = TypeState.equal + let join = TypeState.join Extension.ext + let do_node node typestate = + State.set_node node; + let typestates_succ, typestates_exn = + TypeCheck.typecheck_node + Extension.ext calls_this checks idenv get_proc_desc curr_pname curr_pdesc + find_canonical_duplicate annotated_signature typestate node linereader in + if trace then + list_iter (fun typestate_succ -> + L.stdout + "Typestate After Node %a@\n%a@." + Cfg.Node.pp node + (TypeState.pp Extension.ext) typestate_succ) + typestates_succ; + typestates_succ, typestates_exn + let proc_throws pn = DontKnow + end) in + let initial_typestate = get_initial_typestate () in + do_before_dataflow initial_typestate; + let transitions = DFTypeCheck.run curr_pdesc initial_typestate in + match transitions (Cfg.Procdesc.get_exit_node curr_pdesc) with + | DFTypeCheck.Transition (final_typestate, _, _) -> + do_after_dataflow find_canonical_duplicate final_typestate; + !calls_this, Some final_typestate + | DFTypeCheck.Dead_state -> + !calls_this, None + + let callback2 + calls_this checks all_procs get_proc_desc idenv tenv curr_pname + curr_pdesc annotated_signature linereader proc_loc : unit = + + let find_duplicate_nodes = State.mk_find_duplicate_nodes curr_pdesc in + let find_canonical_duplicate node = + let duplicate_nodes = find_duplicate_nodes node in + try Cfg.NodeSet.min_elt duplicate_nodes with + | Not_found -> node in + + let typecheck_proc do_checks idenv_pn pname pdesc ann_sig loc = + let checks', calls_this' = + if do_checks then checks, calls_this + else + { + TypeCheck.eradicate = false; + check_extension = false; + check_ret_type = []; + }, ref false in + callback1 + find_canonical_duplicate calls_this' checks' get_proc_desc idenv_pn + tenv pname pdesc ann_sig linereader loc in + + let module Initializers = struct + type init = Procname.t * Cfg.Procdesc.t + + let final_typestates initializers_current_class = + (** Get the private methods, from the same class, directly called by the initializers. *) + let get_private_called (initializers : init list) : init list = + let res = ref [] in + let do_proc (init_pn, init_pd) = + let filter callee_pn callee_pd = + let is_private = + (Specs.proc_get_attributes callee_pn callee_pd).Sil.access = Sil.Private in + let same_class = + let get_class_opt pn = + if Procname.is_java pn then Some (Procname.java_get_class pn) + else None in + get_class_opt init_pn = get_class_opt callee_pn in + is_private && same_class in + let private_called = PatternMatch.proc_calls get_proc_desc init_pn init_pd filter in + res := private_called @ !res in + list_iter do_proc initializers; + !res in + + (** Get the initializers recursively called by computing a fixpoint. + Start from the initializers of the current class and the current procedure. *) + let initializers_recursive : init list = + let initializers_base_case = initializers_current_class in + + let res = ref [] in + let seen = ref Procname.Set.empty in + let mark_seen (initializers : init list) : unit = + list_iter (fun (pn, _) -> seen := Procname.Set.add pn !seen) initializers; + res := !res @ initializers in + + let rec fixpoint initializers_old = + let initializers_new = get_private_called initializers_old in + let initializers_new' = + list_filter (fun (pn, _) -> not (Procname.Set.mem pn !seen)) initializers_new in + mark_seen initializers_new'; + if initializers_new' <> [] then fixpoint initializers_new' in + + mark_seen initializers_base_case; + fixpoint initializers_base_case; + !res in + + (** Get the final typestates of all the initializers. *) + let final_typestates = ref [] in + let get_final_typestate (pname, pdesc) = + let ann_sig = Models.get_annotated_signature pdesc pname in + let loc = Cfg.Procdesc.get_loc pdesc in + let idenv_pn = Idenv.create_from_idenv idenv pdesc in + match typecheck_proc false idenv_pn pname pdesc ann_sig loc with + | _, Some final_typestate -> + final_typestates := (pname, final_typestate) :: !final_typestates + | _, None -> () in + list_iter get_final_typestate initializers_recursive; + list_rev !final_typestates + + let pname_and_pdescs_with f = + list_map + (fun n -> match get_proc_desc n with + | Some d -> [(n, d)] + | None -> []) + all_procs + |> list_flatten + |> list_filter f + + (** Typestates after the current procedure and all initializer procedures. *) + let final_initializer_typestates_lazy = lazy + begin + let is_initializer pdesc pname = + PatternMatch.method_is_initializer tenv pname pdesc || + let ia, _ = (Models.get_annotated_signature pdesc pname).Annotations.ret in + Annotations.ia_is_initializer ia in + let initializers_current_class = + pname_and_pdescs_with + (fun (pname, pdesc) -> + is_initializer pdesc pname && + Procname.java_get_class pname = Procname.java_get_class curr_pname) in + final_typestates ((curr_pname, curr_pdesc):: initializers_current_class) + end + + (** Typestates after all constructors. *) + let final_constructor_typestates_lazy = lazy + begin + let constructors_current_class = + pname_and_pdescs_with + (fun (n, d) -> + Procname.is_constructor n && + Procname.java_get_class n = Procname.java_get_class curr_pname) in + final_typestates constructors_current_class + end + + end (* Initializers *) in + + let do_final_typestate typestate_opt calls_this = + let do_typestate typestate = + let start_node = Cfg.Procdesc.get_start_node curr_pdesc in + if not calls_this && (* if 'this(...)' is called, no need to check initialization *) + check_field_initialization && + checks.TypeCheck.eradicate + then begin + EradicateChecks.check_constructor_initialization + find_canonical_duplicate + curr_pname + curr_pdesc + start_node + typestate + Initializers.final_initializer_typestates_lazy + Initializers.final_constructor_typestates_lazy + proc_loc + end; + if verbose then + L.stdout "Final Typestate@\n%a@." + (TypeState.pp Extension.ext) typestate in + match typestate_opt with + | None -> () + | Some typestate -> do_typestate typestate in + + TypeErr.reset (); + + let calls_this, final_typestate_opt = + typecheck_proc true idenv curr_pname curr_pdesc annotated_signature proc_loc in + do_final_typestate final_typestate_opt calls_this; + if checks.TypeCheck.eradicate then + EradicateChecks.check_overridden_annotations + find_canonical_duplicate get_proc_desc + tenv curr_pname curr_pdesc + annotated_signature; + + TypeErr.report_forall_checks_and_reset Checkers.ST.report_error curr_pname; + update_summary curr_pname curr_pdesc final_typestate_opt + + (** Entry point for the eradicate-based checker infrastructure. *) + let callback checks all_procs get_proc_desc idenv tenv proc_name proc_desc = + let calls_this = ref false in + + let filter_special_cases () = + if Procname.java_is_access_method proc_name || + (Specs.proc_get_attributes proc_name proc_desc).Sil.is_bridge_method + then None + else + begin + let annotated_signature = Models.get_annotated_signature proc_desc proc_name in + if (Specs.proc_get_attributes proc_name proc_desc).Sil.is_abstract then + begin + if Models.infer_library_return && + EradicateChecks.classify_procedure proc_name proc_desc = "L" then + (let ret_is_nullable = (* get the existing annotation *) + let ia, _ = annotated_signature.Annotations.ret in + Annotations.ia_is_nullable ia in + EradicateChecks.pp_inferred_return_annotation ret_is_nullable proc_name); + Some annotated_signature + end + else + Some annotated_signature + end in + match filter_special_cases () with + | None -> () + | Some annotated_signature -> + let loc = Cfg.Procdesc.get_loc proc_desc in + let linereader = Printer.LineReader.create () in + if verbose then + L.stdout "%a@." + (Annotations.pp_annotated_signature proc_name) + annotated_signature; + + callback2 + calls_this checks all_procs get_proc_desc idenv tenv + proc_name proc_desc annotated_signature linereader loc + +end (* MkCallback *) + +(** Given an extension to the typestate with a check, call the check on each instruction. *) +module Build + (Extension : ExtensionT) +: CallBackT = +struct + module Callback = MkCallback(Extension) + let callback = Callback.callback +end (* Build *) + +module EmptyExtension : ExtensionT = +struct + type extension = unit + let ext = + let empty = () in + let check_instr get_proc_desc proc_name proc_desc node ext instr param = ext in + let join () () = () in + let pp fmt () = () in + { + TypeState.empty = empty; + check_instr = check_instr; + join = join; + pp = pp; + } + let mkpayload typestate_opt = Specs.TypeState typestate_opt +end + +module Main = + Build(EmptyExtension) + +(** Eradicate checker for Java @Nullable annotations. *) +let callback_eradicate all_procs get_proc_desc idenv tenv proc_name1 proc_desc1 = + let checks = + { + TypeCheck.eradicate = true; + check_extension = false; + check_ret_type = []; + } in + Main.callback checks all_procs get_proc_desc idenv tenv proc_name1 proc_desc1 + +(** Call the given check_return_type at the end of every procedure. *) +let callback_check_return_type check_return_type all_procs + get_proc_desc idenv tenv proc_name1 proc_desc1 = + let checks = + { + TypeCheck.eradicate = false; + check_extension = false; + check_ret_type = [check_return_type]; + } in + Main.callback checks all_procs get_proc_desc idenv tenv proc_name1 proc_desc1 + +(** Export the ability to add nullable annotations. *) +let field_add_nullable_annotation fieldname = + Models.Inference.field_add_nullable_annotation fieldname diff --git a/infer/src/checkers/eradicate.mli b/infer/src/checkers/eradicate.mli new file mode 100644 index 000000000..16c6e1fca --- /dev/null +++ b/infer/src/checkers/eradicate.mli @@ -0,0 +1,34 @@ +(* +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +(** Eradicate NPEs. *) + +val callback_eradicate : Callbacks.proc_callback_t + +val callback_check_return_type : TypeCheck.check_return_type -> Callbacks.proc_callback_t + + +(** Parameters of a call. *) +type parameters = (Sil.exp * Sil.typ) list + + +(** Type for a module that provides a main callback function *) +module type CallBackT = +sig + val callback : + TypeCheck.checks -> Procname.t list -> TypeCheck.get_proc_desc -> + Idenv.t -> Sil.tenv -> Procname.t -> + Cfg.Procdesc.t -> unit +end (* CallBackT *) + + +(** Extension to the type checker. *) +module type ExtensionT = sig + type extension + val ext : extension TypeState.ext + val mkpayload : extension TypeState.t option -> Specs.payload +end + +(** Given an extension to the typestate with a check, call the check on each instruction. *) +module Build (Extension : ExtensionT) : CallBackT diff --git a/infer/src/checkers/eradicateChecks.ml b/infer/src/checkers/eradicateChecks.ml new file mode 100644 index 000000000..c5a318567 --- /dev/null +++ b/infer/src/checkers/eradicateChecks.ml @@ -0,0 +1,555 @@ +(* +* Copyright (c) 2014 - Facebook. All rights reserved. +*) + +open Utils +module L = Logging + +(** Module for the checks called by Eradicate. *) + +(* activate the condition redundant warnings *) +let activate_condition_redundant = Config.from_env_variable "ERADICATE_CONDITION_REDUNDANT" + +(* activate check for @Present annotations *) +let activate_optional_present = Config.from_env_variable "ERADICATE_OPTIONAL_PRESENT" + +(* activate the field not mutable warnings *) +let activate_field_not_mutable = Config.from_env_variable "ERADICATE_FIELD_NOT_MUTABLE" + +(* activate the field over annotated warnings *) +let activate_field_over_annotated = Config.from_env_variable "ERADICATE_FIELD_OVER_ANNOTATED" + +(* activate the return over annotated warning *) +let activate_return_over_annotated = Config.from_env_variable "ERADICATE_RETURN_OVER_ANNOTATED" + +(* do not report RETURN_NOT_NULLABLE if the return is annotated @Nonnull *) +let return_nonnull_silent = true + +(* if true, check calls to libraries (i.e. not modelled and source not available) *) +let check_library_calls = false + + +let get_field_annotation fn typ = + match Annotations.get_field_type_and_annotation fn typ with + | None -> None + | Some (t, ia) -> + let ia' = + (* TODO (t4968422) eliminate not !Config.eradicate check by marking fields as nullified *) + (* outside of Eradicate in some other way *) + if (Models.Inference.enabled || not !Config.eradicate) + && Models.Inference.field_is_marked fn + then Annotations.mk_ia Annotations.Nullable ia + else ia in + Some (t, ia') + +let report_error = TypeErr.report_error Checkers.ST.report_error + +let explain_expr node e = + match Errdesc.exp_rv_dexp node e with + | Some de -> Some (Sil.dexp_to_string de) + | None -> None + +(** Classify a procedure. *) +let classify_procedure pn pd = + let unique_id = Procname.to_unique_id pn in + let classification = + if Models.is_modelled_nullable pn then "M" (* modelled *) + else if Models.is_ret_library pn then "R" (* return library *) + else if Specs.proc_is_library pn pd then "L" (* library *) + else if not (Cfg.Procdesc.is_defined pd) then "S" (* skip *) + else if string_is_prefix "com.facebook" unique_id then "F" (* FB *) + else "?" in + classification + +let pp_inferred_return_annotation is_nullable proc_name = + L.stdout "(*InferredLibraryReturnAnnotation*) %5b, \"%s\";@." + is_nullable + (Procname.to_unique_id proc_name) + + +let is_virtual = function + | ("this", _, _):: _ -> true + | _ -> false + + +(** Check an access (read or write) to a field. *) +let check_field_access + find_canonical_duplicate curr_pname node instr_ref exp fname ta loc : unit = + if TypeAnnotation.get_value Annotations.Nullable ta = true then + let origin_descr = TypeAnnotation.descr_origin ta in + report_error + find_canonical_duplicate + node + (TypeErr.Null_field_access (explain_expr node exp, fname, origin_descr, false)) + (Some instr_ref) + loc curr_pname + +(** Check an access to an array *) +let check_array_access + find_canonical_duplicate + curr_pname + node + instr_ref + array_exp + fname + ta + loc + indexed = + if TypeAnnotation.get_value Annotations.Nullable ta = true then + let origin_descr = TypeAnnotation.descr_origin ta in + report_error + find_canonical_duplicate + node + (TypeErr.Null_field_access (explain_expr node array_exp, fname, origin_descr, indexed)) + (Some instr_ref) + loc + curr_pname + +(** Where the condition is coming from *) +type from_call = + | From_condition (** Direct condition *) + | From_instanceof (** x instanceof C *) + | From_optional_isPresent (** x.isPresent *) + | From_containsKey (** x.containsKey *) + +(** Check the normalized "is zero" or "is not zero" condition of a prune instruction. *) +let check_condition case_zero find_canonical_duplicate get_proc_desc curr_pname + node e typ ta true_branch from_call idenv linereader loc instr_ref : unit = + let is_fun_nonnull ta = match TypeAnnotation.get_origin ta with + | TypeOrigin.Proc (_, _, signature, _) -> + let (ia, _) = signature.Annotations.ret in + Annotations.ia_is_nonnull ia + | _ -> false in + + let contains_instanceof_throwable node = + (* Check if the current procedure has a catch Throwable. *) + (* That always happens in the bytecode generated by try-with-resources. *) + let loc = Cfg.Node.get_loc node in + let throwable_found = ref false in + let throwable_class = Mangled.from_string "java.lang.Throwable" in + let typ_is_throwable = function + | Sil.Tstruct (_, _, Sil.Class, Some c, _, _, _) -> + Mangled.equal c throwable_class + | _ -> false in + let do_instr = function + | Sil.Call (_, Sil.Const (Sil.Cfun pn), [_; (Sil.Sizeof(t, _), _)], _, _) when + Procname.equal pn SymExec.ModelBuiltins.__instanceof && typ_is_throwable t -> + throwable_found := true + | _ -> () in + let do_node n = + if Sil.loc_equal loc (Cfg.Node.get_loc n) + then list_iter do_instr (Cfg.Node.get_instrs n) in + Cfg.Procdesc.iter_nodes do_node (Cfg.Node.get_proc_desc node); + !throwable_found in + + let from_try_with_resources () : bool = + (* heuristic to check if the condition is the translation of try-with-resources *) + match Printer.LineReader.from_loc linereader loc with + | Some line -> + not (string_contains "==" line || string_contains "!=" line) + && (string_contains "}" line) + && contains_instanceof_throwable node + | None -> false in + + let is_temp = Idenv.exp_is_temp idenv e in + let nonnull = is_fun_nonnull ta in + let should_report = + TypeAnnotation.get_value Annotations.Nullable ta = false && + (activate_condition_redundant || nonnull) && + true_branch && + (not is_temp || nonnull) && + PatternMatch.type_is_class typ && + not (from_try_with_resources ()) && + from_call = From_condition && + not (TypeAnnotation.origin_is_fun_library ta) in + let is_always_true = not case_zero in + let nonnull = is_fun_nonnull ta in + if should_report then + report_error + find_canonical_duplicate + node + (TypeErr.Condition_redundant (is_always_true, explain_expr node e, nonnull)) + (Some instr_ref) + loc curr_pname + +(** Check an "is zero" condition. *) +let check_zero find_canonical_duplicate = check_condition true find_canonical_duplicate + +(** Check an "is not zero" condition. *) +let check_nonzero find_canonical_duplicate = check_condition false find_canonical_duplicate + +(** Check an assignment to a field. *) +let check_field_assignment + find_canonical_duplicate curr_pname node instr_ref typestate exp_lhs + exp_rhs typ loc fname t_ia_opt typecheck_expr print_current_state : unit = + let (t_lhs, ta_lhs, _) = + typecheck_expr node instr_ref curr_pname typestate exp_lhs + (typ, TypeAnnotation.const Annotations.Nullable false TypeOrigin.ONone, [loc]) loc in + let (_, ta_rhs, _) = + typecheck_expr node instr_ref curr_pname typestate exp_rhs + (typ, TypeAnnotation.const Annotations.Nullable false TypeOrigin.ONone, [loc]) loc in + let should_report_nullable = + TypeAnnotation.get_value Annotations.Nullable ta_lhs = false && + TypeAnnotation.get_value Annotations.Nullable ta_rhs = true && + PatternMatch.type_is_class t_lhs && + not (Ident.java_fieldname_is_outer_instance fname) in + let should_report_absent = + activate_optional_present && + TypeAnnotation.get_value Annotations.Present ta_lhs = true && + TypeAnnotation.get_value Annotations.Present ta_rhs = false && + not (Ident.java_fieldname_is_outer_instance fname) in + let should_report_mutable = + let field_is_mutable () = match t_ia_opt with + | Some (_, ia) -> Annotations.ia_is_mutable ia + | _ -> true in + activate_field_not_mutable && + not (Procname.is_constructor curr_pname) && + not (Procname.is_class_initializer curr_pname) && + not (field_is_mutable ()) in + if should_report_nullable || should_report_absent then + begin + let ann = if should_report_nullable then Annotations.Nullable else Annotations.Present in + if Models.Inference.enabled then Models.Inference.field_add_nullable_annotation fname; + let origin_descr = TypeAnnotation.descr_origin ta_rhs in + report_error + find_canonical_duplicate + node + (TypeErr.Field_annotation_inconsistent (ann, fname, origin_descr)) + (Some instr_ref) + loc curr_pname + end; + if should_report_mutable then + begin + let origin_descr = TypeAnnotation.descr_origin ta_rhs in + report_error + find_canonical_duplicate + node + (TypeErr.Field_not_mutable (fname, origin_descr)) + (Some instr_ref) + loc curr_pname + end + + +(** Check that nonnullable fields are initialized in constructors. *) +let check_constructor_initialization + find_canonical_duplicate + curr_pname + curr_pdesc + start_node + final_typestate + final_initializer_typestates + final_constructor_typestates + loc: unit = + State.set_node start_node; + if Procname.is_constructor curr_pname + then begin + match PatternMatch.get_this_type curr_pdesc with + | Some (Sil.Tptr (Sil.Tstruct (ftal, _, _, nameo, _, _, _) as ts, _)) -> + let do_fta (fn, ft, ia) = + let annotated_with f = match get_field_annotation fn ts with + | None -> false + | Some (_, ia) -> f ia in + let nullable_annotated = annotated_with Annotations.ia_is_nullable in + let nonnull_annotated = annotated_with Annotations.ia_is_nonnull in + let inject_annotated = annotated_with Annotations.ia_is_inject in + + let final_type_annotation_with unknown list f = + let filter_range_opt = function + | Some (_, ta, _) -> f ta + | None -> unknown in + list_exists + (function pname, typestate -> + let pvar = Sil.mk_pvar + (Mangled.from_string (Ident.fieldname_to_string fn)) + pname in + filter_range_opt (TypeState.lookup_pvar pvar typestate)) + list in + + let may_be_assigned_in_final_typestate = + final_type_annotation_with + false + (Lazy.force final_initializer_typestates) + (fun ta -> TypeAnnotation.get_origin ta <> TypeOrigin.Undef) in + + let may_be_nullable_in_final_typestate () = + final_type_annotation_with + true + (Lazy.force final_constructor_typestates) + (fun ta -> TypeAnnotation.get_value Annotations.Nullable ta = true) in + + let should_check_field = + let in_current_class = + let fld_cname = Ident.java_fieldname_get_class fn in + match nameo with + | None -> false + | Some name -> Mangled.equal name (Mangled.from_string fld_cname) in + not inject_annotated && + PatternMatch.type_is_class ft && + in_current_class && + not (Ident.java_fieldname_is_outer_instance fn) in + + if should_check_field then + begin + if Models.Inference.enabled then Models.Inference.field_add_nullable_annotation fn; + + (* Check if field is missing annotation. *) + if not (nullable_annotated || nonnull_annotated) && + not may_be_assigned_in_final_typestate then + report_error + find_canonical_duplicate + start_node + (TypeErr.Field_not_initialized (fn, curr_pname)) + None + loc + curr_pname; + + (* Check if field is over-annotated. *) + if activate_field_over_annotated && + nullable_annotated && + not (may_be_nullable_in_final_typestate ()) then + report_error + find_canonical_duplicate + start_node + (TypeErr.Field_over_annotated (fn, curr_pname)) + None + loc + curr_pname; + end in + + list_iter do_fta ftal + | _ -> () + end + +(** Check the annotations when returning from a method. *) +let check_return_annotation + find_canonical_duplicate curr_pname curr_pdesc exit_node ret_range + ret_ia ret_implicitly_nullable loc : unit = + let ret_annotated_nullable = Annotations.ia_is_nullable ret_ia in + let ret_annotated_present = Annotations.ia_is_present ret_ia in + let ret_annotated_nonnull = Annotations.ia_is_nonnull ret_ia in + let return_not_nullable = + match ret_range with + | Some (_, final_ta, _) -> + let final_nullable = TypeAnnotation.get_value Annotations.Nullable final_ta in + let final_present = TypeAnnotation.get_value Annotations.Present final_ta in + let origin_descr = TypeAnnotation.descr_origin final_ta in + let return_not_nullable = + final_nullable && + not ret_annotated_nullable && + not ret_implicitly_nullable && + not (return_nonnull_silent && ret_annotated_nonnull) in + let return_value_not_present = + activate_optional_present && + not final_present && + ret_annotated_present in + let return_over_annotated = + not final_nullable && + ret_annotated_nullable && + activate_return_over_annotated in + if return_not_nullable || return_value_not_present then + begin + let ann = + if return_not_nullable then Annotations.Nullable else Annotations.Present in + if Models.Inference.enabled then Models.Inference.proc_add_return_nullable curr_pname; + report_error + find_canonical_duplicate + exit_node + (TypeErr.Return_annotation_inconsistent (ann, curr_pname, origin_descr)) + None + loc curr_pname + end; + if return_over_annotated then + begin + report_error + find_canonical_duplicate + exit_node + (TypeErr.Return_over_annotated curr_pname) + None + loc curr_pname + end; + return_not_nullable + | _ -> + false in + if Models.infer_library_return && classify_procedure curr_pname curr_pdesc = "L" + then pp_inferred_return_annotation return_not_nullable curr_pname + +(** Check the receiver of a virtual call. *) +let check_call_receiver + find_canonical_duplicate + curr_pname + node + typestate + call_params + callee_pname + callee_loc + (instr_ref : TypeErr.InstrRef.t) + loc + typecheck_expr + print_current_state : unit = + match call_params with + | ((original_this_e, this_e), typ) :: _ -> + let (_, this_ta, _) = + typecheck_expr node instr_ref curr_pname typestate this_e + (typ, TypeAnnotation.const Annotations.Nullable false TypeOrigin.ONone, []) loc in + let null_method_call = TypeAnnotation.get_value Annotations.Nullable this_ta in + let optional_get_on_absent = + activate_optional_present && + Models.is_optional_get callee_pname && + not (TypeAnnotation.get_value Annotations.Present this_ta) in + if null_method_call || optional_get_on_absent then + begin + let ann = if null_method_call then Annotations.Nullable else Annotations.Present in + let descr = explain_expr node original_this_e in + let origin_descr = TypeAnnotation.descr_origin this_ta in + report_error + find_canonical_duplicate + node + (TypeErr.Call_receiver_annotation_inconsistent + (ann, descr, callee_pname, origin_descr)) + (Some instr_ref) + loc curr_pname + end + | [] -> () + +(** Check the parameters of a call. *) +let check_call_parameters + find_canonical_duplicate curr_pname node typestate callee_pname + callee_pdesc sig_params call_params loc annotated_signature + instr_ref typecheck_expr print_current_state : unit = + let has_this = is_virtual sig_params in + let tot_param_num = list_length sig_params - (if has_this then 1 else 0) in + let rec check sparams cparams = match sparams, cparams with + | (s1, ia1, t1) :: sparams', ((orig_e2, e2), t2) :: cparams' -> + let param_is_this = s1 = "this" in + let formal_is_nullable = Annotations.ia_is_nullable ia1 in + let formal_is_present = Annotations.ia_is_present ia1 in + let (_, ta2, _) = + typecheck_expr node instr_ref curr_pname typestate e2 + (t2, TypeAnnotation.const Annotations.Nullable false TypeOrigin.ONone, []) loc in + let parameter_not_nullable = + not param_is_this && + PatternMatch.type_is_class t1 && + not formal_is_nullable && + TypeAnnotation.get_value Annotations.Nullable ta2 in + let parameter_absent = + activate_optional_present && + not param_is_this && + PatternMatch.type_is_class t1 && + formal_is_present && + not (TypeAnnotation.get_value Annotations.Present ta2) in + if parameter_not_nullable || parameter_absent then + begin + let ann = + if parameter_not_nullable + then Annotations.Nullable + else Annotations.Present in + let description = + match explain_expr node orig_e2 with + | Some descr -> descr + | None -> "formal parameter " ^ s1 in + let origin_descr = TypeAnnotation.descr_origin ta2 in + + let param_num = list_length sparams' + (if has_this then 0 else 1) in + let callee_loc = Cfg.Procdesc.get_loc callee_pdesc in + report_error + find_canonical_duplicate + node + (TypeErr.Parameter_annotation_inconsistent ( + ann, + description, + param_num, + callee_pname, + callee_loc, + origin_descr)) + (Some instr_ref) + loc curr_pname; + if Models.Inference.enabled then + Models.Inference.proc_add_parameter_nullable callee_pname param_num tot_param_num + end; + check sparams' cparams' + | _ -> () in + let should_check_parameters = + if check_library_calls then true + else Models.is_modelled_nullable callee_pname || Cfg.Procdesc.is_defined callee_pdesc in + if should_check_parameters then + (* left to right to avoid guessing the different lengths *) + check (list_rev sig_params) (list_rev call_params) + +(** Checks if the annotations are consistent with the inherited class or with the +implemented interfaces *) +let check_overridden_annotations + find_canonical_duplicate get_proc_desc tenv proc_name proc_desc annotated_signature = + + let start_node = Cfg.Procdesc.get_start_node proc_desc in + let loc = Cfg.Node.get_loc start_node in + + let check_return overriden_proc_name overriden_signature = + let ret_is_nullable = + let ia, _ = annotated_signature.Annotations.ret in + Annotations.ia_is_nullable ia + and ret_overridden_nullable = + let overriden_ia, _ = overriden_signature.Annotations.ret in + Annotations.ia_is_nullable overriden_ia in + if ret_is_nullable && not ret_overridden_nullable then + report_error + find_canonical_duplicate + start_node + (TypeErr.Inconsistent_subclass_return_annotation (proc_name, overriden_proc_name)) + None + loc proc_name + + and check_params overriden_proc_name overriden_signature = + let compare pos current_param overriden_param : int = + let current_name, current_ia, current_type = current_param in + let _, overriden_ia, overriden_type = overriden_param in + let () = + if not (Annotations.ia_is_nullable current_ia) + && Annotations.ia_is_nullable overriden_ia then + report_error + find_canonical_duplicate + start_node + (TypeErr.Inconsistent_subclass_parameter_annotation + (current_name, pos, proc_name, overriden_proc_name)) + None + loc proc_name in + (pos + 1) in + + (* TODO (#5280249): investigate why argument lists can be of different length *) + let current_params = annotated_signature.Annotations.params + and overridden_params = overriden_signature.Annotations.params in + let initial_pos = if is_virtual current_params then 0 else 1 in + if (list_length current_params) = (list_length overridden_params) then + ignore (list_fold_left2 compare initial_pos current_params overridden_params) in + + let check overriden_proc_name = + (* TODO (#5280260): investigate why proc_desc may not be found *) + match get_proc_desc overriden_proc_name with + | Some overriden_proc_desc -> + let overriden_signature = + Models.get_annotated_signature overriden_proc_desc overriden_proc_name in + check_return overriden_proc_name overriden_signature; + check_params overriden_proc_name overriden_signature + | None -> () in + + let check_overriden_methods super_class_name = + let super_proc_name = Procname.java_replace_class proc_name super_class_name in + let type_name = Sil.TN_csu (Sil.Class, Mangled.from_string super_class_name) in + match Sil.tenv_lookup tenv type_name with + | Some (Sil.Tstruct (_, _, _, _, _, methods, _)) -> + list_iter + (fun pname -> + if Procname.equal pname super_proc_name + then check pname) + methods + | _ -> () in + + let super_types = + let type_name = + let class_name = Procname.java_get_class proc_name in + Sil.TN_csu (Sil.Class, Mangled.from_string class_name) in + match Sil.tenv_lookup tenv type_name with + | Some curr_type -> + list_map Mangled.to_string (PatternMatch.type_get_direct_supertypes curr_type) + | None -> [] in + + list_iter check_overriden_methods super_types diff --git a/infer/src/checkers/idenv.ml b/infer/src/checkers/idenv.ml new file mode 100644 index 000000000..63a1151f2 --- /dev/null +++ b/infer/src/checkers/idenv.ml @@ -0,0 +1,58 @@ +(* +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +(** Environment for temporary identifiers used in instructions. +Lazy implementation: only created when actually used. *) + + +type t = (Sil.exp Ident.IdentHash.t) Lazy.t * Cfg.cfg + +let _create cfg proc_desc = + let map = Ident.IdentHash.create 1 in + let do_instr node = function + | Sil.Letderef (id, e, t, loc) -> + Ident.IdentHash.add map id e + | _ -> () in + Cfg.Procdesc.iter_instrs do_instr proc_desc; + map + +(* lazy implementation, only create when used *) +let create cfg proc_desc = + let map = lazy (_create cfg proc_desc) in + map, cfg + +(* create an idenv for another procedure *) +let create_from_idenv (_, cfg) proc_desc = + let map = lazy (_create cfg proc_desc) in + map, cfg + +let lookup (_map, _) id = + let map = Lazy.force _map in + try + Some (Ident.IdentHash.find map id) + with Not_found -> None + +let expand_expr idenv e = match e with + | Sil.Var id -> + (match lookup idenv id with + | Some e' -> e' + | None -> e) + | _ -> e + +let expand_expr_temps idenv node _exp = + let exp = expand_expr idenv _exp in + match exp with + | Sil.Lvar pvar when Errdesc.pvar_is_frontend_tmp pvar -> + (match Errdesc.find_program_variable_assignment node pvar with + | None -> exp + | Some (_, id) -> + expand_expr idenv (Sil.Var id)) + | _ -> exp + +(** Return true if the expression is a temporary variable introduced by the front-end. *) +let exp_is_temp idenv e = + match expand_expr idenv e with + | Sil.Lvar pvar -> + Errdesc.pvar_is_frontend_tmp pvar + | _ -> false diff --git a/infer/src/checkers/idenv.mli b/infer/src/checkers/idenv.mli new file mode 100644 index 000000000..77971473e --- /dev/null +++ b/infer/src/checkers/idenv.mli @@ -0,0 +1,19 @@ +(* +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +(** Environment for temporary identifiers used in instructions. +Lazy implementation: only created when actually used. *) + + +type t + +val create : Cfg.cfg -> Cfg.Procdesc.t -> t +val create_from_idenv : t -> Cfg.Procdesc.t -> t +val lookup : t -> Ident.t -> Sil.exp option +val expand_expr : t -> Sil.exp -> Sil.exp + +val exp_is_temp : t -> Sil.exp -> bool + +(** Stronger version of expand_expr which also expands a temporary variable. *) +val expand_expr_temps : t -> Cfg.Node.t -> Sil.exp -> Sil.exp diff --git a/infer/src/checkers/immutableChecker.ml b/infer/src/checkers/immutableChecker.ml new file mode 100644 index 000000000..113d7d6b4 --- /dev/null +++ b/infer/src/checkers/immutableChecker.ml @@ -0,0 +1,45 @@ +(* +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +module L = Logging +module F = Format +open Utils + +(** Check an implicit cast when returning an immutable collection from a method whose type is mutable. *) +let check_immutable_cast curr_pname curr_pdesc typ_expected typ_found_opt loc : unit = + match typ_found_opt with + | Some typ_found -> + begin + let casts = + [ + "java.util.List", "com.google.common.collect.ImmutableList"; + "java.util.Map", "com.google.common.collect.ImmutableMap"; + "java.util.Set", "com.google.common.collect.ImmutableSet" + ] in + let in_casts expected given = + list_exists (fun (x, y) -> Mangled.from_string x = expected && Mangled.from_string y = given) casts in + match PatternMatch.type_get_class_name typ_expected, + PatternMatch.type_get_class_name typ_found with + | Some name_expected, Some name_given -> + if in_casts name_expected name_given then + begin + let description = + Printf.sprintf + "Method %s returns %s but the return type is %s. Make sure that users of this method do not try to modify the collection." + (Procname.to_simplified_string curr_pname) + (Mangled.to_string name_given) + (Mangled.to_string name_expected) in + Checkers.ST.report_error + curr_pname + curr_pdesc + "CHECKERS_IMMUTABLE_CAST" + loc + description + end + | _ -> () + end + | None -> () + +let callback_check_immutable_cast get_proc_desc idenv proc_name = + Eradicate.callback_check_return_type check_immutable_cast get_proc_desc idenv proc_name diff --git a/infer/src/checkers/immutableChecker.mli b/infer/src/checkers/immutableChecker.mli new file mode 100644 index 000000000..b2361b072 --- /dev/null +++ b/infer/src/checkers/immutableChecker.mli @@ -0,0 +1,5 @@ +(* + * Copyright (c) 2013 - Facebook. All rights reserved. + *) + +val callback_check_immutable_cast : Callbacks.proc_callback_t diff --git a/infer/src/checkers/models.ml b/infer/src/checkers/models.ml new file mode 100644 index 000000000..fec6c42e9 --- /dev/null +++ b/infer/src/checkers/models.ml @@ -0,0 +1,371 @@ +(* +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +open Utils +module L = Logging + +(** Module for standard library models. *) + +(* in strict mode cannot insert null in containers *) +let strict_containers = false + +(* in strict mode, give an error if a nullable is passed to checkNotNull *) +let check_not_null_strict = false + +(* use library of inferred return annotations *) +let use_library = false + +(* use model annotations for library functions *) +let use_models = true + +(* libary functions: infer nullable annotation of return type *) +let infer_library_return = Config.from_env_variable "ERADICATE_LIBRARY" + + +(** Module for inference of parameter and return annotations. *) +module Inference = struct + let enabled = false + + let get_dir () = Filename.concat !Config.results_dir "eradicate" + + let field_get_dir_fname fn = + let fname = Ident.fieldname_to_string fn in + (get_dir (), fname) + + let field_is_marked fn = + let dir, fname = field_get_dir_fname fn in + DB.read_file_with_lock dir fname <> None + + let proc_get_ret_dir_fname pname = + let fname = Procname.to_filename pname ^ "_ret" in + (get_dir (), fname) + + let proc_get_param_dir_fname pname = + let fname = Procname.to_filename pname ^ "_params" in + (get_dir (), fname) + + let update_count_str s_old = + let n = + if s_old = "" then 0 + else try int_of_string s_old with + | Failure _ -> + L.stderr "int_of_string %s@." s_old; + assert false in + string_of_int (n + 1) + + let update_boolvec_str _s size index bval = + let s = if _s = "" then String.make size '0' else _s in + String.set s index (if bval then '1' else '0'); + s + + let mark_file update_str dir fname = + DB.update_file_with_lock dir fname update_str; + match DB.read_file_with_lock dir fname with + | Some buf -> L.stderr "Read %s: %s@." fname buf + | None -> L.stderr "Read %s: None@." fname + + let mark_file_count = mark_file update_count_str + + (** Mark the field @Nullable indirectly by writing to a global file. *) + let field_add_nullable_annotation fn = + let dir, fname = field_get_dir_fname fn in + mark_file_count dir fname + + (** Mark the return type @Nullable indirectly by writing to a global file. *) + let proc_add_return_nullable pn = + let dir, fname = proc_get_ret_dir_fname pn in + mark_file_count dir fname + + (** Return true if the return type is marked @Nullable in the global file *) + let proc_return_is_marked pname = + let dir, fname = proc_get_ret_dir_fname pname in + DB.read_file_with_lock dir fname <> None + + (** Mark the n-th parameter @Nullable indirectly by writing to a global file. *) + let proc_add_parameter_nullable pn n tot = + let dir, fname = proc_get_param_dir_fname pn in + let update_str s = update_boolvec_str s tot n true in + mark_file update_str dir fname + + (** Return None if the parameters are not marked, or a vector of marked parameters *) + let proc_parameters_marked pn = + let dir, fname = proc_get_param_dir_fname pn in + match DB.read_file_with_lock dir fname with + | None -> None + | Some buf -> + let boolvec = ref [] in + String.iter (fun c -> boolvec := (c = '1') :: !boolvec) buf; + Some (list_rev !boolvec) +end (* Inference *) + + +let o = false and n = true (* o is not annotated and n is annotated with @Nullable *) +let o1 = (o, [o]) (* not annotated with one argument *) +let o2 = (o, [o; o]) (* not annotated with two arguments *) +let n1 = (o, [n]) (* one argument nullable *) +let n2 = (o, [n; n]) (* two arguments nullable *) +let n3 = (o, [n; n; n]) (* three arguments nullable *) +let on = (o, [o; n]) (* the second argument is nullable *) +let ca = if strict_containers then (o, [o]) else (o, [n]) (* container add *) +let cg = if strict_containers then (n, [o]) else (n, [n]) (* container get *) +let cp = if strict_containers then (n, [o; o]) else (n, [n; n]) (* container put *) +let ng = (n, []) (* Nullable getter *) + +let check_not_null_parameter_list, check_not_null_list = + let x = if check_not_null_strict then o else n in + let list = + [ + 1, (o, [x; n]), "com.facebook.common.internal.Preconditions.checkNotNull(java.lang.Object,java.lang.Object):java.lang.Object"; + 1, (o, [x; n; n]), "com.facebook.common.internal.Preconditions.checkNotNull(java.lang.Object,java.lang.String,java.lang.Object[]):java.lang.Object"; + 1, (o, [x]), "com.facebook.common.internal.Preconditions.checkNotNull(java.lang.Object):java.lang.Object"; + 1, (o, [x; n]), "com.google.common.base.Preconditions.checkNotNull(java.lang.Object,java.lang.Object):java.lang.Object"; + 1, (o, [x; n; n]), "com.google.common.base.Preconditions.checkNotNull(java.lang.Object,java.lang.String,java.lang.Object[]):java.lang.Object"; + 1, (o, [x]), "com.google.common.base.Preconditions.checkNotNull(java.lang.Object):java.lang.Object"; + 1, (o, [x]), "org.junit.Assert.assertNotNull(java.lang.Object):void"; + 2, (o, [n; x]), "org.junit.Assert.assertNotNull(java.lang.String,java.lang.Object):void"; + 1, (o, [n]), "com.facebook.infer.annotation.Assertions.assertNotNull(java.lang.Object):java.lang.Object"; + 1, (o, [n; o]), "com.facebook.infer.annotation.Assertions.assertNotNull(java.lang.Object,java.lang.String):java.lang.Object"; + 1, (o, [n]), "com.facebook.infer.annotation.Assertions.assumeNotNull(java.lang.Object):java.lang.Object"; + 1, (o, [n; o]), "com.facebook.infer.annotation.Assertions.assumeNotNull(java.lang.Object,java.lang.String):java.lang.Object"; + ] in + list_map (fun (x, y, z) -> (x, z)) list, list_map (fun (x, y, z) -> (y, z)) list + +let check_state_list = + [ + (o, [n]), "Preconditions.checkState(boolean):void"; + (o, [n]), "com.facebook.common.internal.Preconditions.checkState(boolean):void"; + (o, [n; n]), "com.facebook.common.internal.Preconditions.checkState(boolean,java.lang.Object):void"; + (o, [n; n; n]), "com.facebook.common.internal.Preconditions.checkState(boolean,java.lang.String,java.lang.Object[]):void"; + (o, [n]), "com.google.common.base.Preconditions.checkState(boolean):void"; + (o, [n; n]), "com.google.common.base.Preconditions.checkState(boolean,java.lang.Object):void"; + (o, [n; n; n]), "com.google.common.base.Preconditions.checkState(boolean,java.lang.String,java.lang.Object[]):void"; + (o, [n]), "com.facebook.infer.annotation.Assertions.assertCondition(boolean):void"; + (o, [n; o]), "com.facebook.infer.annotation.Assertions.assertCondition(boolean,java.lang.String):void"; + (o, [n]), "com.facebook.infer.annotation.Assertions.assumeCondition(boolean):void"; + (o, [n; o]), "com.facebook.infer.annotation.Assertions.assumeCondition(boolean,java.lang.String):void"; + ] + +let check_argument_list = + [ + (o, [n]), "com.facebook.common.internal.Preconditions.checkArgument(boolean):void"; + (o, [n; n]), "com.facebook.common.internal.Preconditions.checkArgument(boolean,java.lang.Object):void"; + (o, [n; n; n]), "com.facebook.common.internal.Preconditions.checkArgument(boolean,java.lang.String,java.lang.Object[]):void"; + (o, [n]), "com.google.common.base.Preconditions.checkArgument(boolean):void"; + (o, [n; n]), "com.google.common.base.Preconditions.checkArgument(boolean,java.lang.Object):void"; + (o, [n; n; n]), "com.google.common.base.Preconditions.checkArgument(boolean,java.lang.String,java.lang.Object[]):void"; + ] + +let optional_get_list : ((_ * bool list) * _) list = + [ + (o, []), "Optional.get():java.lang.Object"; + (o, []), "com.google.common.base.Optional.get():java.lang.Object"; + ] + +let optional_isPresent_list : ((_ * bool list) * _) list = + [ + (o, []), "Optional.isPresent():boolean"; + (o, []), "com.google.common.base.Optional.isPresent():boolean"; + ] + +(** Models for Map.containsKey *) +let containsKey_list = + [ + n1, "com.google.common.collect.ImmutableMap.containsKey(java.lang.Object):boolean"; + n1, "java.util.Map.containsKey(java.lang.Object):boolean"; + ] + +(** Models for @Strict annotations *) +let annotated_list_strict = + [ + (n, [o]), "android.content.Context.getSystemService(java.lang.String):java.lang.Object"; + ] + +(** Models for @Nullable annotations *) +let annotated_list_nullable = + check_not_null_list @ check_state_list @ check_argument_list @ + annotated_list_strict @ + [ + n1, "android.os.Parcel.writeList(java.util.List):void"; + n2, "android.os.Parcel.writeParcelable(android.os.Parcelable,int):void"; + n1, "android.os.Parcel.writeString(java.lang.String):void"; + (o, [o; o; n; n; n]), "com.android.sdklib.build.ApkBuilder.(java.io.File,java.io.File,java.io.File,java.lang.String,java.io.PrintStream)"; + (o, [n]), "com.android.manifmerger.ManifestMerger.xmlFileAndLine(org.w3c.dom.Node):com.android.manifmerger.IMergerLog$FileAndLine"; + on, "com.android.util.CommandLineParser$Mode.process(com.android.util.CommandLineParser$Arg,java.lang.String):java.lang.Object"; + on, "com.google.common.base.Objects$ToStringHelper.add(java.lang.String,java.lang.Object):com.google.common.base.Objects$ToStringHelper"; + n2, "com.google.common.base.Objects.equal(java.lang.Object,java.lang.Object):boolean"; + n1, "com.google.common.base.Optional.fromNullable(java.lang.Object):com.google.common.base.Optional"; + (n, []), "com.google.common.base.Optional.orNull():java.lang.Object"; + n1, "com.google.common.base.Strings.nullToEmpty(java.lang.String):java.lang.String"; + cg, "com.google.common.collect.ImmutableMap.get(java.lang.Object):java.lang.Object"; (* container get *) + o1, "com.google.common.collect.ImmutableList$Builder.add(java.lang.Object):com.google.common.collect.ImmutableList$Builder"; + o1, "com.google.common.collect.ImmutableList$Builder.addAll(java.lang.Iterable):com.google.common.collect.ImmutableList$Builder"; + o1, "com.google.common.collect.ImmutableSortedSet$Builder.add(java.lang.Object):com.google.common.collect.ImmutableSortedSet$Builder"; + on, "com.google.common.collect.Iterables.getFirst(java.lang.Iterable,java.lang.Object):java.lang.Object"; + o1, "com.google.common.util.concurrent.SettableFuture.setException(java.lang.Throwable):boolean"; + o1, "java.io.File.(java.lang.String)"; + n1, "java.io.PrintStream.print(java.lang.String):void"; + o1, "java.lang.Class.isAssignableFrom(java.lang.Class):boolean"; + n1, "java.lang.Integer.equals(java.lang.Object):boolean"; + n2, "java.lang.RuntimeException.(java.lang.String,java.lang.Throwable)"; + n1, "java.lang.String.equals(java.lang.Object):boolean"; + n1, "java.lang.StringBuilder.append(java.lang.String):java.lang.StringBuilder"; + on, "java.net.URLClassLoader.newInstance(java.net.URL[],java.lang.ClassLoader):java.net.URLClassLoader"; + n1, "java.util.AbstractList.equals(java.lang.Object):boolean"; + ca, "java.util.ArrayList.add(java.lang.Object):boolean"; (* container add *) + ca, "java.util.List.add(java.lang.Object):boolean"; (* container add *) + cg, "java.util.Map.get(java.lang.Object):java.lang.Object"; (* container get *) + cp, "java.util.Map.put(java.lang.Object,java.lang.Object):java.lang.Object"; (* container put *) + n3, "javax.tools.JavaCompiler.getStandardFileManager(javax.tools.DiagnosticListener,java.util.Locale,java.nio.charset.Charset):javax.tools.StandardJavaFileManager"; + (n, [o; n; n]), "org.w3c.dom.Document.setUserData(java.lang.String,java.lang.Object,org.w3c.dom.UserDataHandler):java.lang.Object"; + (n, [o; n; n]), "org.w3c.dom.Node.setUserData(java.lang.String,java.lang.Object,org.w3c.dom.UserDataHandler):java.lang.Object"; + + (* References *) + ng, "java.lang.ref.Reference.get():java.lang.Object"; + ng, "java.lang.ref.PhantomReference.get():java.lang.Object"; + ng, "java.lang.ref.SoftReference.get():java.lang.Object"; + ng, "java.lang.ref.WeakReference.get():java.lang.Object"; + ng, "java.util.concurrent.atomic.AtomicReference.get():java.lang.Object"; + ] + +(** Models for @Present annotations *) +let annotated_list_present = + [ + (n, [o]), "Optional.of(java.lang.Object):Optional"; + (n, [o]), "com.google.common.base.Optional.of(java.lang.Object):com.google.common.base.Optional"; + ] + +let mk_table list = + let map = Hashtbl.create 1 in + list_iter (function (v, pn_id) -> Hashtbl.replace map pn_id v) list; + map + +let table_has_procedure table proc_name = + let proc_id = Procname.to_unique_id proc_name in + try ignore (Hashtbl.find table proc_id); true + with Not_found -> false + +let annotated_table_nullable = mk_table annotated_list_nullable +let annotated_table_present = mk_table annotated_list_present +let annotated_table_strict = mk_table annotated_list_strict +let check_not_null_table, check_not_null_parameter_table = + mk_table check_not_null_list, mk_table check_not_null_parameter_list +let check_state_table = mk_table check_state_list +let check_argument_table = mk_table check_argument_list +let optional_get_table = mk_table optional_get_list +let optional_isPresent_table = mk_table optional_isPresent_list +let containsKey_table = mk_table containsKey_list + +type table_t = (string, bool) Hashtbl.t + +(* precomputed marshalled table of inferred return annotations. *) +let ret_library_table : table_t Lazy.t = + lazy (Hashtbl.create 1) +(* +lazy (Marshal.from_string Eradicate_library.marshalled_library_table 0) +*) + +(** Return the annotated signature of the procedure, taking into account models. *) +let get_annotated_signature callee_pdesc callee_pname = + let annotated_signature = + Annotations.get_annotated_signature + Specs.proc_get_method_annotation callee_pdesc callee_pname in + let proc_id = Procname.to_unique_id callee_pname in + let infer_parameters ann_sig = + let mark_par = + if Inference.enabled then Inference.proc_parameters_marked callee_pname + else None in + match mark_par with + | None -> ann_sig + | Some bs -> + let mark = (false, bs) in + Annotations.annotated_signature_mark callee_pname Annotations.Nullable ann_sig mark in + let infer_return ann_sig = + let mark_r = + let from_library = + if use_library then + try + Hashtbl.find (Lazy.force ret_library_table) proc_id + with Not_found -> false + else false in + let from_inference = Inference.enabled && Inference.proc_return_is_marked callee_pname in + from_library || from_inference in + if mark_r + then Annotations.annotated_signature_mark_return callee_pname Annotations.Nullable ann_sig + else ann_sig in + let lookup_models_nullable ann_sig = + if use_models then + try + let mark = Hashtbl.find annotated_table_nullable proc_id in + Annotations.annotated_signature_mark callee_pname Annotations.Nullable ann_sig mark + with Not_found -> + ann_sig + else ann_sig in + let lookup_models_present ann_sig = + if use_models then + try + let mark = Hashtbl.find annotated_table_present proc_id in + Annotations.annotated_signature_mark callee_pname Annotations.Present ann_sig mark + with Not_found -> + ann_sig + else ann_sig in + let lookup_models_strict ann_sig = + if use_models + && Hashtbl.mem annotated_table_strict proc_id + then + Annotations.annotated_signature_mark_return_strict callee_pname ann_sig + else + ann_sig in + + annotated_signature + |> lookup_models_nullable + |> lookup_models_present + |> lookup_models_strict + |> infer_return + |> infer_parameters + +(** Return true when the procedure has been modelled for nullable. *) +let is_modelled_nullable proc_name = + if use_models then + let proc_id = Procname.to_unique_id proc_name in + try ignore (Hashtbl.find annotated_table_nullable proc_id ); true + with Not_found -> false + else false + +(** Return true when the procedure belongs to the library of inferred return annotations. *) +let is_ret_library proc_name = + if use_library && not infer_library_return then + let proc_id = Procname.to_unique_id proc_name in + try ignore (Hashtbl.find (Lazy.force ret_library_table) proc_id); true + with Not_found -> false + else false + +(** Check if the procedure is one of the known Preconditions.checkNotNull. *) +let is_check_not_null proc_name = + table_has_procedure check_not_null_table proc_name + +(** Parameter number for a procedure known to be a checkNotNull *) +let get_check_not_null_parameter proc_name = + let proc_id = Procname.to_unique_id proc_name in + try Hashtbl.find check_not_null_parameter_table proc_id + with Not_found -> 0 + +(** Check if the procedure is one of the known Preconditions.checkState. *) +let is_check_state proc_name = + table_has_procedure check_state_table proc_name + +(** Check if the procedure is one of the known Preconditions.checkArgument. *) +let is_check_argument proc_name = + table_has_procedure check_argument_table proc_name + +(** Check if the procedure is Optional.get(). *) +let is_optional_get proc_name = + table_has_procedure optional_get_table proc_name + +(** Check if the procedure is Optional.isPresent(). *) +let is_optional_isPresent proc_name = + table_has_procedure optional_isPresent_table proc_name + +(** Check if the procedure is Map.containsKey(). *) +let is_containsKey proc_name = + table_has_procedure containsKey_table proc_name diff --git a/infer/src/checkers/patternMatch.ml b/infer/src/checkers/patternMatch.ml new file mode 100644 index 000000000..2386f4620 --- /dev/null +++ b/infer/src/checkers/patternMatch.ml @@ -0,0 +1,317 @@ +(* +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +(** Module for Pattern matching. *) + +module L = Logging +module F = Format +open Utils + +let object_name = Mangled.from_string "java.lang.Object" + +let type_is_object = function + | Sil.Tptr (Sil.Tstruct (_, _, _, Some name, _, _, _), _) -> Mangled.equal name object_name + | _ -> false + +let java_proc_name_with_class_method pn class_with_path method_name = + (try + Procname.java_get_class pn = class_with_path && + Procname.java_get_method pn = method_name + with _ -> false) + +let is_direct_subtype_of this_type super_type_name = + match this_type with + | Sil.Tptr (Sil.Tstruct (_, _, _, _, supertypes, _, _), _) -> + list_exists (fun (x, y) -> super_type_name = Mangled.to_string y) supertypes + | _ -> false + +(** The type the method is invoked on *) +let get_this_type proc_desc = match Cfg.Procdesc.get_formals proc_desc with + | (n, t):: args -> Some t + | _ -> None + +let type_get_direct_supertypes = function + | Sil.Tptr (Sil.Tstruct (_, _, _, _, supertypes, _, _), _) + | Sil.Tstruct (_, _, _, _, supertypes, _, _) -> + list_map (fun (_, m) -> m) supertypes + | _ -> [] + +let type_get_class_name t = match t with + | Sil.Tptr (Sil.Tstruct (_, _, _, Some cn, _, _, _), _) -> + Some cn + | Sil.Tptr (Sil.Tvar (Sil.TN_csu (Sil.Class, cn)), _) -> + Some cn + | _ -> None + +let type_get_annotation + (t: Sil.typ): Sil.item_annotation option = + match t with + | Sil.Tptr (Sil.Tstruct (_, _, _, _, _, _, ia), _) + | Sil.Tstruct (_, _, _, _, _, _, ia) -> Some ia + | _ -> None + +let type_has_class_name t name = + type_get_class_name t = Some name + +let type_has_direct_supertype (t : Sil.typ) (s : Mangled.t) = + list_exists (fun c -> c = s) (type_get_direct_supertypes t) + +let type_find_supertype + (tenv: Sil.tenv) + (typ: Sil.typ) + (csu_option: Sil.csu option) + (filter: Mangled.t -> bool): bool = + let rec has_supertype typ visited = + if Sil.TypSet.mem typ visited then + false + else + begin + match Sil.expand_type tenv typ with + | Sil.Tptr (Sil.Tstruct (_, _, _, _, supertypes, _, _), _) + | Sil.Tstruct (_, _, _, _, supertypes, _, _) -> + let match_supertype (csu, m) = + let match_name () = filter m in + let match_csu () = match csu_option with + | Some c -> c = csu + | None -> true in + let has_indirect_supertype () = + match Sil.get_typ m csu_option tenv with + | Some supertype -> has_supertype supertype (Sil.TypSet.add typ visited) + | None -> false in + (match_csu () && match_name () (* only and always visit name with expected csu *)) + || has_indirect_supertype () in + list_exists match_supertype supertypes + | _ -> false + end in + has_supertype typ Sil.TypSet.empty + +let type_has_supertype + (tenv: Sil.tenv) + (typ: Sil.typ) + (csu_option: Sil.csu option) + (name: Mangled.t): bool = + let filter m = Mangled.equal m name in + type_find_supertype tenv typ csu_option filter + +let type_get_supertypes + (tenv: Sil.tenv) + (typ: Sil.typ) + (csu_option: Sil.csu option) : Mangled.t list = + let res = ref [] in + let filter m = + res := m :: !res; + false in + let _ = type_find_supertype tenv typ csu_option filter in + list_rev !res + +let type_is_nested_in_type t n = match t with + | Sil.Tptr (Sil.Tstruct (_, _, _, Some m, _, _, _), _) -> + string_is_prefix (Mangled.to_string n ^ "$") (Mangled.to_string m) + | _ -> false + +let type_is_nested_in_direct_supertype t n = + let is_nested_in m2 m1 = string_is_prefix (Mangled.to_string m2 ^ "$") (Mangled.to_string m1) in + list_exists (is_nested_in n) (type_get_direct_supertypes t) + +let type_is_nested_in_supertype tenv t csu_option n = + let is_nested_in m2 m1 = string_is_prefix (Mangled.to_string m2 ^ "$") (Mangled.to_string m1) in + list_exists (is_nested_in n) (type_get_supertypes tenv t csu_option) + +let rec get_type_name = function + | Sil.Tstruct (_, _, _, Some mangled, _, _, _) -> Mangled.to_string mangled + | Sil.Tptr (t, _) -> get_type_name t + | Sil.Tvar tn -> Sil.typename_name tn + | _ -> "_" + +let get_field_type_name + (typ: Sil.typ) + (fieldname: Ident.fieldname): string option = + match typ with + | Sil.Tstruct (fields, _, _, _, _, _, _) + | Sil.Tptr (Sil.Tstruct (fields, _, _, _, _, _, _), _) -> ( + try + let _, ft, _ = list_find + (function | (fn, _, _) -> Ident.fieldname_equal fn fieldname) + fields in + Some (get_type_name ft) + with Not_found -> None) + | _ -> None + +let java_get_const_type_name + (const: Sil.const): string = + match const with + | Sil.Cstr _ -> "java.lang.String" + | Sil.Cint _ -> "java.lang.Integer" + | Sil.Cfloat _ -> "java.lang.Double" + | _ -> "_" + +let get_vararg_type_names + (call_node: Cfg.Node.t) + (ivar: Sil.pvar): string list = + (* Is this the node creating ivar? *) + let rec initializes_array instrs = + match instrs with + | Sil.Call ([t1], Sil.Const (Sil.Cfun pn), _, _, _):: + Sil.Set (Sil.Lvar iv, _, Sil.Var t2, _):: is -> + (Sil.pvar_equal ivar iv && Ident.equal t1 t2 && + Procname.equal pn (Procname.from_string "__new_array")) + || initializes_array is + | i:: is -> initializes_array is + | _ -> false in + + (* Get the type name added to ivar or None *) + let added_type_name node = + let rec nvar_type_name nvar instrs = + match instrs with + | Sil.Letderef (nv, Sil.Lfield (_, id, t), _, _):: _ + when Ident.equal nv nvar -> get_field_type_name t id + | Sil.Letderef (nv, e, t, _):: _ + when Ident.equal nv nvar -> + Some (get_type_name t) + | i:: is -> nvar_type_name nvar is + | _ -> None in + let rec added_nvar array_nvar instrs = + match instrs with + | Sil.Set (Sil.Lindex (Sil.Var iv, _), _, Sil.Var nvar, _):: _ + when Ident.equal iv array_nvar -> nvar_type_name nvar (Cfg.Node.get_instrs node) + | Sil.Set (Sil.Lindex (Sil.Var iv, _), _, Sil.Const c, _):: _ + when Ident.equal iv array_nvar -> Some (java_get_const_type_name c) + | i:: is -> added_nvar array_nvar is + | _ -> None in + let rec array_nvar instrs = + match instrs with + | Sil.Letderef (nv, Sil.Lvar iv, _, _):: _ + when Sil.pvar_equal iv ivar -> + added_nvar nv instrs + | i:: is -> array_nvar is + | _ -> None in + array_nvar (Cfg.Node.get_instrs node) in + + (* Walk nodes backward until definition of ivar, adding type names *) + let rec type_names node = + if initializes_array (Cfg.Node.get_instrs node) then + [] + else + match (Cfg.Node.get_preds node) with + | [n] -> (match (added_type_name node) with + | Some name -> name:: type_names n + | None -> type_names n) + | _ -> raise Not_found in + + list_rev (type_names call_node) + +let has_type_name typ type_name = + get_type_name typ = type_name + +let has_formal_proc_argument_type_names proc_desc proc_name argument_type_names = + let formals = Cfg.Procdesc.get_formals proc_desc in + let equal_formal_arg (_, typ) arg_type_name = get_type_name typ = arg_type_name in + list_length formals = list_length argument_type_names + && list_for_all2 equal_formal_arg formals argument_type_names + +let has_formal_method_argument_type_names cfg proc_name argument_type_names = + has_formal_proc_argument_type_names + cfg proc_name ((Procname.java_get_class proc_name):: argument_type_names) + +let is_getter proc_name = + Str.string_match (Str.regexp "get*") (Procname.java_get_method proc_name) 0 + +let is_setter proc_name = + Str.string_match (Str.regexp "set*") (Procname.java_get_method proc_name) 0 + +(** Returns the signature of a field access (class name, field name, field type name) *) +let get_java_field_access_signature = function + | Sil.Letderef (id, Sil.Lfield (e, fn, ft), bt, loc) -> + Some (get_type_name bt, Ident.java_fieldname_get_field fn, get_type_name ft) + | _ -> None + +(** Returns the formal signature (class name, method name, +argument type names and return type name) *) +let get_java_method_call_formal_signature = function + | Sil.Call (ret_ids, Sil.Const (Sil.Cfun pn), (te, tt):: args, loc, cf) -> + (try + let arg_names = list_map (function | e, t -> get_type_name t) args in + let rt_name = Procname.java_get_return_type pn in + let m_name = Procname.java_get_method pn in + Some (get_type_name tt, m_name, arg_names, rt_name) + with _ -> None) + | _ -> None + + +let type_is_class = function + | Sil.Tptr (Sil.Tstruct _, _) -> true + | Sil.Tptr (Sil.Tvar _, _) -> true + | Sil.Tptr (Sil.Tarray _, _) -> true + | Sil.Tstruct _ -> true + | _ -> false + +let initializer_classes = list_map Mangled.from_string [ + "android.app.Activity"; + "android.app.Application"; + "android.app.Fragment"; + "android.support.v4.app.Fragment"; + ] + +let initializer_methods = [ + "onActivityCreated"; + "onAttach"; + "onCreate"; + "onCreateView"; + ] + +(** Check if the type has in its supertypes from the initializer_classes list. *) +let type_has_initializer + (tenv: Sil.tenv) + (t: Sil.typ): bool = + let check_candidate cname = type_has_supertype tenv t (Some Sil.Class) cname in + list_exists check_candidate initializer_classes + +(** Check if the method is one of the known initializer methods. *) +let method_is_initializer + (tenv: Sil.tenv) + (proc_name: Procname.t) + (proc_desc: Cfg.Procdesc.t) : bool = + match get_this_type proc_desc with + | Some this_type -> + if type_has_initializer tenv this_type then + let mname = Procname.java_get_method proc_name in + list_exists (string_equal mname) initializer_methods + else + false + | None -> false + +(** Get the vararg values by looking for array assignments to the pvar. *) +let java_get_vararg_values node pvar idenv pdesc = + let values = ref [] in + let do_instr = function + | Sil.Set (Sil.Lindex (array_exp, _), _, content_exp, _) + when Sil.exp_equal (Sil.Lvar pvar) (Idenv.expand_expr idenv array_exp) -> + (* Each vararg argument is an assigment to a pvar denoting an array of objects. *) + values := content_exp :: !values + | _ -> () in + let do_node n = + list_iter do_instr (Cfg.Node.get_instrs n) in + let () = match Errdesc.find_program_variable_assignment node pvar with + | Some (node', _) -> + Cfg.Procdesc.iter_slope_range do_node pdesc node' node + | None -> () in + !values + +let proc_calls get_proc_desc pname pdesc filter : (Procname.t * Cfg.Procdesc.t) list = + let res = ref [] in + let do_instruction node instr = match instr with + | Sil.Call (_, Sil.Const (Sil.Cfun callee_pn), _, _, _) -> + begin + match get_proc_desc callee_pn with + | Some callee_pd -> + if filter callee_pn callee_pd then res := (callee_pn, callee_pd) :: !res + | None -> () + end + | _ -> () in + let do_node node = + let instrs = Cfg.Node.get_instrs node in + list_iter (do_instruction node) instrs in + let nodes = Cfg.Procdesc.get_nodes pdesc in + list_iter do_node nodes; + list_rev !res diff --git a/infer/src/checkers/patternMatch.mli b/infer/src/checkers/patternMatch.mli new file mode 100644 index 000000000..398ddcdd7 --- /dev/null +++ b/infer/src/checkers/patternMatch.mli @@ -0,0 +1,77 @@ +(* +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +(** Module for Pattern matching. *) + +(** Returns the signature of a field access (class name, field name, field type name) *) +val get_java_field_access_signature : Sil.instr -> (string * string * string) option + +(** Returns the formal signature (class name, method name, +argument type names and return type name) *) +val get_java_method_call_formal_signature : +Sil.instr -> (string * string * string list * string) option + +(** Get the this type of a procedure *) +val get_this_type : Cfg.Procdesc.t -> Sil.typ option + +(** Get the name of a type *) +val get_type_name : Sil.typ -> string + +(** Get the type names of a variable argument *) +val get_vararg_type_names : Cfg.Node.t -> Sil.pvar -> string list + +val has_formal_method_argument_type_names : Cfg.Procdesc.t -> Procname.t -> string list -> bool + +(** Check if the method is one of the known initializer methods. *) +val method_is_initializer : Sil.tenv -> Procname.t -> Cfg.Procdesc.t -> bool + +(** Is this a getter proc name? *) +val is_getter : Procname.t -> bool + +(** Is this a setter proc name? *) +val is_setter : Procname.t -> bool + +(** Is the type a direct subtype of *) +val is_direct_subtype_of : Sil.typ -> string -> bool + +(** Get the name of the type of a constant *) +val java_get_const_type_name : Sil.const -> string + +(** Get the values of a vararg parameter given the pvar used to assign the elements. *) +val java_get_vararg_values : Cfg.Node.t -> Sil.pvar -> Idenv.t -> Cfg.Procdesc.t -> Sil.exp list + +val java_proc_name_with_class_method : Procname.t -> string -> string -> bool + +(** [proc_calls get_proc_desc pn pd filter] returns the callees that satisfy [filter]. *) +val proc_calls : (Procname.t -> Cfg.Procdesc.t option) -> Procname.t -> Cfg.Procdesc.t -> +(Procname.t -> Cfg.Procdesc.t -> bool) -> +(Procname.t * Cfg.Procdesc.t) list + +val type_get_annotation : Sil.typ -> Sil.item_annotation option + +(** Get the class name of the type *) +val type_get_class_name : Sil.typ -> Mangled.t option + +val type_get_direct_supertypes : Sil.typ -> Mangled.t list + +val type_get_supertypes : Sil.tenv -> Sil.typ -> Sil.csu option -> Mangled.t list + +(** Is the type a class with the given name *) +val type_has_class_name : Sil.typ -> Mangled.t -> bool + +val type_has_direct_supertype : Sil.typ -> Mangled.t -> bool + +val type_has_supertype : Sil.tenv -> Sil.typ -> Sil.csu option -> Mangled.t -> bool + +(** Is the type a class type *) +val type_is_class : Sil.typ -> bool + +val type_is_nested_in_direct_supertype : Sil.typ -> Mangled.t -> bool + +val type_is_nested_in_supertype : Sil.tenv -> Sil.typ -> Sil.csu option -> Mangled.t -> bool + +val type_is_nested_in_type : Sil.typ -> Mangled.t -> bool + +(** Is the type java.lang.Object *) +val type_is_object : Sil.typ -> bool diff --git a/infer/src/checkers/registerCheckers.ml b/infer/src/checkers/registerCheckers.ml new file mode 100644 index 000000000..6e0683bf1 --- /dev/null +++ b/infer/src/checkers/registerCheckers.ml @@ -0,0 +1,51 @@ +(* +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +(** Module for registering checkers. *) + +module L = Logging +module F = Format +open Utils + +(** Flags to activate checkers. *) +let active_procedure_checkers () = + let checkers_enabled = Config.checkers_enabled () in + + let java_checkers = + let l = + [ + CallbackChecker.callback_checker_main, false; + Checkers.callback_check_access, false; + Checkers.callback_monitor_nullcheck, false; + Checkers.callback_test_state , false; + Checkers.callback_checkVisibleForTesting, false; + Checkers.callback_check_write_to_parcel, false; + Checkers.callback_find_deserialization, false; + Dataflow.callback_test_dataflow, false; + SqlChecker.callback_sql, false; + Eradicate.callback_eradicate, !Config.eradicate; + CodeQuery.code_query_callback, !CodeQuery.query <> None; + Checkers.callback_check_field_access, false; + ImmutableChecker.callback_check_immutable_cast, checkers_enabled; + RepeatedCallsChecker.callback_check_repeated_calls, checkers_enabled; + ] in + list_map (fun (x, y) -> (x, y, Some Sil.Java)) l in + let c_cpp_checkers = + let l = + [ + Checkers.callback_print_c_method_calls, false; + CheckDeadCode.callback_check_dead_code, checkers_enabled; + ] in + list_map (fun (x, y) -> (x, y, Some Sil.C_CPP)) l in + + java_checkers @ c_cpp_checkers + +let active_cluster_checkers () = + [(Checkers.callback_check_cluster_access, false, Some Sil.Java)] + +let register () = + let register registry (callback, active, language_opt) = + if active then registry language_opt callback in + list_iter (register Callbacks.register_procedure_callback) (active_procedure_checkers ()); + list_iter (register Callbacks.register_cluster_callback) (active_cluster_checkers ()) diff --git a/infer/src/checkers/repeatedCallsChecker.ml b/infer/src/checkers/repeatedCallsChecker.ml new file mode 100644 index 000000000..a0008c7b6 --- /dev/null +++ b/infer/src/checkers/repeatedCallsChecker.ml @@ -0,0 +1,170 @@ +(* +* Copyright (c) 2014 - Facebook. All rights reserved. +*) + +module L = Logging +module F = Format +open Utils + +let checkers_repeated_calls_name = "CHECKERS_REPEATED_CALLS" + +(* activate the check for repeated calls *) +let checkers_repeated_calls = Config.from_env_variable checkers_repeated_calls_name + + +(** Extension for the repeated calls check. *) +module RepeatedCallsExtension : Eradicate.ExtensionT = +struct + module InstrSet = + Set.Make(struct + type t = Sil.instr + let compare i1 i2 = match i1, i2 with + | Sil.Call (ret1, e1, etl1, loc1, cf1), Sil.Call (ret2, e2, etl2, loc2, cf2) -> + (* ignore return ids and call flags *) + let n = Sil.exp_compare e1 e2 in + if n <> 0 then n else let n = list_compare Sil.exp_typ_compare etl1 etl2 in + if n <> 0 then n else Sil.call_flags_compare cf1 cf2 + | _ -> Sil.instr_compare i1 i2 + end) + + type extension = InstrSet.t + + let empty = InstrSet.empty + + let join calls1 calls2 = + InstrSet.inter calls1 calls2 + + let pp fmt calls = + let pp_call instr = F.fprintf fmt " %a@\n" (Sil.pp_instr pe_text) instr in + if not (InstrSet.is_empty calls) then + begin + F.fprintf fmt "Calls:@\n"; + InstrSet.iter pp_call calls; + end + + let get_old_call instr calls = + try + Some (InstrSet.find instr calls) + with Not_found -> None + + let add_call instr calls = + if InstrSet.mem instr calls then calls + else InstrSet.add instr calls + + type paths = + | AllPaths (** Check on all paths *) + | SomePath (** Check if some path exists *) + + (** Check if the procedure performs an allocation operation. + If [paths] is AllPaths, check if an allocation happens on all paths. + If [paths] is SomePath, check if a path with an allocation exists. *) + let proc_performs_allocation pdesc paths : Sil.location option = + + let node_allocates node : Sil.location option = + let found = ref None in + let proc_is_new pn = + Procname.equal pn SymExec.ModelBuiltins.__new || + Procname.equal pn SymExec.ModelBuiltins.__new_array in + let do_instr instr = + match instr with + | Sil.Call (_, Sil.Const (Sil.Cfun pn), _, loc, _) when proc_is_new pn -> + found := Some loc + | _ -> () in + list_iter do_instr (Cfg.Node.get_instrs node); + !found in + + let module DFAllocCheck = Dataflow.MakeDF(struct + type t = Sil.location option + let equal = opt_equal Sil.loc_equal + let _join _paths l1o l2o = (* join with left priority *) + match l1o, l2o with + | None, None -> + None + | Some loc, None + | None, Some loc -> + if _paths = AllPaths then None else Some loc + | Some loc1, Some loc2 -> + Some loc1 (* left priority *) + let join = _join paths + let do_node node lo1 = + let lo2 = node_allocates node in + let lo' = (* use left priority join to implement transfer function *) + _join SomePath lo1 lo2 in + [lo'], [lo'] + let proc_throws pn = Dataflow.DontKnow + end) in + + if Cfg.Procdesc.is_defined pdesc then + let transitions = DFAllocCheck.run pdesc None in + match transitions (Cfg.Procdesc.get_exit_node pdesc) with + | DFAllocCheck.Transition (loc, _, _) -> loc + | DFAllocCheck.Dead_state -> None + else None + + (** Check repeated calls to the same procedure. *) + let check_instr get_proc_desc curr_pname curr_pdesc node extension instr normalized_etl = + + (** Arguments are not temporary variables. *) + let arguments_not_temp args = + let filter_arg (e, t) = match e with + | Sil.Lvar pvar -> + (* same temporary variable does not imply same value *) + not (Errdesc.pvar_is_frontend_tmp pvar) + | _ -> true in + list_for_all filter_arg args in + + match instr with + | Sil.Call (ret_ids, Sil.Const (Sil.Cfun callee_pname), _, loc, call_flags) + when ret_ids <> [] && arguments_not_temp normalized_etl -> + let instr_normalized_args = Sil.Call ( + ret_ids, + Sil.Const (Sil.Cfun callee_pname), + normalized_etl, + loc, + call_flags) in + let report callee_pdesc = + match get_old_call instr_normalized_args extension with + | Some (Sil.Call (_, _, _, loc_old, _)) -> + begin + match proc_performs_allocation callee_pdesc AllPaths with + | Some alloc_loc -> + let description = + Printf.sprintf "call to %s seen before on line %d (may allocate at %s:%n)" + (Procname.to_simplified_string callee_pname) + loc_old.Sil.line + (DB.source_file_to_string alloc_loc.Sil.file) + alloc_loc.Sil.line in + Checkers.ST.report_error + curr_pname curr_pdesc checkers_repeated_calls_name loc description + | None -> () + end + | _ -> () in + + let () = match get_proc_desc callee_pname with + | None -> () + | Some callee_pdesc -> report callee_pdesc in + add_call instr_normalized_args extension + | _ -> extension + + let ext = + { + TypeState.empty = empty; + check_instr = check_instr; + join = join; + pp = pp; + } + + let mkpayload typestate = Specs.TypeState None +end (* CheckRepeatedCalls *) + +module MainRepeatedCalls = + Eradicate.Build(RepeatedCallsExtension) + +let callback_check_repeated_calls all_procs get_proc_desc idenv tenv proc_name proc_desc = + let checks = + { + TypeCheck.eradicate = false; + check_extension = checkers_repeated_calls; + check_ret_type = []; + } in + MainRepeatedCalls.callback checks all_procs get_proc_desc idenv tenv proc_name proc_desc diff --git a/infer/src/checkers/repeatedCallsChecker.mli b/infer/src/checkers/repeatedCallsChecker.mli new file mode 100644 index 000000000..bc1c8f469 --- /dev/null +++ b/infer/src/checkers/repeatedCallsChecker.mli @@ -0,0 +1,5 @@ +(* + * Copyright (c) 2014 - Facebook. All rights reserved. + *) + +val callback_check_repeated_calls : Callbacks.proc_callback_t diff --git a/infer/src/checkers/sqlChecker.ml b/infer/src/checkers/sqlChecker.ml new file mode 100644 index 000000000..9b85a3eeb --- /dev/null +++ b/infer/src/checkers/sqlChecker.ml @@ -0,0 +1,58 @@ +(* +* Copyright (c) 2014 - Facebook. All rights reserved. +*) + +open Utils +open ConstantPropagation + +module L = Logging + + +(** Find SQL statements in string concatenations *) +let callback_sql all_procs get_proc_desc idenv tenv proc_name proc_desc = + let verbose = false in + + (* Case insensitive SQL statement patterns *) + let sql_start = + let _sql_start = [ + "select.*from.*"; + "insert into.*"; + "update .* set.*"; + "delete .* from.*"; + ] in + list_map Str.regexp_case_fold _sql_start in + + (* Check for SQL string concatenations *) + let do_instr get_constants node = function + | Sil.Call (_, Sil.Const (Sil.Cfun pn), (Sil.Var i1, _):: (Sil.Var i2, _):: [], l, _) + when Procname.java_get_class pn = "java.lang.StringBuilder" + && Procname.java_get_method pn = "append" -> + let rvar1 = Ident.to_string i1 in + let rvar2 = Ident.to_string i2 in + begin + let matches s r = Str.string_match r s 0 in + let constants = get_constants node in + if ConstantMap.mem rvar1 constants + && ConstantMap.mem rvar2 constants then + begin + match ConstantMap.find rvar1 constants, ConstantMap.find rvar2 constants with + | Some (Sil.Cstr ""), Some (Sil.Cstr s2) -> + if list_exists (matches s2) sql_start then + begin + L.stdout + "%s%s@." + "Possible SQL query using string concatenation. " + "Please consider using a prepared statement instead."; + let linereader = Printer.LineReader.create () in + L.stdout "%a@." (Checkers.PP.pp_loc_range linereader 2 2) l + end + | _ -> () + end + end + | _ -> () in + + try + let get_constants = ConstantPropagation.run proc_desc in + if verbose then L.stdout "Analyzing %a...\n@." Procname.pp proc_name; + Cfg.Procdesc.iter_instrs (do_instr get_constants) proc_desc + with _ -> () diff --git a/infer/src/checkers/sqlChecker.mli b/infer/src/checkers/sqlChecker.mli new file mode 100644 index 000000000..0c304fb11 --- /dev/null +++ b/infer/src/checkers/sqlChecker.mli @@ -0,0 +1,5 @@ +(* +* Copyright (c) 2014 - Facebook. All rights reserved. +*) + +val callback_sql : Callbacks.proc_callback_t diff --git a/infer/src/checkers/sql_examples/Makefile b/infer/src/checkers/sql_examples/Makefile new file mode 100644 index 000000000..fccd9bdf0 --- /dev/null +++ b/infer/src/checkers/sql_examples/Makefile @@ -0,0 +1,11 @@ +all: + InferAnalyze -results_dir out -checkers + +.PHONY: capture +capture: + InferCapture out javac *.java + +.PHONY: clean +clean: + rm -rf out *.class + diff --git a/infer/src/checkers/sql_examples/Test.java b/infer/src/checkers/sql_examples/Test.java new file mode 100644 index 000000000..3857853f6 --- /dev/null +++ b/infer/src/checkers/sql_examples/Test.java @@ -0,0 +1,16 @@ +class Test { + + public void select() { + String a = "SELeCT id FROM "; + String b = "table"; + String c = " WHERE something"; + String d = a + b + c; + } + + public void insert() { + int id = 10; + String a = "INSERT into table VALUES (1) WHERE id="; + String q = a + Integer.toString(id); + } + +} diff --git a/infer/src/checkers/typeAnnotation.ml b/infer/src/checkers/typeAnnotation.ml new file mode 100644 index 000000000..5b0b29495 --- /dev/null +++ b/infer/src/checkers/typeAnnotation.ml @@ -0,0 +1,80 @@ +(* +* Copyright (c) 2014 - Facebook. All rights reserved. +*) + +module L = Logging +module F = Format +module P = Printf +open Utils + +(** Module to represent annotations on types. *) + +type t = { + nullable : bool; + present : bool; + origin : TypeOrigin.t; +} + +let equal ta1 ta2 = + bool_equal ta1.nullable ta2.nullable && + bool_equal ta1.present ta2.present && + TypeOrigin.equal ta1.origin ta2.origin + +let to_string ta = + let nullable_s = if ta.nullable then " @Nullable" else "" in + let present_s = if ta.present then " @Present" else "" in + nullable_s ^ present_s + +let join ta1 ta2 = + let present = ta1.present && ta2.present in + let ta' = match ta1.nullable, ta2.nullable with + | false, true -> + { ta2 with + present; + origin = TypeOrigin.join ta2.origin ta1.origin; + } + | true, false -> + { ta1 with + present; + origin = TypeOrigin.join ta1.origin ta2.origin; + } + | _ -> + { ta1 with + present; + origin = TypeOrigin.join ta1.origin ta2.origin; + } in + if ta' = ta1 then None else Some ta' + +let get_value annotation ta = match annotation with + | Annotations.Nullable -> ta.nullable + | Annotations.Present -> ta.present + +let set_value annotation ta b = match annotation with + | Annotations.Nullable -> { ta with nullable = b } + | Annotations.Present -> { ta with present = b } + +let get_origin ta = ta.origin + +let origin_is_fun_library ta = match get_origin ta with + | TypeOrigin.Proc (pname, _, _, is_library) -> + is_library + | _ -> false + +let descr_origin ta : TypeErr.origin_descr = + let descr_opt = TypeOrigin.get_description ta.origin in + match descr_opt with + | None -> ("", None, None) + | Some (str, loc_opt, sig_opt) -> ("(Origin: " ^ str ^ ")", loc_opt, sig_opt) + +let const annotation b origin = + let nullable, present = match annotation with + | Annotations.Nullable -> b, false + | Annotations.Present -> false, b in + { nullable; present; origin; } + +let with_origin ta o = + { ta with origin = o } + +let from_item_annotation ia origin = + let ann = const Annotations.Nullable (Annotations.ia_is_nullable ia) origin in + set_value Annotations.Present ann (Annotations.ia_is_present ia) diff --git a/infer/src/checkers/typeAnnotation.mli b/infer/src/checkers/typeAnnotation.mli new file mode 100644 index 000000000..42880270e --- /dev/null +++ b/infer/src/checkers/typeAnnotation.mli @@ -0,0 +1,21 @@ +(* +* Copyright (c) 2014 - Facebook. All rights reserved. +*) + +(** Nodule to represent annotations on types. *) + +type t + +val const : Annotations.annotation -> bool -> TypeOrigin.t -> t + +(** Human-readable description of the origin of a nullable value. *) +val descr_origin : t -> TypeErr.origin_descr + +val equal : t -> t -> bool +val from_item_annotation : Sil.item_annotation -> TypeOrigin.t -> t +val get_origin : t -> TypeOrigin.t +val get_value : Annotations.annotation -> t -> bool +val join : t -> t -> t option +val origin_is_fun_library : t -> bool +val to_string : t -> string +val with_origin : t -> TypeOrigin.t -> t diff --git a/infer/src/checkers/typeCheck.ml b/infer/src/checkers/typeCheck.ml new file mode 100644 index 000000000..05664a864 --- /dev/null +++ b/infer/src/checkers/typeCheck.ml @@ -0,0 +1,968 @@ +(* +* Copyright (c) 2014 - Facebook. All rights reserved. +*) + +open Utils +module L = Logging +module F = Format + +(** Module for type checking. *) + +let remove_temps = true (* remove temp ids from typestates *) + +(* print debug info when errors are found *) +let debug = Config.from_env_variable "ERADICATE_DEBUG" + + +(** Module to treat selected complex expressions as constants. *) +module ComplexExpressions = struct + (** What complex expressions are considered constant, each case includes the previous ones. + Boolean checks (e.g. null check) and assignments on expressions considered constant are + retained across the control flow assuming there are no modifications in between. *) + type expressions_constant = + | FL_NO (* none *) + | FL_PARAMETER_STATIC (* parameter.field and static fields *) + | FL_ALL_NESTED_FIELDS (* all forms of var.field1. ... .fieldn *) + | FUNCTIONS_IDEMPOTENT (* the above plus function calls are considered idempotent *) + + let complex_expressions_flag = FUNCTIONS_IDEMPOTENT + + let parameter_and_static_field () = + complex_expressions_flag >= FL_PARAMETER_STATIC + + let all_nested_fields () = + complex_expressions_flag >= FL_ALL_NESTED_FIELDS + + let functions_idempotent () = + complex_expressions_flag >= FUNCTIONS_IDEMPOTENT + + + let procname_optional_isPresent = Models.is_optional_isPresent + let procname_instanceof = Procname.equal SymExec.ModelBuiltins.__instanceof + let procname_containsKey = Models.is_containsKey + + (** Recognize *all* the procedures treated specially in conditionals *) + let procname_used_in_condition pn = + procname_optional_isPresent pn || + procname_instanceof pn || + procname_containsKey pn || + SymExec.function_is_builtin pn + + + exception Not_handled + + (* Convert an expression to a unique string. *) + (* This is used to turn complex expressions into pvar's.*) + (* Arbitrary function parameters and field access are allowed *) + (* when the relevant options are active. *) + let exp_to_string_map_dexp map_dexp node' exp = + + let rec dexp_to_string dexp = + let case_not_handled () = + raise Not_handled in + + match dexp with + | Sil.Darray (de1, de2) -> + dexp_to_string de1 ^ "[" ^ dexp_to_string de2 ^ "]" + | Sil.Darrow (de, f) + | Sil.Ddot (de, f) -> + dexp_to_string de ^ "." ^ Ident.fieldname_to_string f + | Sil.Dbinop (op, de1, de2) -> + "(" ^ dexp_to_string de1 ^ (Sil.str_binop pe_text op) ^ dexp_to_string de2 ^ ")" + | Sil.Dconst (Sil.Cfun pn) -> + Procname.to_unique_id pn + | Sil.Dconst c -> + pp_to_string (Sil.pp_const pe_text) c + | Sil.Dderef de -> + dexp_to_string de + | Sil.Dfcall (fun_dexp, args, loc, { Sil.cf_virtual = isvirtual }) + | Sil.Dretcall (fun_dexp, args, loc, { Sil.cf_virtual = isvirtual }) + when functions_idempotent () -> + let pp_arg fmt de = F.fprintf fmt "%s" (dexp_to_string de) in + let pp_args fmt des = (pp_comma_seq) pp_arg fmt des in + let pp fmt () = + let virt = if isvirtual then "V" else "" in + F.fprintf fmt "%a(%a)%s" pp_arg fun_dexp pp_args args virt in + pp_to_string pp () + | Sil.Dfcall _ + | Sil.Dretcall _ -> + case_not_handled () + | Sil.Dpvar pv + | Sil.Dpvaraddr pv when not (Errdesc.pvar_is_frontend_tmp pv) -> + Sil.pvar_to_string pv + | Sil.Dpvar pv + | Sil.Dpvaraddr pv (* front-end variable -- this should not happen) *) -> + case_not_handled () + | Sil.Dunop (op, de) -> + Sil.str_unop op ^ dexp_to_string de + + | Sil.Dsizeof (typ, sub) -> + case_not_handled () + | Sil.Dunknown -> + case_not_handled () in + + match map_dexp (Errdesc.exp_rv_dexp node' exp) with + | Some de -> + begin + try Some (dexp_to_string de) + with Not_handled -> None + end + | None -> None + + let exp_to_string node' exp = + let map_dexp de_opt = de_opt in + exp_to_string_map_dexp map_dexp node' exp + +end (* ComplexExpressions *) + +type check_return_type = + Procname.t -> Cfg.Procdesc.t -> Sil.typ -> Sil.typ option -> Sil.location -> unit + +type find_canonical_duplicate = Cfg.Node.t -> Cfg.Node.t + +type get_proc_desc = TypeState.get_proc_desc + +type checks = + { + eradicate : bool; + check_extension : bool; + check_ret_type : check_return_type list; + } + +(** Typecheck an expression. *) +let rec typecheck_expr + find_canonical_duplicate visited checks node instr_ref curr_pname + typestate e tr_default loc : TypeState.range = match e with + | Sil.Lvar pvar -> + (match TypeState.lookup_pvar pvar typestate with + | Some tr -> TypeState.range_add_locs tr [loc] + | None -> tr_default) + | Sil.Var id -> + (match TypeState.lookup_id id typestate with + | Some tr -> TypeState.range_add_locs tr [loc] + | None -> tr_default) + | Sil.Const (Sil.Cint i) when Sil.Int.iszero i -> + let (typ, _, locs) = tr_default in + if PatternMatch.type_is_class typ + then (typ, TypeAnnotation.const Annotations.Nullable true (TypeOrigin.Const loc), locs) + else + let t, ta, ll = tr_default in + (t, TypeAnnotation.with_origin ta (TypeOrigin.Const loc), ll) + | Sil.Const (Sil.Cexn e1) -> + typecheck_expr + find_canonical_duplicate visited checks + node instr_ref curr_pname + typestate e1 tr_default loc + | Sil.Const c -> + let (typ, _, locs) = tr_default in + (typ, TypeAnnotation.const Annotations.Nullable false (TypeOrigin.Const loc), locs) + | Sil.Lfield (exp, fn, typ) -> + let _, _, locs = tr_default in + let (_, ta, locs') = + typecheck_expr + find_canonical_duplicate visited checks node instr_ref curr_pname typestate exp + (typ, TypeAnnotation.const Annotations.Nullable false TypeOrigin.ONone, locs) loc in + let tr_new = match EradicateChecks.get_field_annotation fn typ with + | Some (t, ia) -> + ( + t, + TypeAnnotation.from_item_annotation ia (TypeOrigin.Field (fn, loc)), + locs' + ) + | None -> tr_default in + if checks.eradicate then + EradicateChecks.check_field_access + find_canonical_duplicate curr_pname node instr_ref exp fn ta loc; + tr_new + | Sil.Lindex (array_exp, index_exp) -> + let (_, ta, _) = + typecheck_expr + find_canonical_duplicate + visited + checks + node + instr_ref + curr_pname + typestate + array_exp + tr_default + loc in + let index = + match EradicateChecks.explain_expr node index_exp with + | Some s -> Format.sprintf "%s" s + | None -> "?" in + let fname = Ident.create_fieldname + (Mangled.from_string index) + 0 in + if checks.eradicate then + EradicateChecks.check_array_access + find_canonical_duplicate + curr_pname + node + instr_ref + array_exp + fname + ta + loc + true; + tr_default + | _ -> tr_default + +(** Typecheck an instruction. *) +let typecheck_instr ext calls_this checks (node: Cfg.Node.t) idenv get_proc_desc curr_pname + curr_pdesc find_canonical_duplicate annotated_signature instr_ref linereader typestate instr = + let print_current_state () = + L.stdout "Current Typestate in node %a@\n%a@." + Cfg.Node.pp (TypeErr.InstrRef.get_node instr_ref) + (TypeState.pp ext) typestate; + L.stdout " %a@." (Sil.pp_instr pe_text) instr in + + (** Handle the case where a field access X.f happens via a temporary variable $Txxx. + This has been observed in assignments this.f = exp when exp contains an ifthenelse. + Reconstuct the original expression knowing: the origin of $Txxx is 'this'. *) + let handle_field_access_via_temporary typestate exp loc = + let name_is_temporary name = + let prefix = "$T" in + Utils.string_is_prefix prefix name in + let pvar_get_origin pvar = + match TypeState.lookup_pvar pvar typestate with + | Some (_, ta, _) -> + Some (TypeAnnotation.get_origin ta) + | None -> None in + let handle_temporary e = match Idenv.expand_expr idenv e with + | Sil.Lvar pvar when name_is_temporary (Sil.pvar_to_string pvar) -> + begin + match pvar_get_origin pvar with + | Some (TypeOrigin.Formal s) -> + let pvar' = Sil.mk_pvar (Mangled.from_string s) curr_pname in + Some (Sil.Lvar pvar') + | _ -> None + end + | _ -> None in + match exp with + | Sil.Lfield (e, fn, typ) -> + let exp' = match handle_temporary e with + | Some e' -> + Sil.Lfield (e', fn, typ) + | None -> exp in + exp' + | _ -> exp in + + (** Convert a complex expressions into a pvar. + When [is_assigment] is true, update the relevant annotations for the pvar. *) + let convert_complex_exp_to_pvar node' is_assignment _exp typestate loc = + let exp = + handle_field_access_via_temporary + typestate + (Idenv.expand_expr idenv _exp) + loc in + let default = exp, typestate in + + (* If this is an assignment, update the typestate for a field access pvar. *) + let update_typestate_fld pvar fn typ = + match TypeState.lookup_pvar pvar typestate with + | Some _ when not is_assignment -> typestate + | _ -> + (match EradicateChecks.get_field_annotation fn typ with + | Some (t, ia) -> + let range = + ( + t, + TypeAnnotation.from_item_annotation ia (TypeOrigin.Field (fn, loc)), + [loc] + ) in + TypeState.add_pvar pvar range typestate + | None -> typestate) in + + (* Convert a function call to a pvar. *) + let handle_function_call call_node id = + match Errdesc.find_normal_variable_funcall call_node id with + | Some (Sil.Const (Sil.Cfun pn), _, _, _) + when not (ComplexExpressions.procname_used_in_condition pn) -> + begin + match ComplexExpressions.exp_to_string node' exp with + | None -> default + | Some exp_str -> + let pvar = Sil.mk_pvar (Mangled.from_string exp_str) curr_pname in + let already_in_typestate = TypeState.lookup_pvar pvar typestate <> None in + + if is_assignment && already_in_typestate + then default (* Don't overwrite pvar representing result of function call. *) + else Sil.Lvar pvar, typestate + end + | _ -> default in + + match exp with + | Sil.Var id when + ComplexExpressions.functions_idempotent () && + Errdesc.find_normal_variable_funcall node' id <> None -> + handle_function_call node' id + | Sil.Lvar pvar when + ComplexExpressions.functions_idempotent () && + Errdesc.pvar_is_frontend_tmp pvar -> + let frontend_variable_assignment = + Errdesc.find_program_variable_assignment node pvar in + begin + match frontend_variable_assignment with + | Some (call_node, id) -> + handle_function_call call_node id + + | _ -> default + end + + | Sil.Lvar pvar -> + default + | Sil.Lfield (_exp, fn, typ) when ComplexExpressions.parameter_and_static_field () -> + let exp' = Idenv.expand_expr_temps idenv node _exp in + + let is_parameter_field pvar = (* parameter.field *) + let name = Sil.pvar_to_string pvar in + let filter (s, ia, typ) = string_equal s name in + list_exists filter annotated_signature.Annotations.params in + + let is_static_field pvar = (* static field *) + Sil.pvar_is_global pvar in + + let pvar_to_str pvar = + if Sil.exp_is_this (Sil.Lvar pvar) then "" + else Sil.pvar_to_string pvar ^ "_" in + + let res = match exp' with + | Sil.Lvar pv when is_parameter_field pv || is_static_field pv -> + let fld_name = pvar_to_str pv ^ Ident.fieldname_to_string fn in + let pvar = Sil.mk_pvar (Mangled.from_string fld_name) curr_pname in + let typestate' = update_typestate_fld pvar fn typ in + (Sil.Lvar pvar, typestate') + | Sil.Lfield (_exp', fn', typ') when Ident.java_fieldname_is_outer_instance fn' -> + (** handle double dereference when accessing a field from an outer class *) + let fld_name = Ident.fieldname_to_string fn' ^ "_" ^ Ident.fieldname_to_string fn in + let pvar = Sil.mk_pvar (Mangled.from_string fld_name) curr_pname in + let typestate' = update_typestate_fld pvar fn typ in + (Sil.Lvar pvar, typestate') + | Sil.Lvar _ | Sil.Lfield _ when ComplexExpressions.all_nested_fields () -> + (** treat var.field1. ... .fieldn as a constant *) + begin + match ComplexExpressions.exp_to_string node' exp with + | Some exp_str -> + let pvar = Sil.mk_pvar (Mangled.from_string exp_str) curr_pname in + let typestate' = update_typestate_fld pvar fn typ in + (Sil.Lvar pvar, typestate') + | None -> + default + end + | _ -> + default in + res + | _ -> default in + + let constructor_check_calls_this calls_this pn = + if Procname.java_get_class curr_pname = Procname.java_get_class pn + then calls_this := true in + + (* Drops hidden and synthetic parameters which we do not check in a call. *) + let drop_unchecked_params calls_this pdesc pname params = + if Procname.is_constructor pname then + match PatternMatch.get_this_type pdesc with + | Some this_type -> + begin + constructor_check_calls_this calls_this pname; + + (* Drop reference parameters to this and outer objects. *) + let is_hidden_parameter (n, t) = + string_equal n "this" || + Str.string_match (Str.regexp "$bcvar[0-9]+") n 0 in + let rec drop_n_args ntl = match ntl with + | fp:: tail when is_hidden_parameter fp -> 1 + drop_n_args tail + | _ -> 0 in + let n = drop_n_args (Cfg.Procdesc.get_formals pdesc) in + let visible_params = list_drop_first n params in + + (* Drop the trailing hidden parameter if the constructor is synthetic. *) + if (Cfg.Procdesc.get_attributes pdesc).Sil.is_synthetic_method then + list_drop_last 1 visible_params + else + visible_params + end + | None -> params + else + params in + + (* Drop parameters from the signature which we do not check in a call. *) + let drop_unchecked_signature_params pdesc pname annotated_signature = + if Procname.is_constructor pname && + (Cfg.Procdesc.get_attributes pdesc).Sil.is_synthetic_method then + list_drop_last 1 annotated_signature.Annotations.params + else + annotated_signature.Annotations.params in + + let pvar_is_return pvar = + let pdesc = Cfg.Node.get_proc_desc node in + let ret_pvar = Cfg.Procdesc.get_ret_var pdesc in + Sil.pvar_equal pvar ret_pvar in + + (* Apply a function to a pvar and its associated content if front-end generated. *) + let pvar_apply loc handle_pvar typestate pvar = + let typestate' = handle_pvar typestate pvar in + let curr_node = TypeErr.InstrRef.get_node instr_ref in + let frontent_variable_assignment = + if Errdesc.pvar_is_frontend_tmp pvar + then Errdesc.find_program_variable_assignment curr_node pvar + else None in + match frontent_variable_assignment with + | None -> + typestate' + | Some (node', id) -> + (* handle the case where pvar is a frontend-generated program variable *) + let exp = Idenv.expand_expr idenv (Sil.Var id) in + begin + match convert_complex_exp_to_pvar node' false exp typestate' loc with + | Sil.Lvar pvar', _ -> handle_pvar typestate' pvar' + | _ -> typestate' + end in + + + (* typecheck_expr with fewer parameters, using a common template for typestate range *) + let typecheck_expr_simple typestate1 exp1 typ1 origin1 loc1 = + typecheck_expr + find_canonical_duplicate calls_this checks node instr_ref + curr_pname typestate1 exp1 + (typ1, TypeAnnotation.const Annotations.Nullable false origin1, [loc1]) + loc1 in + + (* check if there are errors in exp1 *) + let typecheck_expr_for_errors typestate1 exp1 loc1 : unit = + ignore (typecheck_expr_simple typestate1 exp1 Sil.Tvoid TypeOrigin.Undef loc1) in + + match instr with + | Sil.Remove_temps (idl, loc) -> + if remove_temps then list_fold_right TypeState.remove_id idl typestate + else typestate + | Sil.Declare_locals _ + | Sil.Abstract _ + | Sil.Nullify _ -> typestate + | Sil.Letderef (id, e, typ, loc) -> + typecheck_expr_for_errors typestate e loc; + let e', typestate' = convert_complex_exp_to_pvar node false e typestate loc in + TypeState.add_id id + (typecheck_expr_simple typestate' e' typ TypeOrigin.Undef loc) + typestate' + | Sil.Set (Sil.Lvar pvar, typ, Sil.Const (Sil.Cexn _), loc) when pvar_is_return pvar -> + (* skip assignment to return variable where it is an artifact of a throw instruction *) + typestate + | Sil.Set (e1, typ, e2, loc) -> + typecheck_expr_for_errors typestate e1 loc; + let e1', typestate1 = convert_complex_exp_to_pvar node true e1 typestate loc in + let check_field_assign () = match e1 with + | Sil.Lfield (_, fn, f_typ) -> + let t_ia_opt = EradicateChecks.get_field_annotation fn f_typ in + if checks.eradicate then + EradicateChecks.check_field_assignment + find_canonical_duplicate curr_pname node + instr_ref typestate1 e1' e2 typ loc fn t_ia_opt + (typecheck_expr find_canonical_duplicate calls_this checks) + print_current_state + | _ -> () in + let typestate2 = + match e1' with + | Sil.Lvar pvar -> + TypeState.add_pvar + pvar + (typecheck_expr_simple typestate1 e2 typ TypeOrigin.Undef loc) + typestate1 + | Sil.Lfield (_, fn, styp) -> + typestate1 + | _ -> + typestate1 in + check_field_assign (); + typestate2 + | Sil.Call ([id], Sil.Const (Sil.Cfun pn), [(_, typ)], loc, _) + when Procname.equal pn SymExec.ModelBuiltins.__new || + Procname.equal pn SymExec.ModelBuiltins.__new_array -> + TypeState.add_id + id + (typ, TypeAnnotation.const Annotations.Nullable false TypeOrigin.New, [loc]) + typestate (* new never returns null *) + | Sil.Call ([id], Sil.Const (Sil.Cfun pn), (e, typ):: _, loc, _) + when Procname.equal pn SymExec.ModelBuiltins.__cast -> + typecheck_expr_for_errors typestate e loc; + let e', typestate' = + convert_complex_exp_to_pvar node false e typestate loc in + (* cast copies the type of the first argument *) + TypeState.add_id id + (typecheck_expr_simple typestate' e' typ TypeOrigin.ONone loc) + typestate' + | Sil.Call ([id], Sil.Const (Sil.Cfun pn), [(array_exp, t)], loc, _) + when Procname.equal pn SymExec.ModelBuiltins.__get_array_size -> + let (_, ta, _) = typecheck_expr + find_canonical_duplicate + calls_this + checks + node + instr_ref + curr_pname + typestate + array_exp + (t, TypeAnnotation.const Annotations.Nullable false TypeOrigin.ONone, [loc]) + loc in + if checks.eradicate then + EradicateChecks.check_array_access + find_canonical_duplicate + curr_pname + node + instr_ref + array_exp + (Ident.create_fieldname (Mangled.from_string "length") 0) + ta + loc + false; + TypeState.add_id + id + ( + Sil.Tint (Sil.IInt), + TypeAnnotation.const Annotations.Nullable false TypeOrigin.New, + [loc] + ) + typestate + | Sil.Call (_, Sil.Const (Sil.Cfun pn), _, _, _) when SymExec.function_is_builtin pn -> + typestate (* skip othe builtins *) + | Sil.Call (ret_ids, Sil.Const (Sil.Cfun callee_pname), _etl, loc, cflags) + when get_proc_desc callee_pname <> None -> + let callee_pdesc = match get_proc_desc callee_pname with + | Some callee_pdesc -> callee_pdesc + | None -> assert false in + let callee_loc = Cfg.Procdesc.get_loc callee_pdesc in + + let etl = drop_unchecked_params calls_this callee_pdesc callee_pname _etl in + let call_params, typestate1 = + let handle_et (e1, t1) (etl1, typestate1) = + typecheck_expr_for_errors typestate e1 loc; + let e2, typestate2 = convert_complex_exp_to_pvar node false e1 typestate1 loc in + (((e1, e2), t1) :: etl1), typestate2 in + list_fold_right handle_et etl ([], typestate) in + + let annotated_signature = Models.get_annotated_signature callee_pdesc callee_pname in + let signature_params = drop_unchecked_signature_params + callee_pdesc + callee_pname + annotated_signature in + + let is_anonymous_inner_class_constructor = + Procname.java_is_anonymous_inner_class_constructor callee_pname in + + let do_return loc' typestate' = + match ret_ids with + | [] -> typestate' + | [id] -> + let (ia, ret_typ) = annotated_signature.Annotations.ret in + let is_library = Specs.proc_is_library callee_pname callee_pdesc in + let origin = TypeOrigin.Proc (callee_pname, loc', annotated_signature, is_library) in + TypeState.add_id + id + ( + ret_typ, + TypeAnnotation.from_item_annotation ia origin, + [loc'] + ) + typestate' + | _ :: _ :: _ -> assert false in + + (** Handle Preconditions.checkNotNull. *) + let do_preconditions_check_not_null parameter_num is_vararg typestate' = + (* clear the nullable flag of the first parameter of the procedure *) + let clear_nullable_flag typestate'' pvar = + (* remove the nullable flag for the given pvar *) + match TypeState.lookup_pvar pvar typestate'' with + | Some (t, ta, locs) -> + let should_report = + EradicateChecks.activate_condition_redundant && + TypeAnnotation.get_value Annotations.Nullable ta = false && + not (TypeAnnotation.origin_is_fun_library ta) in + if checks.eradicate && should_report then + begin + let cond = Sil.BinOp (Sil.Ne, Sil.Lvar pvar, Sil.exp_null) in + EradicateChecks.report_error + find_canonical_duplicate + node + (TypeErr.Condition_redundant + (true, EradicateChecks.explain_expr node cond, false)) + (Some instr_ref) + loc curr_pname + end; + TypeState.add_pvar + pvar + (t, TypeAnnotation.const Annotations.Nullable false TypeOrigin.ONone, [loc]) + typestate'' + | None -> + typestate' in + let rec find_parameter n eetl1 = match n, eetl1 with + | n, _ :: eetl2 when n > 1 -> find_parameter (n -1) eetl2 + | 1, ((_, Sil.Lvar pvar), typ):: _ -> Some (pvar, typ) + | _ -> None in + + match find_parameter parameter_num call_params with + | Some (pvar, typ) -> + if is_vararg + then + let do_vararg_value e ts = match Idenv.expand_expr idenv e with + | Sil.Lvar pvar1 -> + pvar_apply loc clear_nullable_flag ts pvar1 + | _ -> ts in + let vararg_values = PatternMatch.java_get_vararg_values node pvar idenv curr_pdesc in + Utils.list_fold_right do_vararg_value vararg_values typestate' + else + pvar_apply loc clear_nullable_flag typestate' pvar + | None -> typestate' in + + + (** Handle Preconditions.checkState for &&-separated conditions x!=null. *) + let do_preconditions_check_state typestate' = + let handle_pvar ann b typestate1 pvar = (* handle the annotation flag for pvar *) + match TypeState.lookup_pvar pvar typestate1 with + | Some (t, _, _) -> + TypeState.add_pvar + pvar + (t, TypeAnnotation.const ann b TypeOrigin.ONone, [loc]) + typestate1 + | None -> + typestate1 in + + let res_typestate = ref typestate' in + + let set_flag pvar ann b = (* set the annotation flag for pvar *) + res_typestate := pvar_apply loc (handle_pvar ann b) !res_typestate pvar in + + let handle_negated_condition cond_node = + let do_instr = function + | Sil.Prune (Sil.BinOp (Sil.Eq, _cond_e, Sil.Const (Sil.Cint i)), _, _, _) + | Sil.Prune (Sil.BinOp (Sil.Eq, Sil.Const (Sil.Cint i), _cond_e), _, _, _) + when Sil.Int.iszero i -> + let cond_e = Idenv.expand_expr_temps idenv cond_node _cond_e in + begin + match convert_complex_exp_to_pvar cond_node false cond_e typestate' loc with + | Sil.Lvar pvar', _ -> + set_flag pvar' Annotations.Nullable false + | _ -> () + end + | _ -> () in + list_iter do_instr (Cfg.Node.get_instrs cond_node) in + let handle_optional_isPresent node' e = + match convert_complex_exp_to_pvar node' false e typestate' loc with + | Sil.Lvar pvar', _ -> + set_flag pvar' Annotations.Present true + | _ -> () in + match call_params with + | ((_, Sil.Lvar pvar), typ):: _ -> + (* temporary variable for the value of the boolean condition *) + begin + let curr_node = TypeErr.InstrRef.get_node instr_ref in + let branch = false in + match Errdesc.find_boolean_assignment curr_node pvar branch with + (* In foo(cond1 && cond2), the node that sets the result to false + has all the negated conditions as parents. *) + | Some boolean_assignment_node -> + list_iter handle_negated_condition (Cfg.Node.get_preds boolean_assignment_node); + !res_typestate + | None -> + begin + match Errdesc.find_program_variable_assignment curr_node pvar with + | None -> + () + | Some (node', id) -> + let () = match Errdesc.find_normal_variable_funcall node' id with + | Some (Sil.Const (Sil.Cfun pn), [e], loc, call_flags) + when ComplexExpressions.procname_optional_isPresent pn -> + handle_optional_isPresent node' e + | _ -> () in + () + end; + !res_typestate + end + | _ -> typestate' in + + + let typestate2 = + if not is_anonymous_inner_class_constructor then + begin + if debug then + begin + let unique_id = Procname.to_unique_id callee_pname in + let classification = + EradicateChecks.classify_procedure callee_pname callee_pdesc in + L.stdout " %s unique id: %s@." classification unique_id + end; + if cflags.Sil.cf_virtual && checks.eradicate then + EradicateChecks.check_call_receiver + find_canonical_duplicate + curr_pname + node + typestate1 + call_params + callee_pname + callee_loc + instr_ref + loc + (typecheck_expr find_canonical_duplicate calls_this checks) + print_current_state; + if checks.eradicate then + EradicateChecks.check_call_parameters + find_canonical_duplicate + curr_pname + node + typestate1 + callee_pname + callee_pdesc + signature_params + call_params + loc + annotated_signature + instr_ref + (typecheck_expr find_canonical_duplicate calls_this checks) + print_current_state; + let typestate2 = + if checks.check_extension then + let etl' = list_map (fun ((_, e), t) -> (e, t)) call_params in + let extension = TypeState.get_extension typestate1 in + let extension' = + ext.TypeState.check_instr + get_proc_desc curr_pname curr_pdesc node + extension instr etl' in + TypeState.set_extension typestate1 extension' + else typestate1 in + if Models.is_check_not_null callee_pname then + do_preconditions_check_not_null + (Models.get_check_not_null_parameter callee_pname) + false (* is_vararg *) + typestate2 + else + if Procname.java_get_method callee_pname = "checkNotNull" + && Procname.java_is_vararg callee_pname + then + let last_parameter = list_length call_params in + do_preconditions_check_not_null + last_parameter + true (* is_vararg *) + typestate2 + else if Models.is_check_state callee_pname || + Models.is_check_argument callee_pname then + do_preconditions_check_state typestate2 + else typestate2 + end + else typestate1 in + do_return loc typestate2 + | Sil.Call _ -> + typestate + | Sil.Prune (cond, loc, true_branch, ik) -> + let rec check_condition node' c : _ TypeState.t = + (* check if the expression is coming from a call, and return the argument *) + let from_call filter_callee e : Sil.exp option = + match e with + | Sil.Var id -> + begin + match Errdesc.find_normal_variable_funcall node' id with + | Some (Sil.Const (Sil.Cfun pn), e1:: _, loc, call_flags) when + filter_callee pn -> + Some e1 + | _ -> None + end + | _ -> None in + + (* check if the expression is coming from instanceof *) + let from_instanceof e : Sil.exp option = + from_call ComplexExpressions.procname_instanceof e in + + (* check if the expression is coming from Optional.isPresent *) + let from_optional_isPresent e : Sil.exp option = + from_call ComplexExpressions.procname_optional_isPresent e in + + (* check if the expression is coming from Map.containsKey *) + let from_containsKey e : Sil.exp option = + from_call ComplexExpressions.procname_containsKey e in + + (* Turn x.containsKey(e) into the pvar for x.get(e) *) + (* which is then treated as a normal condition != null. *) + let handle_containsKey e = + let map_dexp = function + | Some (Sil.Dretcall (Sil.Dconst (Sil.Cfun pname), args, loc, call_flags)) -> + let pname' = + let object_t = (Some "java.lang", "Object") in + Procname.java_replace_return_type + (Procname.java_replace_method pname "get") + object_t in + let fun_dexp = Sil.Dconst (Sil.Cfun pname') in + Some (Sil.Dretcall (fun_dexp, args, loc, call_flags)) + | _ -> None in + begin + match ComplexExpressions.exp_to_string_map_dexp map_dexp node' e with + | Some e_str -> + let pvar = + Sil.mk_pvar (Mangled.from_string e_str) curr_pname in + let e1 = Sil.Lvar pvar in + let (typ, ta, _) = + typecheck_expr_simple typestate e1 Sil.Tvoid TypeOrigin.ONone loc in + let range = (typ, ta, [loc]) in + let typestate1 = TypeState.add_pvar pvar range typestate in + typestate1, e1, EradicateChecks.From_containsKey + | None -> + typestate, e, EradicateChecks.From_condition + end in + + + match c with + | Sil.BinOp (Sil.Eq, Sil.Const (Sil.Cint i), e) + | Sil.BinOp (Sil.Eq, e, Sil.Const (Sil.Cint i)) when Sil.Int.iszero i -> + typecheck_expr_for_errors typestate e loc; + let e', typestate' = convert_complex_exp_to_pvar node' false e typestate loc in + let (typ, ta, _) = + typecheck_expr_simple typestate' e' Sil.Tvoid TypeOrigin.ONone loc in + if checks.eradicate then + EradicateChecks.check_zero + find_canonical_duplicate get_proc_desc curr_pname + node' e' typ + ta true_branch EradicateChecks.From_condition + idenv linereader loc instr_ref; + typestate' + | Sil.BinOp (Sil.Ne, Sil.Const (Sil.Cint i), e) + | Sil.BinOp (Sil.Ne, e, Sil.Const (Sil.Cint i)) when Sil.Int.iszero i -> + let typestate1, e1, from_call = match from_instanceof e with + | Some e1 -> (* (e1 instanceof C) implies (e1 != null) *) + typestate, e1, EradicateChecks.From_instanceof + | None -> + begin + match from_optional_isPresent e with + | Some e1 -> + typestate, e1, EradicateChecks.From_optional_isPresent + | None -> + begin + match from_containsKey e with + | Some e1 when ComplexExpressions.functions_idempotent () -> + handle_containsKey e + | _ -> + typestate, e, EradicateChecks.From_condition + end + end in + typecheck_expr_for_errors typestate1 e1 loc; + let e', typestate2 = convert_complex_exp_to_pvar node' false e1 typestate1 loc in + let (typ, ta, _) = + typecheck_expr_simple typestate2 e' Sil.Tvoid TypeOrigin.ONone loc in + + let set_flag e' ann b = (* add constraint on e' for annotation ann *) + let handle_pvar typestate' pvar = + match TypeState.lookup_pvar pvar typestate' with + | Some (t, ta1, locs) -> + if TypeAnnotation.get_value ann ta1 <> b then + let origin = TypeAnnotation.get_origin ta1 in + let ta2 = TypeAnnotation.const ann b origin in + TypeState.add_pvar pvar (t, ta2, locs) typestate' + else typestate' + | None -> typestate' in + match e' with + | Sil.Lvar pvar -> + pvar_apply loc handle_pvar typestate2 pvar + | _ -> typestate2 in + + if checks.eradicate then + EradicateChecks.check_nonzero find_canonical_duplicate get_proc_desc curr_pname + node e' typ ta true_branch from_call idenv linereader loc instr_ref; + begin + match from_call with + | EradicateChecks.From_optional_isPresent -> + if TypeAnnotation.get_value Annotations.Present ta = false + then set_flag e' Annotations.Present true + else typestate2 + | EradicateChecks.From_condition + | EradicateChecks.From_instanceof + | EradicateChecks.From_containsKey -> + if TypeAnnotation.get_value Annotations.Nullable ta then + set_flag e' Annotations.Nullable false + else typestate2 + end + + | Sil.UnOp (Sil.LNot, (Sil.BinOp (Sil.Eq, e1, e2)), _) -> + check_condition node' (Sil.BinOp (Sil.Ne, e1, e2)) + | Sil.UnOp (Sil.LNot, (Sil.BinOp (Sil.Ne, e1, e2)), _) -> + check_condition node' (Sil.BinOp (Sil.Eq, e1, e2)) + | _ -> typestate in + + (** Handle assigment fron a temp pvar in a condition. + This recognizes the handling of temp variables in ((x = ...) != null) *) + let handle_assignment_in_condition pvar = + match Cfg.Node.get_preds node with + | [prev_node] -> + let found = ref None in + let do_instr i = match i with + | Sil.Set (e, _, e', _) + when Sil.exp_equal (Sil.Lvar pvar) (Idenv.expand_expr idenv e') -> + found := Some e + | _ -> () in + list_iter do_instr (Cfg.Node.get_instrs prev_node); + !found + | _ -> None in + + (** Normalize the condition by resolving temp variables. *) + let rec normalize_cond _node _cond = match _cond with + | Sil.UnOp (Sil.LNot, c, top) -> + let node', c' = normalize_cond _node c in + node', Sil.UnOp (Sil.LNot, c', top) + | Sil.BinOp (bop, c1, c2) -> + let node', c1' = normalize_cond _node c1 in + let node'', c2' = normalize_cond node' c2 in + node'', Sil.BinOp (bop, c1', c2') + | Sil.Var id -> + let c' = Idenv.expand_expr idenv _cond in + if not (Sil.exp_equal c' _cond) then normalize_cond _node c' + else _node, c' + | Sil.Lvar pvar when Errdesc.pvar_is_frontend_tmp pvar -> + (match handle_assignment_in_condition pvar with + | None -> + (match Errdesc.find_program_variable_assignment _node pvar with + | Some (node', id) -> node', Sil.Var id + | None -> _node, _cond) + | Some e2 -> _node, e2) + | c -> _node, c in + + let node', ncond = normalize_cond node cond in + check_condition node' ncond + | Sil.Stackop _ -> + typestate + | Sil.Goto_node _ -> + typestate + +(** Typecheck the instructions in a cfg node. *) +let typecheck_node + ext calls_this checks idenv get_proc_desc curr_pname curr_pdesc + find_canonical_duplicate annotated_signature typestate node linereader = + + let instrs = Cfg.Node.get_instrs node in + let instr_ref_gen = TypeErr.InstrRef.create_generator node in + + let typestates_exn = ref [] in + let handle_exceptions typestate instr = match instr with + | Sil.Call (_, Sil.Const (Sil.Cfun callee_pname), _, _, _) -> + let exceptions = + match get_proc_desc callee_pname with + | Some callee_pdesc -> + (Specs.proc_get_attributes callee_pname callee_pdesc).Sil.exceptions + | None -> [] in + if exceptions <> [] then + typestates_exn := typestate :: !typestates_exn; + | _ -> () in + + let canonical_node = find_canonical_duplicate node in + + let do_instruction ext typestate instr = + let instr_ref = (* keep unique instruction reference per-node *) + TypeErr.InstrRef.gen instr_ref_gen in + let instr' = + typecheck_instr ext calls_this checks node idenv get_proc_desc curr_pname curr_pdesc + find_canonical_duplicate annotated_signature instr_ref linereader typestate instr in + handle_exceptions typestate instr; + instr' in + + (* Reset 'always' field for forall errors to false. *) + (* This is used to track if it is set to true for all visit to the node. *) + TypeErr.node_reset_forall canonical_node; + + let typestate_succ = list_fold_left (do_instruction ext) typestate instrs in + if Cfg.Node.get_kind node = Cfg.Node.exn_sink_kind + then [], [] (* don't propagate exceptions to exit node *) + else [typestate_succ], !typestates_exn diff --git a/infer/src/checkers/typeCheck.mli b/infer/src/checkers/typeCheck.mli new file mode 100644 index 000000000..5858951ef --- /dev/null +++ b/infer/src/checkers/typeCheck.mli @@ -0,0 +1,27 @@ +(* +* Copyright (c) 2014 - Facebook. All rights reserved. +*) + + +(** Module type for the type checking functions. *) + +type check_return_type = + Procname.t -> Cfg.Procdesc.t -> Sil.typ -> Sil.typ option -> Sil.location -> unit + +type find_canonical_duplicate = Cfg.Node.t -> Cfg.Node.t + +type get_proc_desc = TypeState.get_proc_desc + +type checks = + { + eradicate : bool; + check_extension : bool; + check_ret_type : check_return_type list; + } + +val typecheck_node : +'a TypeState.ext -> +bool ref -> checks -> Idenv.t -> +get_proc_desc -> Procname.t -> Cfg.Procdesc.t -> +find_canonical_duplicate -> Annotations.annotated_signature -> 'a TypeState.t -> +Cfg.Node.t -> Printer.LineReader.t -> 'a TypeState.t list * 'a TypeState.t list diff --git a/infer/src/checkers/typeErr.ml b/infer/src/checkers/typeErr.ml new file mode 100644 index 000000000..28e408d11 --- /dev/null +++ b/infer/src/checkers/typeErr.ml @@ -0,0 +1,537 @@ +(* +* Copyright (c) 2014 - Facebook. All rights reserved. +*) + +module L = Logging +module P = Printf +open Utils + +(** Module for Type Error messages. *) + + +(** Describe the origin of values propagated by the checker. *) +module type InstrRefT = +sig + type t + type generator + val create_generator : Cfg.Node.t -> generator + val equal : t -> t -> bool + val gen : generator -> t + val get_node : t -> Cfg.Node.t + val hash : t -> int + val replace_node : t -> Cfg.Node.t -> t +end (* InstrRefT *) + + +(** Per-node instruction reference. *) +module InstrRef : InstrRefT = +struct + type t = Cfg.Node.t * int + type generator = Cfg.Node.t * int ref + let equal (n1, i1) (n2, i2) = + Cfg.Node.equal n1 n2 && i1 = i2 + let hash (n, i) = Hashtbl.hash (Cfg.Node.hash n, i) + let get_node (n, i) = n + let replace_node (n, i) n' = (n', i) + let create_generator n = (n, ref 0) + let gen instr_ref_gen = + let (node, ir) = instr_ref_gen in + incr ir; + (node, !ir) +end (* InstrRef *) + + +type origin_descr = + string * + Sil.location option * + Annotations.annotated_signature option (* callee signature *) + +type parameter_not_nullable = + Annotations.annotation * + string * (* description *) + int * (* parameter number *) + Procname.t * + Sil.location * (* callee location *) + origin_descr + +(** Instance of an error *) +type err_instance = + | Condition_redundant of (bool * (string option) * bool) + | Inconsistent_subclass_return_annotation of Procname.t * Procname.t + | Inconsistent_subclass_parameter_annotation of string * int * Procname.t * Procname.t + | Field_not_initialized of Ident.fieldname * Procname.t + | Field_not_mutable of Ident.fieldname * origin_descr + | Field_annotation_inconsistent of Annotations.annotation * Ident.fieldname * origin_descr + | Field_over_annotated of Ident.fieldname * Procname.t + | Null_field_access of string option * Ident.fieldname * origin_descr * bool + | Call_receiver_annotation_inconsistent + of Annotations.annotation * string option * Procname.t * origin_descr + | Parameter_annotation_inconsistent of parameter_not_nullable + | Return_annotation_inconsistent of Annotations.annotation * Procname.t * origin_descr + | Return_over_annotated of Procname.t + +module H = Hashtbl.Make(struct + type t = err_instance * InstrRef.t option + let err_instance_equal x y = match x, y with + | Condition_redundant (b1, so1, nn1), Condition_redundant (b2, so2, nn2) -> + bool_equal b1 b2 && + (opt_equal string_equal) so1 so2 && + bool_equal nn1 nn2 + | Condition_redundant _, _ + | _, Condition_redundant _ -> false + | Field_not_initialized (fn1, pn1), Field_not_initialized (fn2, pn2) -> + Ident.fieldname_equal fn1 fn2 && + Procname.equal pn1 pn2 + | Field_not_initialized (_, _), _ + | _, Field_not_initialized (_, _) -> false + | Field_not_mutable (fn1, od1), Field_not_mutable (fn2, od2) -> + Ident.fieldname_equal fn1 fn2 + | Field_not_mutable _, _ + | _, Field_not_mutable _ -> false + | Field_annotation_inconsistent (ann1, fn1, od1), Field_annotation_inconsistent (ann2, fn2, od2) -> + ann1 = ann2 && + Ident.fieldname_equal fn1 fn2 + | Field_annotation_inconsistent _, _ + | _, Field_annotation_inconsistent _ -> false + | Field_over_annotated (fn1, pn1), Field_over_annotated (fn2, pn2) -> + Ident.fieldname_equal fn1 fn2 && + Procname.equal pn1 pn2 + | Field_over_annotated (_, _), _ + | _, Field_over_annotated (_, _) -> false + | Null_field_access (so1, fn1, od1, ii1), Null_field_access (so2, fn2, od2, ii2) -> + (opt_equal string_equal) so1 so2 && + Ident.fieldname_equal fn1 fn2 && + bool_equal ii1 ii2 + | Null_field_access _, _ + | _, Null_field_access _ -> false + | Call_receiver_annotation_inconsistent (ann1, so1, pn1, od1), + Call_receiver_annotation_inconsistent (ann2, so2, pn2, od2) -> + ann1 = ann2 && + (opt_equal string_equal) so1 so2 && + Procname.equal pn1 pn2 + | Call_receiver_annotation_inconsistent _, _ + | _, Call_receiver_annotation_inconsistent _ -> false + | Parameter_annotation_inconsistent (ann1, s1, n1, pn1, cl1, od1), + Parameter_annotation_inconsistent (ann2, s2, n2, pn2, cl2, od2) -> + ann1 = ann2 && + string_equal s1 s2 && + int_equal n1 n2 && + Procname.equal pn1 pn2 && + Sil.loc_equal cl1 cl2 + | Parameter_annotation_inconsistent _, _ + | _, Parameter_annotation_inconsistent _ -> false + | Return_annotation_inconsistent (ann1, pn1, od1), + Return_annotation_inconsistent (ann2, pn2, od2) -> + ann1 = ann2 && Procname.equal pn1 pn2 + | Return_annotation_inconsistent _, _ + | _, Return_annotation_inconsistent _ -> false + | Return_over_annotated pn1, Return_over_annotated pn2 -> + Procname.equal pn1 pn2 + | Inconsistent_subclass_return_annotation (pn1, spn1), + Inconsistent_subclass_return_annotation (pn2, spn2) -> + if Procname.equal pn1 pn2 then true + else Procname.equal spn1 spn2 + | Inconsistent_subclass_parameter_annotation (param_name_1, pos_1, pn_1, overriden_pn_1), + Inconsistent_subclass_parameter_annotation (param_name_2, pos_2, pn_2, overriden_pn_2) -> + string_equal param_name_1 param_name_2 && + int_equal pos_1 pos_2 && + Procname.equal pn_1 pn_2 && + Procname.equal overriden_pn_1 overriden_pn_2 + | Inconsistent_subclass_return_annotation _, _ + | _, Inconsistent_subclass_return_annotation _ -> false + | Inconsistent_subclass_parameter_annotation _, _ + | _, Inconsistent_subclass_parameter_annotation _ -> false + + let equal (err_inst1, instr_ref_opt1) (err_inst2, instr_ref_opt2) = + err_instance_equal err_inst1 err_inst2 && + opt_equal InstrRef.equal instr_ref_opt1 instr_ref_opt2 + + let err_instance_hash x = + let string_hash s = Hashtbl.hash s in + let string_opt_hash so = Hashtbl.hash so in + match x with + | Condition_redundant (b, so, nn) -> + Hashtbl.hash (1, b, string_opt_hash so, nn) + | Field_not_initialized (fn, pn) -> + Hashtbl.hash (2, string_hash ((Ident.fieldname_to_string fn) ^ (Procname.to_string pn))) + | Field_not_mutable (fn, od) -> + Hashtbl.hash (3, string_hash (Ident.fieldname_to_string fn)) + | Field_annotation_inconsistent (ann, fn, od) -> + Hashtbl.hash (4, ann, string_hash (Ident.fieldname_to_string fn)) + | Field_over_annotated (fn, pn) -> + Hashtbl.hash (5, string_hash ((Ident.fieldname_to_string fn) ^ (Procname.to_string pn))) + | Null_field_access (so, fn, od, ii) -> + Hashtbl.hash (6, string_opt_hash so, string_hash (Ident.fieldname_to_string fn)) + | Call_receiver_annotation_inconsistent (ann, so, pn, od) -> + Hashtbl.hash (7, ann, string_opt_hash so, Procname.hash_pname pn) + | Parameter_annotation_inconsistent (ann, s, n, pn, cl, od) -> + Hashtbl.hash (8, ann, string_hash s, n, Procname.hash_pname pn) + | Return_annotation_inconsistent (ann, pn, od) -> + Hashtbl.hash (9, ann, Procname.hash_pname pn) + | Return_over_annotated pn -> + Hashtbl.hash (10, Procname.hash_pname pn) + | Inconsistent_subclass_return_annotation (pn, opn) -> + Hashtbl.hash (11, Procname.hash_pname pn, Procname.hash_pname opn) + | Inconsistent_subclass_parameter_annotation (param_name, pos, pn, opn) -> + let pn_hash = string_hash param_name in + Hashtbl.hash (12, pn_hash, pos, Procname.hash_pname pn, Procname.hash_pname opn) + + let hash (err_inst, instr_ref_opt) = + let x = match instr_ref_opt with + | None -> None + | Some instr_ref -> Some (InstrRef.hash instr_ref) in + let y = err_instance_hash err_inst in + Hashtbl.hash (x, y) + end (* H *)) + +type err_state = { + loc: Sil.location; (** location of the error *) + mutable always: bool; (** always fires on its associated node *) +} + +let err_tbl : err_state H.t = + H.create 1 + +(** Reset the error table. *) +let reset () = H.reset err_tbl + +(** Get the forall status of an err_instance. +The forall status indicates that the error should be printed only if it +occurs on every path. *) +let get_forall = function + | Condition_redundant _ -> true + | Field_not_initialized _ -> false + | Field_not_mutable _ -> false + | Field_annotation_inconsistent _ -> false + | Field_over_annotated _ -> false + | Inconsistent_subclass_return_annotation _ -> false + | Inconsistent_subclass_parameter_annotation _ -> false + | Null_field_access _ -> false + | Call_receiver_annotation_inconsistent _ -> false + | Parameter_annotation_inconsistent _ -> false + | Return_annotation_inconsistent _ -> false + | Return_over_annotated _ -> false + + +(** Reset the always field of the forall erros in the node, so if they are not set again +we know that they don't fire on every path. *) +let node_reset_forall node = + let iter (err_instance, instr_ref_opt) err_state = + match instr_ref_opt, get_forall err_instance with + | Some instr_ref, is_forall -> + let node' = InstrRef.get_node instr_ref in + if is_forall && Cfg.Node.equal node node' then err_state.always <- false + | None, _ -> () in + H.iter iter err_tbl + +(** Add an error to the error table and return whether it should be printed now. *) +let add_err find_canonical_duplicate err_instance instr_ref_opt loc = + let is_forall = get_forall err_instance in + if H.mem err_tbl (err_instance, instr_ref_opt) + then false (* don't print now *) + else begin + let instr_ref_opt_deduplicate = + match is_forall, instr_ref_opt with + | true, Some instr_ref -> (* use canonical duplicate for forall checks *) + let node = InstrRef.get_node instr_ref in + let canonical_node = find_canonical_duplicate node in + let instr_ref' = InstrRef.replace_node instr_ref canonical_node in + Some instr_ref' + | _ -> instr_ref_opt in + H.add err_tbl (err_instance, instr_ref_opt_deduplicate) { loc = loc; always = true }; + not is_forall (* print now if it's not a forall check *) + end + +module Strict = struct + let method_get_strict signature = + let (ia, _) = signature.Annotations.ret in + Annotations.ia_get_strict ia + + let this_type_get_strict signature = + match signature.Annotations.params with + | ("this", _, this_type):: _ -> begin + match PatternMatch.type_get_annotation this_type with + | Some ia -> Annotations.ia_get_strict ia + | None -> None + end + | _ -> None + + let signature_get_strict signature = + match method_get_strict signature with + | None -> this_type_get_strict signature + | Some x -> Some x + + let origin_descr_get_strict origin_descr = match origin_descr with + | _, _, Some signature -> + signature_get_strict signature + | _, _, None -> + None + + let report_on_method_arguments = false + + (* Return (Some parameters) if there is a method call on a @Nullable object,*) + (* where the origin of @Nullable in the analysis is the return value of a Strict method*) + (* with parameters. A method is Strict if it or its class are annotated @Strict. *) + let err_instance_get_strict err_instance : Sil.annotation option = + match err_instance with + | Call_receiver_annotation_inconsistent (Annotations.Nullable, _, _, origin_descr) + | Null_field_access (_, _, origin_descr, _) -> + origin_descr_get_strict origin_descr + | Parameter_annotation_inconsistent (Annotations.Nullable, _, _, _, _, origin_descr) + when report_on_method_arguments -> + origin_descr_get_strict origin_descr + | _ -> None +end (* Strict *) + +type st_report_error = + Procname.t -> + Cfg.Procdesc.t -> + string -> + Sil.location -> + ?advice: string option -> + ?field_name: Ident.fieldname option -> + ?origin_loc: Sil.location option -> + ?exception_kind: (string -> Localise.error_desc -> exn) -> + ?always_report: bool -> + string -> + unit + +(** Report an error right now. *) +let report_error_now + (st_report_error : st_report_error) + node err_instance instr_ref_opt loc proc_name : unit = + let demo_mode = true in + let do_print_base ew_string kind_s s = + L.stdout "%s %s in %s %s@." ew_string kind_s (Procname.java_get_method proc_name) s in + let do_print ew_string kind_s s = + L.stdout "%s:%d " (DB.source_file_to_string loc.Sil.file) loc.Sil.line; + do_print_base ew_string kind_s s in + let do_print_demo ew_string kind_s s = (* demo mode print for Eclipse and ocaml plugin *) + let process_path s = filename_to_relative (Sys.getcwd ()) s in + L.stdout + "File %s, line %d, characters 0-10:\n" + (process_path (DB.source_file_to_string loc.Sil.file)) + loc.Sil.line; + do_print_base ew_string kind_s s in + + let is_err, kind_s, description, advice, field_name, origin_loc = match err_instance with + | Condition_redundant (b, s_opt, nonnull) -> + let name = + if nonnull + then "ERADICATE_CONDITION_REDUNDANT_NONNULL" + else "ERADICATE_CONDITION_REDUNDANT" in + false, + name, + P.sprintf + "The condition %s is always %b according to the existing annotations." + (string_value_or_empty_string s_opt) + b, + Some "Consider adding a `@Nullable` annotation or removing the redundant check.", + None, + None + | Field_not_initialized (fn, pn) -> + let constructor_name = + if Procname.is_constructor pn then "the constructor" else Procname.java_get_method pn in + true, + "ERADICATE_FIELD_NOT_INITIALIZED", + P.sprintf + "Field `%s` is not initialized in %s and is not declared `@Nullable`" + (Ident.fieldname_to_simplified_string fn) + constructor_name, + None, + Some fn, + None + | Field_not_mutable (fn, (origin_description, origin_loc, _)) -> + true, + "ERADICATE_FIELD_NOT_MUTABLE", + P.sprintf + "Field `%s` is modified but is not declared `@Mutable`. %s" + (Ident.fieldname_to_simplified_string fn) + origin_description, + None, + None, + origin_loc + | Field_annotation_inconsistent (ann, fn, (origin_description, origin_loc, _)) -> + let kind_s, description = match ann with + | Annotations.Nullable -> + "ERADICATE_FIELD_NOT_NULLABLE", + P.sprintf + "Field `%s` can be null but is not declared `@Nullable`. %s" + (Ident.fieldname_to_simplified_string fn) + origin_description + | Annotations.Present -> + "ERADICATE_FIELD_VALUE_ABSENT", + P.sprintf + "Field `%s` is assigned a possibly absent value but is declared `@Present`. %s" + (Ident.fieldname_to_simplified_string fn) + origin_description in + true, + kind_s, + description, + None, + None, + origin_loc + | Field_over_annotated (fn, pn) -> + let constructor_name = + if Procname.is_constructor pn then "the constructor" else Procname.java_get_method pn in + true, + "ERADICATE_FIELD_OVER_ANNOTATED", + P.sprintf + "Field `%s` is always initialized in %s but is declared `@Nullable`" + (Ident.fieldname_to_simplified_string fn) + constructor_name, + None, + Some fn, + None + | Null_field_access (s_opt, fn, (origin_description, origin_loc, _), indexed) -> + let at_index = if indexed then "element at index" else "field" in + true, + "ERADICATE_NULL_FIELD_ACCESS", + P.sprintf + "Object `%s` could be null when accessing %s `%s`. %s" + (string_value_or_empty_string s_opt) + at_index + (Ident.fieldname_to_simplified_string fn) + origin_description, + None, + None, + origin_loc + | Call_receiver_annotation_inconsistent (ann, s_opt, pn, (origin_description, origin_loc, _)) -> + let kind_s, description = match ann with + | Annotations.Nullable -> + "ERADICATE_NULL_METHOD_CALL", + P.sprintf + "The value of `%s` in the call to `%s` could be null. %s" + (string_value_or_empty_string s_opt) + (Procname.to_simplified_string pn) + origin_description + | Annotations.Present -> + "ERADICATE_VALUE_NOT_PRESENT", + P.sprintf + "The value of `%s` in the call to `%s` is not @Present. %s" + (string_value_or_empty_string s_opt) + (Procname.to_simplified_string pn) + origin_description in + true, + kind_s, + description, + None, + None, + origin_loc + | Parameter_annotation_inconsistent (ann, s, n, pn, callee_loc, (origin_desc, origin_loc, _)) -> + let kind_s, description = match ann with + | Annotations.Nullable -> + "ERADICATE_PARAMETER_NOT_NULLABLE", + P.sprintf + "`%s` needs a non-null value in parameter %d but argument `%s` can be null. %s" + (Procname.to_simplified_string pn) + n + s + origin_desc + | Annotations.Present -> "ERADICATE_PARAMETER_VALUE_ABSENT", + P.sprintf + "`%s` needs a present value in parameter %d but argument `%s` can be absent. %s" + (Procname.to_simplified_string pn) + n + s + origin_desc in + true, + kind_s, + description, + None, + None, + origin_loc + | Return_annotation_inconsistent (ann, pn, (origin_description, origin_loc, _)) -> + let kind_s, description = match ann with + | Annotations.Nullable -> + "ERADICATE_RETURN_NOT_NULLABLE", + P.sprintf + "Method `%s` may return null but it is not annotated with `@Nullable`. %s" + (Procname.to_simplified_string pn) + origin_description + | Annotations.Present -> + "ERADICATE_RETURN_VALUE_NOT_PRESENT", + P.sprintf + "Method `%s` may return an absent value but it is annotated with `@Present`. %s" + (Procname.to_simplified_string pn) + origin_description in + true, + kind_s, + description, + None, + None, + origin_loc + | Return_over_annotated pn -> + false, + "ERADICATE_RETURN_OVER_ANNOTATED", + P.sprintf + "Method `%s` is annotated with `@Nullable` but never returns null." + (Procname.to_simplified_string pn), + None, + None, + None + | Inconsistent_subclass_return_annotation (pn, opn) -> + false, + "ERADICATE_INCONSISTENT_SUBCLASS_RETURN_ANNOTATION", + P.sprintf + "Method `%s` is annotated with `@Nullable` but overrides unannotated method `%s`." + (Procname.to_simplified_string ~withclass: true pn) + (Procname.to_simplified_string ~withclass: true opn), + None, + None, + None + | Inconsistent_subclass_parameter_annotation (param_name, pos, pn, opn) -> + let translate_position = function + | 1 -> "First" + | 2 -> "Second" + | 3 -> "Third" + | n -> (string_of_int n)^"th" in + false, + "ERADICATE_INCONSISTENT_SUBCLASS_PARAMETER_ANNOTATION", + P.sprintf "%s parameter `%s` of method `%s` is not `@Nullable` but is declared `@Nullable` in the parent class method `%s`." + (translate_position pos) param_name + (Procname.to_simplified_string ~withclass: true pn) + (Procname.to_simplified_string ~withclass: true opn), + None, + None, + None + in + let ew_string = if is_err then "Error" else "Warning" in + (if demo_mode then do_print_demo else do_print) ew_string kind_s description; + let always_report = Strict.err_instance_get_strict err_instance <> None in + st_report_error + proc_name + (Cfg.Node.get_proc_desc node) + kind_s + loc + ~advice: advice + ~field_name: field_name + ~origin_loc: origin_loc + ~exception_kind: (fun k d -> Exceptions.Eradicate (k, d)) + ~always_report: always_report + description + + +(** Report an error unless is has been reported already, or unless it's a forall error +since it requires waiting until the end of the analysis and be printed by flush. *) +let report_error st_report_error find_canonical_duplicate node + err_instance instr_ref_opt loc proc_name = + let should_report_now = + add_err find_canonical_duplicate err_instance instr_ref_opt loc in + if should_report_now then + report_error_now + st_report_error node err_instance instr_ref_opt loc proc_name + +(** Report the forall checks at the end of the analysis and reset the error table *) +let report_forall_checks_and_reset st_report_error proc_name = + let iter (err_instance, instr_ref_opt) err_state = + match instr_ref_opt, get_forall err_instance with + | Some instr_ref, is_forall -> + let node = InstrRef.get_node instr_ref in + State.set_node node; + if is_forall && err_state.always + then report_error_now + st_report_error node err_instance instr_ref_opt err_state.loc proc_name + | None, _ -> () in + H.iter iter err_tbl; + reset () diff --git a/infer/src/checkers/typeErr.mli b/infer/src/checkers/typeErr.mli new file mode 100644 index 000000000..7c1d8ed12 --- /dev/null +++ b/infer/src/checkers/typeErr.mli @@ -0,0 +1,82 @@ +(* +* Copyright (c) 2014 - Facebook. All rights reserved. +*) + + +(** Module for Type Error messages. *) + + +module type InstrRefT = +sig + type t + type generator + val create_generator : Cfg.Node.t -> generator + val equal : t -> t -> bool + val gen : generator -> t + val get_node : t -> Cfg.Node.t + val hash : t -> int + val replace_node : t -> Cfg.Node.t -> t +end (* InstrRefT *) + +module InstrRef : InstrRefT + +module Strict : +sig + val signature_get_strict : Annotations.annotated_signature -> Sil.annotation option +end (* Strict *) + + +type origin_descr = + string * + Sil.location option * + Annotations.annotated_signature option (* callee signature *) + +type parameter_not_nullable = + Annotations.annotation * + string * (* description *) + int * (* parameter number *) + Procname.t * + Sil.location * (* callee location *) + origin_descr + +(** Instance of an error *) +type err_instance = + | Condition_redundant of (bool * (string option) * bool) + | Inconsistent_subclass_return_annotation of Procname.t * Procname.t + | Inconsistent_subclass_parameter_annotation of string * int * Procname.t * Procname.t + | Field_not_initialized of Ident.fieldname * Procname.t + | Field_not_mutable of Ident.fieldname * origin_descr + | Field_annotation_inconsistent of Annotations.annotation * Ident.fieldname * origin_descr + | Field_over_annotated of Ident.fieldname * Procname.t + | Null_field_access of string option * Ident.fieldname * origin_descr * bool + | Call_receiver_annotation_inconsistent + of Annotations.annotation * string option * Procname.t * origin_descr + | Parameter_annotation_inconsistent of parameter_not_nullable + | Return_annotation_inconsistent of Annotations.annotation * Procname.t * origin_descr + | Return_over_annotated of Procname.t + + +val node_reset_forall : Cfg.Node.t -> unit + +type st_report_error = + Procname.t -> + Cfg.Procdesc.t -> + string -> + Sil.location -> + ?advice: string option -> + ?field_name: Ident.fieldname option -> + ?origin_loc: Sil.location option -> + ?exception_kind: (string -> Localise.error_desc -> exn) -> + ?always_report: bool -> + string -> + unit + +val report_error : +st_report_error -> +(Cfg.Node.t -> Cfg.Node.t) -> Cfg.Node.t -> +err_instance -> InstrRef.t option -> Sil.location -> +Procname.t -> unit + +val report_forall_checks_and_reset : st_report_error -> Procname.t -> unit + +val reset : unit -> unit diff --git a/infer/src/checkers/typeOrigin.ml b/infer/src/checkers/typeOrigin.ml new file mode 100644 index 000000000..7cdb6b983 --- /dev/null +++ b/infer/src/checkers/typeOrigin.ml @@ -0,0 +1,98 @@ +(* +* Copyright (c) 2014 - Facebook. All rights reserved. +*) + +module L = Logging +module P = Printf +open Utils + + +(** Describe the origin of values propagated by the checker. *) + + +type proc_origin = + Procname.t * Sil.location * Annotations.annotated_signature * bool (* is_library *) + +type t = + | Const of Sil.location + | Field of Ident.fieldname * Sil.location + | Formal of string + | Proc of proc_origin + | New + | ONone + | Undef + +let equal o1 o2 = match o1, o2 with + | Const loc1, Const loc2 -> + Sil.loc_equal loc1 loc2 + | Const _, _ + | _, Const _ -> false + | Field (fn1, loc1), Field (fn2, loc2) -> + Ident.fieldname_equal fn1 fn2 && + Sil.loc_equal loc1 loc2 + | Field _, _ + | _, Field _ -> false + | Formal s1, Formal s2 -> + string_equal s1 s2 + | Formal _, _ + | _, Formal _ -> false + | Proc (pn1, loc1, as1, b1), Proc (pn2, loc2, as2, b2) -> + Procname.equal pn1 pn2 && + Sil.loc_equal loc1 loc2 && + Annotations.equal as1 as2 && + bool_equal b1 b2 + | Proc _, _ + | _, Proc _ -> false + | New, New -> true + | New, _ + | _, New -> false + | ONone, ONone -> true + | ONone, _ + | _, ONone -> false + | Undef, Undef -> true + +let to_string = function + | Const loc -> "Const" + | Field (fn, loc) -> "Field " ^ Ident.fieldname_to_simplified_string fn + | Formal s -> "Formal " ^ s + | Proc (pname, _, _, _) -> + Printf.sprintf + "Fun %s" + (Procname.to_simplified_string pname) + | New -> "New" + | ONone -> "ONone" + | Undef -> "Undef" + +let get_description origin = + let atline loc = + " at line " ^ (string_of_int loc.Sil.line) in + match origin with + | Const loc -> + Some ("null constant" ^ atline loc, Some loc, None) + | Field (fn, loc) -> + Some ("field " ^ Ident.fieldname_to_simplified_string fn ^ atline loc, Some loc, None) + | Formal s -> + Some ("method parameter " ^ s, None, None) + | Proc (pname, loc, signature, is_library) -> + let strict = match TypeErr.Strict.signature_get_strict signature with + | Some ann -> + let str = "@Strict" in + (match ann.Sil.parameters with + | par1 :: _ -> Printf.sprintf "%s(%s) " str par1 + | [] -> Printf.sprintf "%s " str) + | None -> "" in + let description = Printf.sprintf + "call to %s%s %s" + strict + (Procname.to_simplified_string pname) + (atline loc) in + Some (description, Some loc, Some signature) + | New + | ONone + | Undef -> None + + +let join o1 o2 = match o1, o2 with (* left priority *) + | Undef, _ + | _, Undef -> Undef + | _ -> o1 diff --git a/infer/src/checkers/typeOrigin.mli b/infer/src/checkers/typeOrigin.mli new file mode 100644 index 000000000..4bae281bb --- /dev/null +++ b/infer/src/checkers/typeOrigin.mli @@ -0,0 +1,27 @@ +(* +* Copyright (c) 2014 - Facebook. All rights reserved. +*) + + +type proc_origin = (** Case Proc *) + Procname.t * Sil.location * Annotations.annotated_signature * bool (* is_library *) + +type t = + | Const of Sil.location (** A constant in the source *) + | Field of Ident.fieldname * Sil.location (** A field access *) + | Formal of string (** A formal parameter *) + | Proc of proc_origin (** A procedure call *) + | New (** A new object creation *) + | ONone (** No origin is known *) + | Undef (** Undefined value before initialization *) + +val equal : t -> t -> bool + +(** Get a description to be used for error messages. *) +val get_description : t -> TypeErr.origin_descr option + +(** Join with left priority *) +val join : t -> t -> t + +(** Raw string representation. *) +val to_string : t -> string diff --git a/infer/src/checkers/typeState.ml b/infer/src/checkers/typeState.ml new file mode 100644 index 000000000..6b1870d59 --- /dev/null +++ b/infer/src/checkers/typeState.ml @@ -0,0 +1,154 @@ +(* +* Copyright (c) 2014 - Facebook. All rights reserved. +*) + +module L = Logging +module F = Format +module P = Printf +open Utils + +(** Module for typestates: maps from expressions to annotated types, with extensions. *) + +(** Parameters of a call. *) +type parameters = (Sil.exp * Sil.typ) list + +type get_proc_desc = Procname.t -> Cfg.Procdesc.t option + +(** Extension to a typestate with values of type 'a. *) +type 'a ext = + { + empty : 'a; (** empty extension *) + check_instr : (** check the extension for an instruction *) + get_proc_desc -> Procname.t -> Cfg.Procdesc.t -> Cfg.Node.t + -> 'a -> Sil.instr -> parameters -> 'a; + join : 'a -> 'a -> 'a; (** join two extensions *) + pp : Format.formatter -> 'a -> unit (** pretty print an extension *) + } + + +module M = Map.Make (struct + type t = Sil.exp + let compare = Sil.exp_compare end) + +type range = Sil.typ * TypeAnnotation.t * (Sil.location list) + +type 'a t = + { + map: range M.t; + extension : 'a; + } + +let empty ext = + { + map = M.empty; + extension = ext.empty; + } + +let locs_compare = list_compare Sil.loc_compare +let locs_equal locs1 locs2 = locs_compare locs1 locs2 = 0 + +let range_equal (typ1, ta1, locs1) (typ2, ta2, locs2) = + Sil.typ_equal typ1 typ2 && TypeAnnotation.equal ta1 ta2 && locs_equal locs1 locs2 + +let equal t1 t2 = + (* Ignore the calls field, which is a pure instrumentation *) + M.equal range_equal t1.map t2.map + +let pp ext fmt typestate = + let pp_loc fmt loc = F.fprintf fmt "%d" loc.Sil.line in + let pp_locs fmt locs = F.fprintf fmt " [%a]" (pp_seq pp_loc) locs in + let pp_one exp (typ, ta, locs) = + F.fprintf fmt " %a -> [%s] %s %a%a@\n" + (Sil.pp_exp pe_text) exp + (TypeOrigin.to_string (TypeAnnotation.get_origin ta)) (TypeAnnotation.to_string ta) + (Sil.pp_typ_full pe_text) typ + pp_locs locs in + let pp_map map = M.iter pp_one map in + + pp_map typestate.map; + ext.pp fmt typestate.extension + +exception JoinFail + +let type_join typ1 typ2 = + if PatternMatch.type_is_object typ1 then typ2 else typ1 +let locs_join locs1 locs2 = + list_merge_sorted_nodup Sil.loc_compare [] locs1 locs2 + +(** Add a list of locations to a range. *) +let range_add_locs (typ, ta, locs1) locs2 = + let locs' = locs_join locs1 locs2 in + (typ, ta, locs') + +(** Join m2 to m1 if there are no inconsistencies, otherwise return t1. *) +let map_join m1 m2 = + let tjoined = ref m1 in + let range_join (typ1, ta1, locs1) (typ2, ta2, locs2) = + match TypeAnnotation.join ta1 ta2 with + | None -> None + | Some ta' -> + let typ' = type_join typ1 typ2 in + let locs' = locs_join locs1 locs2 in + Some (typ', ta', locs') in + let extend_lhs exp2 range2 = (* extend lhs if possible, otherwise return false *) + try + let range1 = M.find exp2 m1 in + (match range_join range1 range2 with + | None -> () + | Some range' -> tjoined := M.add exp2 range' !tjoined) + with Not_found -> + let (t2, ta2, locs2) = range2 in + let range2' = + let ta2' = TypeAnnotation.with_origin ta2 TypeOrigin.Undef in + (t2, ta2', locs2) in + tjoined := M.add exp2 range2' !tjoined in + let missing_rhs exp1 range1 = (* handle elements missing in the rhs *) + try + ignore (M.find exp1 m2) + with Not_found -> + let (t1, ta1, locs1) = range1 in + let range1' = + let ta1' = TypeAnnotation.with_origin ta1 TypeOrigin.Undef in + (t1, ta1', locs1) in + tjoined := M.add exp1 range1' !tjoined in + if m1 == m2 then m1 + else + try + M.iter extend_lhs m2; + M.iter missing_rhs m1; + !tjoined + with JoinFail -> m1 + +let join ext t1 t2 = + { + map = map_join t1.map t2.map; + extension = ext.join t1.extension t2.extension; + } + +let lookup_id id typestate = + try Some (M.find (Sil.Var id) typestate.map) + with Not_found -> None + +let lookup_pvar pvar typestate = + try Some (M.find (Sil.Lvar pvar) typestate.map) + with Not_found -> None + +let add_id id range typestate = + let map' = M.add (Sil.Var id) range typestate.map in + if map' == typestate.map then typestate + else { typestate with map = map' } + +let add_pvar pvar range typestate = + let map' = M.add (Sil.Lvar pvar) range typestate.map in + if map' == typestate.map then typestate + else { typestate with map = map' } + +let remove_id id typestate = + let map' = M.remove (Sil.Var id) typestate.map in + if map' == typestate.map then typestate + else { typestate with map = map' } + +let get_extension typestate = typestate.extension + +let set_extension typestate extension = + { typestate with extension = extension } diff --git a/infer/src/checkers/typeState.mli b/infer/src/checkers/typeState.mli new file mode 100644 index 000000000..74f01d659 --- /dev/null +++ b/infer/src/checkers/typeState.mli @@ -0,0 +1,39 @@ +(* +* Copyright (c) 2014 - Facebook. All rights reserved. +*) + +(** Module for typestates: maps from expressions to annotated types, with extensions. *) + +(** Parameters of a call. *) +type parameters = (Sil.exp * Sil.typ) list + +type get_proc_desc = Procname.t -> Cfg.Procdesc.t option + +(** Extension to a typestate with values of type 'a. *) +type 'a ext = + { + empty : 'a; (** empty extension *) + check_instr : (** check the extension for an instruction *) + get_proc_desc -> Procname.t -> Cfg.Procdesc.t -> Cfg.Node.t + ->'a -> Sil.instr -> parameters -> 'a; + join : 'a -> 'a -> 'a; (** join two extensions *) + pp : Format.formatter -> 'a -> unit (** pretty print an extension *) + } + +(** Typestate extended with elements of type 'a. *) +type 'a t + +type range = Sil.typ * TypeAnnotation.t * (Sil.location list) + +val add_id : Ident.t -> range -> 'a t -> 'a t +val add_pvar : Sil.pvar -> range -> 'a t -> 'a t +val empty : 'a ext -> 'a t +val equal : 'a t -> 'a t -> bool +val get_extension : 'a t -> 'a +val join : 'a ext -> 'a t -> 'a t -> 'a t +val lookup_id : Ident.t -> 'a t -> range option +val lookup_pvar : Sil.pvar -> 'a t -> range option +val pp : 'a ext -> Format.formatter -> 'a t -> unit +val range_add_locs : range -> (Sil.location list) -> range +val remove_id : Ident.t -> 'a t -> 'a t +val set_extension : 'a t -> 'a -> 'a t diff --git a/infer/src/clang/Makefile b/infer/src/clang/Makefile new file mode 100644 index 000000000..edfffe515 --- /dev/null +++ b/infer/src/clang/Makefile @@ -0,0 +1,19 @@ + +REMOVE = rm -vf + +EXAMPLE_SOURCES = test.c +EXAMPLE_AST = test.ast + +.PHONY: lib + +all: $(EXAMPLE_AST) + +lib: + ocamlbuild -I backend cMain.cma + +$(EXAMPLE_AST): $(EXAMPLE_SOURCES) + clang -cc1 -ast-dump test.c > test.ast + +clean: + ocamlbuild -clean + $(REMOVE) *~ $(EXAMPLE_AST) diff --git a/infer/src/clang/ast_expressions.ml b/infer/src/clang/ast_expressions.ml new file mode 100644 index 000000000..202fbe5a8 --- /dev/null +++ b/infer/src/clang/ast_expressions.ml @@ -0,0 +1,342 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +open Clang_ast_t +open CFrontend_utils + +(** This module creates extra ast constructs that are needed for the translation *) + +let dummy_source_range () = + let dummy_source_loc = { + sl_file = None; + sl_line = None; + sl_column = None + } in + (dummy_source_loc, dummy_source_loc) + +let dummy_stmt_info () = + { + Clang_ast_t.si_pointer = Ast_utils.get_fresh_pointer (); + Clang_ast_t.si_source_range = dummy_source_range () + } + +let dummy_decl_info decl_info = + { + decl_info with + Clang_ast_t.di_pointer = Ast_utils.get_fresh_pointer (); + Clang_ast_t.di_source_range = dummy_source_range (); + } + +let empty_decl_info = { + Clang_ast_t.di_pointer = ""; + Clang_ast_t.di_parent_pointer = None; + Clang_ast_t.di_previous_decl = `None; + Clang_ast_t.di_source_range = dummy_source_range (); + Clang_ast_t.di_owning_module = None; + Clang_ast_t.di_is_hidden = false; + Clang_ast_t.di_is_implicit = false; + Clang_ast_t.di_is_used = true; + Clang_ast_t.di_is_this_declaration_referenced = true; + Clang_ast_t.di_is_invalid_decl = false; + Clang_ast_t.di_attributes = []; + Clang_ast_t.di_full_comment = None; +} + +let stmt_info_with_fresh_pointer stmt_info = + { + Clang_ast_t.si_pointer = Ast_utils.get_fresh_pointer (); + Clang_ast_t.si_source_range = stmt_info.si_source_range + } + +let create_qual_type s = + { + Clang_ast_t.qt_raw = s; + Clang_ast_t.qt_desugared = Some s + } + +let create_pointer_type s = + create_qual_type (s^" *") + +let create_int_type () = create_qual_type "int" + +let create_void_type () = create_qual_type "void *" + +let create_id_type () = create_qual_type "id" + +let create_char_type () = create_qual_type "char *" + +let create_integer_literal stmt_info n = + let stmt_info = dummy_stmt_info () in + let expr_info = { + Clang_ast_t.ei_qual_type = create_int_type (); + Clang_ast_t.ei_value_kind = `RValue; + Clang_ast_t.ei_object_kind = `Ordinary + } in + let integer_literal_info = { + Clang_ast_t.ili_is_signed = true; + Clang_ast_t.ili_bitwidth = 32; + Clang_ast_t.ili_value = n + } in + IntegerLiteral (stmt_info, [], expr_info, integer_literal_info) + +let create_cstyle_cast_expr stmt_info stmts qt = + let expr_info = { + Clang_ast_t.ei_qual_type = create_void_type (); + Clang_ast_t.ei_value_kind = `RValue; + Clang_ast_t.ei_object_kind = `Ordinary + } in + let cast_expr = { + Clang_ast_t.cei_cast_kind = `NullToPointer; + Clang_ast_t.cei_base_path = [] + } in + CStyleCastExpr (stmt_info, stmts, expr_info, cast_expr, qt) + +let create_parent_expr stmt_info stmts = + let expr_info = { + Clang_ast_t.ei_qual_type = create_void_type (); + Clang_ast_t.ei_value_kind = `RValue; + Clang_ast_t.ei_object_kind = `Ordinary + } in + ParenExpr (stmt_info, stmts, expr_info) + +let create_implicit_cast_expr stmt_info stmts typ cast_kind = + let expr_info = { + Clang_ast_t.ei_qual_type = typ; + Clang_ast_t.ei_value_kind = `RValue; + Clang_ast_t.ei_object_kind = `Ordinary + } in + let cast_expr_info = { + Clang_ast_t.cei_cast_kind = cast_kind; + Clang_ast_t.cei_base_path = [] + } in + ImplicitCastExpr (stmt_info, stmts, expr_info, cast_expr_info) + +let create_nil stmt_info = + let integer_literal = create_integer_literal stmt_info "0" in + let cstyle_cast_expr = create_cstyle_cast_expr stmt_info [integer_literal] (create_int_type ()) in + let paren_expr = create_parent_expr stmt_info [cstyle_cast_expr] in + let implicit_cast_expr = create_implicit_cast_expr stmt_info [paren_expr] (create_id_type ()) `NullToPointer in + implicit_cast_expr + +let dummy_stmt () = + let pointer = Ast_utils.get_fresh_pointer () in + let source_range = dummy_source_range () in + NullStmt({ Clang_ast_t.si_pointer = pointer; Clang_ast_t.si_source_range = source_range } ,[]) + +let make_stmt_info di = + { Clang_ast_t.si_pointer = di.Clang_ast_t.di_pointer; Clang_ast_t.si_source_range = di.Clang_ast_t.di_source_range } + +let make_expr_info qt objc_kind = { + Clang_ast_t.ei_qual_type = qt; + Clang_ast_t.ei_value_kind = `LValue; + Clang_ast_t.ei_object_kind = objc_kind;} + +let make_method_decl_info mdi body = { + Clang_ast_t.omdi_is_instance_method = mdi.Clang_ast_t.omdi_is_instance_method; + Clang_ast_t.omdi_result_type = mdi.Clang_ast_t.omdi_result_type; + Clang_ast_t.omdi_parameters = mdi.Clang_ast_t.omdi_parameters; + Clang_ast_t.omdi_is_variadic = mdi.Clang_ast_t.omdi_is_variadic; + Clang_ast_t.omdi_body = Some body; } + +let make_decl_ref_exp stmt_info expr_info drei = + let stmt_info = { + Clang_ast_t.si_pointer = Ast_utils.get_fresh_pointer (); + Clang_ast_t.si_source_range = stmt_info.Clang_ast_t.si_source_range + } in + DeclRefExpr(stmt_info, [], expr_info, drei) + +let make_obj_c_message_expr_info sel typ = + { + Clang_ast_t.omei_selector = sel; + Clang_ast_t.omei_receiver_kind = `Class (create_qual_type typ) + } + +let make_general_decl_ref k name is_hidden qt = { + Clang_ast_t.dr_kind = k; + Clang_ast_t.dr_name = Some name; + Clang_ast_t.dr_is_hidden = is_hidden ; + Clang_ast_t.dr_qual_type = Some (qt) +} + +let make_decl_ref name = + make_general_decl_ref (`Var) name false (create_int_type ()) + +let make_decl_ref_self qt = { + Clang_ast_t.dr_kind = `ImplicitParam; + Clang_ast_t.dr_name = Some "self"; + Clang_ast_t.dr_is_hidden = false ; + Clang_ast_t.dr_qual_type = Some qt +} + +let make_decl_ref_expr_info decl_ref = { + Clang_ast_t.drti_decl_ref = Some decl_ref; + Clang_ast_t.drti_found_decl_ref = None; +} + +let make_obj_c_ivar_ref_expr_info k n qt = { + Clang_ast_t.ovrei_decl_ref = make_general_decl_ref k n false qt; + Clang_ast_t.ovrei_pointer = Ast_utils.get_fresh_pointer (); + Clang_ast_t.ovrei_is_free_ivar = true; +} + +let make_nondet_exp stmt_info = + let drei = { + Clang_ast_t.drti_decl_ref = Some (make_decl_ref "__INFER_NON_DET"); + Clang_ast_t.drti_found_decl_ref = None; } in + let qt = create_int_type () in + let ei = make_expr_info qt `Ordinary in + let e = make_decl_ref_exp stmt_info ei drei in + let cast = create_implicit_cast_expr stmt_info [e] qt `LValueToRValue in + let zero = create_integer_literal stmt_info "0" in + BinaryOperator(stmt_info, [cast; zero], ei, { Clang_ast_t.boi_kind = `GT } ) + +(* Build an AST cast expression of a decl_ref_expr *) +let make_cast_expr qt di decl_ref_expr_info objc_kind = + let expr_info = make_expr_info qt objc_kind in + let stmt_info = make_stmt_info di in + let decl_ref_exp = make_decl_ref_exp stmt_info expr_info decl_ref_expr_info in + let cast_expr = { + Clang_ast_t.cei_cast_kind = `LValueToRValue; + Clang_ast_t.cei_base_path = [] + } in + let cast_exp_rhs = ImplicitCastExpr(stmt_info, [decl_ref_exp], expr_info, cast_expr) in + cast_exp_rhs + +(* Build AST expression self.field_name as `LValue *) +let make_self_field class_type di qt field_name = + let qt_class = create_qual_type class_type in + let expr_info = make_expr_info qt `ObjCProperty in + let stmt_info = make_stmt_info di in + let cast_exp = make_cast_expr qt_class di (make_decl_ref_expr_info (make_decl_ref_self qt_class)) `ObjCProperty in + let obj_c_ivar_ref_expr_info = make_obj_c_ivar_ref_expr_info (`ObjCIvar) field_name qt in + let ivar_ref_exp = ObjCIvarRefExpr(stmt_info, [cast_exp], expr_info, obj_c_ivar_ref_expr_info) in + ivar_ref_exp + +(* Build AST expression for self.field_name casted as `RValue. *) +let make_deref_self_field class_decl_opt di qt field_name = + let stmt_info = make_stmt_info di in + let ivar_ref_exp = make_self_field class_decl_opt di qt field_name in + let expr_info' = make_expr_info qt `ObjCProperty in + let cast_exp_info = + { + Clang_ast_t.cei_cast_kind = `LValueToRValue; + Clang_ast_t.cei_base_path = [] + } in + let cast_exp' = ImplicitCastExpr(stmt_info, [ivar_ref_exp], expr_info', cast_exp_info) in + cast_exp' + +let make_objc_ivar_decl decl_info qt property_impl_decl_info = + let name = Ast_utils.property_name property_impl_decl_info in + let qt = match qt with + | Some qt' -> qt' + | None -> (* a qual_type was not found by the caller, so we try to get it out of property_impl_decl_info *) + (match property_impl_decl_info.Clang_ast_t.opidi_ivar_decl with + | Some decl_ref -> (match decl_ref.Clang_ast_t.dr_qual_type with + | Some qt' -> qt' + | None -> assert false) + | _ -> assert false) in + let field_decl_info = { + Clang_ast_t.fldi_is_mutable = true; + Clang_ast_t.fldi_is_module_private = true; + Clang_ast_t.fldi_init_expr = None; + Clang_ast_t.fldi_bit_width_expr = None } in + let obj_c_ivar_decl_info = { + Clang_ast_t.ovdi_is_synthesize = true; (* NOTE: We set true here because we use this definition to synthesize the getter/setter*) + Clang_ast_t.ovdi_access_control = `Private } in + ObjCIvarDecl(decl_info, name, qt, field_decl_info, obj_c_ivar_decl_info) + +let make_expr_info qt = + { + Clang_ast_t.ei_qual_type = qt; + Clang_ast_t.ei_value_kind = `LValue; + Clang_ast_t.ei_object_kind = `ObjCProperty + } + +let make_decl_ref_exp_var (var_name, var_qt) var_kind stmt_info = + let stmt_info = stmt_info_with_fresh_pointer stmt_info in + let decl_ref = make_general_decl_ref var_kind var_name false var_qt in + let expr_info = make_expr_info var_qt in + make_decl_ref_exp stmt_info expr_info (make_decl_ref_expr_info decl_ref) + +let make_message_expr param_qt selector decl_ref_exp stmt_info = + let stmt_info = stmt_info_with_fresh_pointer stmt_info in + let cast_expr = create_implicit_cast_expr stmt_info [decl_ref_exp] param_qt `LValueToRValue in + let parameters = [cast_expr] in + let obj_c_message_expr_info = { + omei_selector = selector; + omei_receiver_kind = `Instance + } in + let expr_info = make_expr_info param_qt in + ObjCMessageExpr (stmt_info, parameters, expr_info, obj_c_message_expr_info) + +let make_compound_stmt stmts stmt_info = + let stmt_info = stmt_info_with_fresh_pointer stmt_info in + CompoundStmt (stmt_info, stmts) + +let make_binary_stmt stmt1 stmt2 stmt_info expr_info boi = + let stmt_info = stmt_info_with_fresh_pointer stmt_info in + BinaryOperator(stmt_info, [stmt1; stmt2], expr_info, boi) + +let make_obj_c_message_expr_info_class selector qt = + { + omei_selector = selector; + omei_receiver_kind = `Class qt; + } + +let empty_var_decl = { + vdi_storage_class = None; + vdi_tls_kind =`Tls_none; + vdi_is_module_private = false; + vdi_is_nrvo_variable = false; + vdi_init_expr = None +} + +(* dispatch_once(v,block_def) is transformed as: *) +(* void (^block_var)()=block_def; block_var() *) +let translate_dispatch_function block_name stmt_info stmt_list ei n = + let block_expr = + try Utils.list_nth stmt_list (n + 1) + with Not_found -> assert false in + match block_expr with BlockExpr(bsi, bsl, bei, bd) -> + let qt = bei.Clang_ast_t.ei_qual_type in + let cast_info = { cei_cast_kind = `BitCast; cei_base_path =[]} in + let block_def = ImplicitCastExpr(stmt_info,[block_expr], bei, cast_info) in + let decl_info = { empty_decl_info + with di_pointer = stmt_info.si_pointer; di_source_range = stmt_info.si_source_range } in + let var_decl_info = { empty_var_decl with vdi_init_expr = Some block_def } in + let block_var_decl = VarDecl(decl_info, block_name, ei.ei_qual_type, var_decl_info) in + let decl_stmt = DeclStmt(stmt_info,[], [block_var_decl]) in + let expr_info_call = { + Clang_ast_t.ei_qual_type = create_void_type (); + Clang_ast_t.ei_value_kind = `XValue; + Clang_ast_t.ei_object_kind = `Ordinary + } in + let expr_info_dre = { + Clang_ast_t.ei_qual_type = qt; + Clang_ast_t.ei_value_kind = `LValue; + Clang_ast_t.ei_object_kind = `Ordinary + } in + let decl_ref = { + dr_kind = `Var; + dr_name = Some block_name; + dr_is_hidden = false; + dr_qual_type = Some qt; + } in + let decl_ref_expr_info = { + drti_decl_ref = Some decl_ref; + drti_found_decl_ref = None + } in + let cast_info_call = { cei_cast_kind = `LValueToRValue; cei_base_path =[]} in + let decl_ref_exp = DeclRefExpr(stmt_info, [], expr_info_dre, decl_ref_expr_info) in + let stmt_call = ImplicitCastExpr(stmt_info, [decl_ref_exp], bei, cast_info_call) in + let call_block_var = CallExpr(stmt_info, [stmt_call], expr_info_call) in + CompoundStmt (stmt_info, [decl_stmt; call_block_var]), qt + | _ -> assert false (* when we call this function we have already checked that this cannot be possible *) + +(* We translate the logical negation of an integer with a conditional*) +(* !x <=> x?0:1 *) +let trans_negation_with_conditional stmt_info expr_info stmt_list = + let stmt_list_cond = stmt_list @ [create_integer_literal stmt_info "0"] @ [create_integer_literal stmt_info "1"] in + ConditionalOperator(stmt_info, stmt_list_cond, expr_info) diff --git a/infer/src/clang/ast_expressions.mli b/infer/src/clang/ast_expressions.mli new file mode 100644 index 000000000..8c5052aaf --- /dev/null +++ b/infer/src/clang/ast_expressions.mli @@ -0,0 +1,64 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +open Clang_ast_t + +(** This module creates extra ast constructs that are needed for the translation *) + +val dummy_stmt : unit -> stmt + +val dummy_decl_info : decl_info -> decl_info + +val dummy_source_range : unit -> source_range + +val dummy_stmt_info : unit -> stmt_info + +val create_qual_type : string -> qual_type + +val create_pointer_type : string -> qual_type + +val make_objc_ivar_decl : decl_info -> qual_type option -> obj_c_property_impl_decl_info -> decl + +val make_deref_self_field : string -> decl_info -> qual_type -> string -> stmt + +val make_stmt_info : decl_info -> stmt_info + +val make_method_decl_info : obj_c_method_decl_info -> stmt -> obj_c_method_decl_info + +val make_general_decl_ref : decl_kind -> string -> bool -> qual_type -> decl_ref + +val make_decl_ref_expr_info : decl_ref -> decl_ref_expr_info + +val make_expr_info : qual_type -> expr_info + +val make_cast_expr : qual_type -> decl_info -> decl_ref_expr_info -> object_kind -> stmt + +val make_self_field : string -> decl_info -> qual_type -> string -> stmt + +val make_nondet_exp : stmt_info -> stmt + +val make_obj_c_message_expr_info : string -> string -> obj_c_message_expr_info + +val create_nil : stmt_info -> stmt + +val create_implicit_cast_expr : stmt_info -> stmt list -> qual_type -> cast_kind -> stmt + +val create_char_type : unit -> qual_type + +val make_message_expr : qual_type -> string -> stmt -> stmt_info -> stmt + +val make_compound_stmt : stmt list -> stmt_info -> stmt + +val make_decl_ref_exp_var : string * qual_type -> decl_kind -> stmt_info -> stmt + +val make_binary_stmt : stmt -> stmt -> stmt_info -> expr_info -> binary_operator_info -> stmt + +val make_obj_c_message_expr_info_class : string -> qual_type -> obj_c_message_expr_info + +val translate_dispatch_function : string -> stmt_info -> stmt list -> expr_info -> int -> stmt * qual_type + +(* We translate the logical negation of an integer with a conditional*) +(* !x <=> x?0:1 *) +val trans_negation_with_conditional : stmt_info -> expr_info -> stmt list -> stmt diff --git a/infer/src/clang/ast_lexer.mll b/infer/src/clang/ast_lexer.mll new file mode 100644 index 000000000..46ca24b62 --- /dev/null +++ b/infer/src/clang/ast_lexer.mll @@ -0,0 +1,142 @@ +(* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*) + +(* Lexer to support a parser of types *) +{ + +open CTypes_parser + +let line_number = ref 1 +let parentheses = ref 0 + +let parsed_tokens = Buffer.create 1 +let log lexbuf = Buffer.add_char parsed_tokens ' '; Buffer.add_string parsed_tokens (Lexing.lexeme lexbuf) +let reset_log () = Buffer.reset parsed_tokens +let get_log () = Buffer.contents parsed_tokens + +let attr_par = ref 0 +let attr_buf = Buffer.create 1 +let reset_attr_buf () = Buffer.reset attr_buf +let log_attr_buf lexbuf = + Buffer.add_string parsed_tokens (Lexing.lexeme lexbuf); + Buffer.add_string attr_buf (Lexing.lexeme lexbuf) +let get_attr_buf () = Buffer.contents attr_buf + + +} + +let lowerletter = ['a'-'z'] +let upperletter = ['A'-'Z'] + +let underscore = '_' +let minus = '-' +let letter = lowerletter | upperletter + +let digit = ['0'-'9'] +let number = digit* | digit+ '.' digit* | digit* '.' digit+ +let space = [' ' '\t'] + +let identifier = (letter | underscore) (letter | underscore | digit | ':')* + +let anonymous_identifier = '(' 'a' 'n' 'o' 'n' 'y' 'm' 'o' 'u' 's' (letter | underscore | digit | ' ' | '.' | ':' | '/' | minus)* ')' + +let nested_anonymous_identifier = (identifier ':' ':')+ (anonymous_identifier | identifier) + +let nested_identifier = identifier (':' ':' identifier)+ + +let char = letter | digit + +let hexa_digit = ['0'-'9' 'a'-'f'] +let hexa = '0' 'x' hexa_digit* + +let dir_sep = '/' +let dot = '.' +let extension = dot ('c' | 'h' | 'm') +let basename = (char | underscore | minus | dot )+ +let filename = basename extension +let path = (dir_sep basename | basename)* filename +let attribute_digit = letter | underscore | digit | '(' | ')' | '\"' | "," + +let newline = '\n' + +rule token = parse + | space { log lexbuf; token lexbuf } + | newline { log lexbuf; incr line_number; token lexbuf } + | '.' { log lexbuf; DOT } + | ';' { log lexbuf; SEMICOLON } + | ':' { log lexbuf; COLON } + | ',' { log lexbuf; COMMA } + | '\'' { log lexbuf; SINGLE_QUOTE } + | '`' { log lexbuf; REV_QUOTE } + | '\"' { log lexbuf; DOUBLE_QUOTE } + | '^' { log lexbuf; CARRET } + | "class" { log lexbuf; CLASS } + | "struct" { log lexbuf; STRUCT } + | "union" { log lexbuf; UNION } + | "enum" { log lexbuf; ENUM } + | "unsigned" { log lexbuf; UNSIGNED } + | "signed" { log lexbuf; SIGNED } + | "void" { log lexbuf; VOID } + | "char" { log lexbuf; CHAR } + | "short" { log lexbuf; SHORT } + | "int" { log lexbuf; INT } + | "long" { log lexbuf; LONG } + | "_Bool" { log lexbuf; UND_BOOL } + | "__uint16_" { log lexbuf; UND_UND_UINT16_ } + | "__uint16_t" { log lexbuf; UND_UND_UINT16_T } + | "__uint32_" { log lexbuf; UND_UND_UINT32_ } + | "__uint32_t" { log lexbuf; UND_UND_UINT32_T } + | "__int32_t" { log lexbuf; UND_UND_INT32_T } + | "__int64_t" { log lexbuf; UND_UND_INT64_T } + | "__uint64_t" { log lexbuf; UND_UND_UINT64_T } + | "UInt8" { log lexbuf; UINT8 } + | "UInt16" { log lexbuf; UINT16 } + | "UInt32" { log lexbuf; UINT32 } + | "UInt64" { log lexbuf; UINT64 } + | "float" { log lexbuf; FLOAT } + | "double" { log lexbuf; DOUBLE } + | "volatile" { log lexbuf; VOLATILE } + | "*volatile" { log lexbuf; STARVOLATILE } + | "" { log lexbuf; BUILTIN_FN_TYPE} + | "inline" { log lexbuf; INLINE } + | "typename" { log lexbuf; TYPENAME } + | "const" { log lexbuf; CONST } + | "*const" { log lexbuf; STARCONST } + | "__strong" { log lexbuf; UND_UND_STRONG } + | "__unsafe_unretained" { log lexbuf; UND_UND_UNSAFE_UNRETAINED } + | "__weak" { log lexbuf; UND_UND_WEAK } + | "__autoreleasing" { log lexbuf; UND_UND_AUTORELEASING } + | "*__strong" { log lexbuf; STAR_UND_UND_STRONG } + | "*__unsafe_unretained" { log lexbuf; STAR_UND_UND_UNSAFE_UNRETAINED } + | "*__weak" { log lexbuf; STAR_UND_UND_WEAK } + | "*__autoreleasing" { log lexbuf; STAR_UND_UND_AUTORELEASING } + | "noexcept" { log lexbuf; NOEXCEPT } + | "restrict" { log lexbuf; RESTRICT } + | '%' { log lexbuf; PERCENT } + | '&' { log lexbuf; AMPERSAND } + | '!' { log lexbuf; EXCLAMATION } + | '=' { log lexbuf; EQUAL } + | '-' { log lexbuf; MINUS } + | '+' { log lexbuf; PLUS } + | '<' { log lexbuf; LEFT_CHEVRON } + | '>' { log lexbuf; RIGHT_CHEVRON } + | '(' { log lexbuf; incr parentheses; LEFT_PARENTHESIS } + | ')' { log lexbuf; decr parentheses; RIGHT_PARENTHESIS } + | '[' { log lexbuf; LEFT_SQBRACKET } + | ']' { log lexbuf; RIGHT_SQBRACKET } + | '{' { log lexbuf; LEFT_BRACE } + | '}' { log lexbuf; RIGHT_BRACE } + | '*' { log lexbuf; STAR } + | '|' { log lexbuf; PIPE } + | '/' { log lexbuf; SLASH } + | '\\' { log lexbuf; BACKSLASH } + | hexa as s { log lexbuf; HEXA (s) } + | number as n { log lexbuf; NUMBER n } + | identifier as s { log lexbuf; IDENT (s) } + | anonymous_identifier as s { log lexbuf; ANONYM_IDENT (s) } + | nested_identifier as s { log lexbuf; NESTED_IDENT (s) } + | nested_anonymous_identifier as s { log lexbuf; NESTED_ANONYM_IDENT (s) } + | eof { EOF } + diff --git a/infer/src/clang/cArithmetic_trans.ml b/infer/src/clang/cArithmetic_trans.ml new file mode 100644 index 000000000..ac90a030b --- /dev/null +++ b/infer/src/clang/cArithmetic_trans.ml @@ -0,0 +1,207 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Utility module for translating unary and binary operations and compound assignments *) + +open CFrontend_utils + +(* Returns the translation of assignment when ARC mode is enabled in Obj-C *) +(* For __weak and __unsafe_unretained the translation is the same as non-ARC *) +(* (this is because, in these cases, there is no change in the reference counter *) +(* of the pointee).*) +(* The difference is when the lvalue is a __strong or __autoreleasing. In those*) +(* case we need to add proper retain/release.*) +(* See document: "Objective-C Automatic Reference Counting" describing the semantics *) +let assignment_arc_mode context e1 typ e2 loc rhs_owning_method is_e1_decl = + let assign = Sil.Set (e1, typ, e2, loc) in + let retain_pname = SymExec.ModelBuiltins.__objc_retain in + let release_pname = SymExec.ModelBuiltins.__objc_release in + let autorelease_pname = SymExec.ModelBuiltins.__set_autorelease_attribute in + let mk_call procname e t = + let bi_retain = Sil.Const (Sil.Cfun procname) in + Sil.Call([], bi_retain, [(e, t)], loc, Sil.cf_default) in + match typ with + | Sil.Tptr (t, Sil.Pk_pointer) when not rhs_owning_method && not is_e1_decl -> + (* for __strong e1 = e2 the semantics is*) + (* retain(e2); tmp=e1; e1=e2; release(tmp); *) + let retain = mk_call retain_pname e2 typ in + let id = Ident.create_fresh Ident.knormal in + let tmp_assign = Sil.Letderef(id, e1, typ, loc) in + let release = mk_call release_pname (Sil.Var id) typ in + (e1,[retain; tmp_assign; assign; release ], [id]) + | Sil.Tptr (t, Sil.Pk_pointer) when not rhs_owning_method && is_e1_decl -> + (* for A __strong *e1 = e2 the semantics is*) + (* retain(e2); e1=e2; *) + let retain = mk_call retain_pname e2 typ in + (e1,[retain; assign ], []) + | Sil.Tptr (t, Sil.Pk_objc_weak) + | Sil.Tptr (t, Sil.Pk_objc_unsafe_unretained) -> + (e1, [assign],[]) + | Sil.Tptr (t, Sil.Pk_objc_autoreleasing) -> + (* for __autoreleasing e1 = e2 the semantics is*) + (* retain(e2); autorelease(e2); e1=e2; *) + let retain = mk_call retain_pname e2 typ in + let autorelease = mk_call autorelease_pname e2 typ in + (e1, [retain; autorelease; assign], []) + | _ -> (e1, [assign], []) + +(* Returns a pair ([binary_expression], instructions). "binary_expression" *) +(* is returned when we are calculating an expression "instructions" is not *) +(* empty when the binary operator is actually a statement like an *) +(* assignment. *) +let binary_operation_instruction context boi e1 typ e2 loc rhs_owning_method = + let binop_exp op = Sil.BinOp(op, e1, e2) in + match boi.Clang_ast_t.boi_kind with + | `Add -> (binop_exp (Sil.PlusA), [], []) + | `Mul -> (binop_exp (Sil.Mult), [], []) + | `Div -> (binop_exp (Sil.Div), [], []) + | `Rem -> (binop_exp (Sil.Mod), [], []) + | `Sub -> (binop_exp (Sil.MinusA), [], []) + | `Shl -> (binop_exp (Sil.Shiftlt), [], []) + | `Shr -> (binop_exp(Sil.Shiftrt), [], []) + | `Or -> (binop_exp (Sil.BOr), [], []) + | `And -> (binop_exp (Sil.BAnd), [], []) + | `Xor -> (binop_exp (Sil.BXor), [], []) + | `LT -> (binop_exp (Sil.Lt), [], []) + | `GT -> (binop_exp (Sil.Gt), [], []) + | `LE -> (binop_exp (Sil.Le), [], []) + | `GE -> (binop_exp (Sil.Ge), [], []) + | `NE -> (binop_exp (Sil.Ne), [], []) + | `EQ -> (binop_exp (Sil.Eq), [], []) + | `LAnd -> (binop_exp (Sil.LAnd), [], []) + | `LOr -> (binop_exp (Sil.LOr), [], []) + | `Assign -> + if !Config.arc_mode && ObjcInterface_decl.is_pointer_to_objc_class context.CContext.tenv typ then + assignment_arc_mode context e1 typ e2 loc rhs_owning_method false + else + (e1, [Sil.Set (e1, typ, e2, loc)], []) + | `Comma -> (e2, [], []) (* C99 6.5.17-2 *) + | `MulAssign | `DivAssign | `RemAssign | `AddAssign | `SubAssign + | `ShlAssign | `ShrAssign | `AndAssign | `XorAssign | `OrAssign -> + assert false + (* We should not get here. *) + (* These should be treated by compound_assignment_binary_operation_instruction*) + | bok -> + Printing.log_stats + ~fmt:"\nWARNING: Missing translation for Binary Operator Kind %s. Construct ignored...\n" + (Clang_ast_j.string_of_binary_operator_kind bok); + (Sil.exp_minus_one, [], []) + +(* Returns a pair ([binary_expression], instructions) for binary operator representing a CompoundAssignment. *) +(* "binary_expression" is returned when we are calculating an expression*) +(* "instructions" is not empty when the binary operator is actually a statement like an assignment. *) +let compound_assignment_binary_operation_instruction boi e1 typ e2 loc = + let id = Ident.create_fresh Ident.knormal in + let instr1 = Sil.Letderef (id, e1, typ, loc) in + let e_res, instr_op = match boi.Clang_ast_t.boi_kind with + | `AddAssign -> + let e1_plus_e2 = Sil.BinOp(Sil.PlusA, Sil.Var id, e2) in + (e1, [Sil.Set (e1, typ, e1_plus_e2, loc)]) + | `SubAssign -> + let e1_sub_e2 = Sil.BinOp(Sil.MinusA, Sil.Var id, e2) in + (e1, [Sil.Set (e1, typ, e1_sub_e2, loc)]) + | `MulAssign -> + let e1_mul_e2 = Sil.BinOp(Sil.Mult, Sil.Var id, e2) in + (e1, [Sil.Set (e1, typ, e1_mul_e2, loc)]) + | `DivAssign -> + let e1_div_e2 = Sil.BinOp(Sil.Div, Sil.Var id, e2) in + (e1, [Sil.Set (e1, typ, e1_div_e2, loc)]) + | `ShlAssign -> + let e1_shl_e2 = Sil.BinOp(Sil.Shiftlt, Sil.Var id, e2) in + (e1, [Sil.Set (e1, typ, e1_shl_e2, loc)]) + | `ShrAssign -> + let e1_shr_e2 = Sil.BinOp(Sil.Shiftrt, Sil.Var id, e2) in + (e1, [Sil.Set (e1, typ, e1_shr_e2, loc)]) + | `RemAssign -> + let e1_mod_e2 = Sil.BinOp(Sil.Mod, Sil.Var id, e2) in + (e1, [Sil.Set (e1, typ, e1_mod_e2, loc)]) + | `AndAssign -> + let e1_and_e2 = Sil.BinOp(Sil.BAnd, Sil.Var id, e2) in + (e1, [Sil.Set (e1, typ, e1_and_e2, loc)]) + | `OrAssign -> + let e1_or_e2 = Sil.BinOp(Sil.BOr, Sil.Var id, e2) in + (e1, [Sil.Set (e1, typ, e1_or_e2, loc)]) + | `XorAssign -> + let e1_xor_e2 = Sil.BinOp(Sil.BXor, Sil.Var id, e2) in + (e1, [Sil.Set (e1, typ, e1_xor_e2, loc)]) + | bok -> + Printing.log_stats + ~fmt:"\nWARNING: Missing translation for CompoundAssignment Binary Operator Kind %s. Construct ignored...\n" + (Clang_ast_j.string_of_binary_operator_kind bok); + (Sil.exp_minus_one, []) in + ([id], e_res, instr1:: instr_op) + +let unary_operation_instruction uoi e typ loc = + let uok = Clang_ast_j.string_of_unary_operator_kind (uoi.Clang_ast_t.uoi_kind) in + let un_exp op = + Sil.UnOp(op, e, Some typ) in + match uoi.Clang_ast_t.uoi_kind with + | `PostInc -> + let id = Ident.create_fresh Ident.knormal in + let instr1 = Sil.Letderef (id, e, typ, loc) in + let e_plus_1 = Sil.BinOp(Sil.PlusA, Sil.Var id, Sil.Const(Sil.Cint (Sil.Int.one))) in + ([id], Sil.Var id, instr1::[Sil.Set (e, typ, e_plus_1, loc)]) + | `PreInc -> + let id = Ident.create_fresh Ident.knormal in + let instr1 = Sil.Letderef (id, e, typ, loc) in + let e_plus_1 = Sil.BinOp(Sil.PlusA, Sil.Var id, Sil.Const(Sil.Cint (Sil.Int.one))) in + ([id], e_plus_1, instr1::[Sil.Set (e, typ, e_plus_1, loc)]) + | `PostDec -> + let id = Ident.create_fresh Ident.knormal in + let instr1 = Sil.Letderef (id, e, typ, loc) in + let e_minus_1 = Sil.BinOp(Sil.MinusA, Sil.Var id, Sil.Const(Sil.Cint (Sil.Int.one))) in + ([id], Sil.Var id, instr1::[Sil.Set (e, typ, e_minus_1, loc)]) + | `PreDec -> + let id = Ident.create_fresh Ident.knormal in + let instr1 = Sil.Letderef (id, e, typ, loc) in + let e_minus_1 = Sil.BinOp(Sil.MinusA, Sil.Var id, Sil.Const(Sil.Cint (Sil.Int.one))) in + ([id], e_minus_1, instr1::[Sil.Set (e, typ, e_minus_1, loc)]) + | `Not -> ([], un_exp (Sil.BNot), []) + | `Minus -> ([], un_exp (Sil.Neg), []) + | `Plus -> ([], e, []) + | `LNot -> ([], un_exp (Sil.LNot), []) + | `Deref -> + (* Actual dereferencing is handled by implicit cast from rvalue to lvalue *) + ([], e, []) + | `AddrOf -> ([], e, []) + | `Real | `Imag | `Extension -> + Printing.log_stats + ~fmt:"\nWARNING: Missing translation for Unary Operator Kind %s. The construct has been ignored...\n" uok; + ([], e, []) + +let bin_op_to_string boi = + match boi.Clang_ast_t.boi_kind with + | `PtrMemD -> "PtrMemD" + | `PtrMemI -> "PtrMemI" + | `Mul -> "Mul" + | `Div -> "Div" + | `Rem -> "Rem" + | `Add -> "Add" + | `Sub -> "Sub" + | `Shl -> "Shl" + | `Shr -> "Shr" + | `LT -> "LT" + | `GT -> "GT" + | `LE -> "LE" + | `GE -> "GE" + | `EQ -> "EQ" + | `NE -> "NE" + | `And -> "And" + | `Xor -> "Xor" + | `Or -> "Or" + | `LAnd -> "LAnd" + | `LOr -> "LOr" + | `Assign -> "Assign" + | `MulAssign -> "MulAssign" + | `DivAssign -> "DivAssign" + | `RemAssign -> "RemAssing" + | `AddAssign -> "AddAssign" + | `SubAssign -> "SubAssign" + | `ShlAssign -> "ShlAssign" + | `ShrAssign -> "ShrAssign" + | `AndAssign -> "AndAssign" + | `XorAssign -> "XorAssign" + | `OrAssign -> "OrAssign" + | `Comma -> "Comma" diff --git a/infer/src/clang/cArithmetic_trans.mli b/infer/src/clang/cArithmetic_trans.mli new file mode 100644 index 000000000..af8e1632a --- /dev/null +++ b/infer/src/clang/cArithmetic_trans.mli @@ -0,0 +1,20 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Utility module for translating unary and binary operations and compound assignments *) + +val bin_op_to_string : Clang_ast_t.binary_operator_info -> string + +val binary_operation_instruction : CContext.t -> Clang_ast_t.binary_operator_info -> Sil.exp -> Sil.typ -> Sil.exp -> +Sil.location -> bool -> Sil.exp * Sil.instr list * Ident.t list + +val unary_operation_instruction : Clang_ast_t.unary_operator_info -> Sil.exp -> Sil.typ -> Sil.location -> +Ident.t list * Sil.exp * Sil.instr list + +val compound_assignment_binary_operation_instruction : Clang_ast_t.binary_operator_info -> Sil.exp -> +Sil.typ -> Sil.exp -> Sil.location -> Ident.t list * Sil.exp * Sil.instr list + +val assignment_arc_mode : CContext.t -> Sil.exp -> Sil.typ -> Sil.exp -> Sil.location -> bool -> bool -> Sil.exp * Sil.instr list * Ident.t list + diff --git a/infer/src/clang/cContext.ml b/infer/src/clang/cContext.ml new file mode 100644 index 000000000..57a6f4f51 --- /dev/null +++ b/infer/src/clang/cContext.ml @@ -0,0 +1,290 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Contains current class and current method to be translated as well as local variables, *) +(** and the cg, cfg, and tenv corresponding to the current file. *) + +open Utils +open CFrontend_utils + +type varStack = (Mangled.t * Sil.typ * int) Stack.t + +type varMap = varStack StringMap.t + +type pointerVarMap = Sil.pvar StringMap.t + +module L = Logging + +type curr_class = + | ContextCls of string * string option * string list + (*class name and name of (optional) super class , and a list of protocols *) + | ContextCategory of string * string (* category name and corresponding class *) + | ContextProtocol of string (* category name and corresponding class *) + | ContextNoCls + +type t = + { + tenv : Sil.tenv; + cg : Cg.t; + cfg : Cfg.cfg; + procdesc : Cfg.Procdesc.t; + is_objc_method : bool; + is_instance : bool; + curr_class: curr_class; + is_callee_expression : bool; + namespace: string option; (* contains the name of the namespace if we are in the scope of one*) + mutable local_vars : (Mangled.t * Sil.typ * bool) list; (* (name, type, is_static flag) *) + mutable captured_vars : (Mangled.t * Sil.typ * bool) list; (* (name, type, is_static flag) *) + mutable local_vars_stack : varMap; + mutable local_vars_pointer : pointerVarMap + } + +module LocalVars = +struct + + module Block = + struct + let depth_counter = ref 0 + + let enter () = + depth_counter := !depth_counter + 1 + + let leave () = + depth_counter := !depth_counter - 1 + + let reset () = + depth_counter := 0 + + let depth () = !depth_counter + + end + + let lookup_var_map context var_name = + try + StringMap.find var_name context.local_vars_stack + with Not_found -> Stack.create () + + let lookup_var_formals context procname var_name = + let formals = Cfg.Procdesc.get_formals context.procdesc in + let arg, typ = list_find (fun (arg, typ) -> arg = var_name) formals in + let var = Sil.mk_pvar (Mangled.from_string var_name) procname in + (var, typ) + + let lookup_var_captured context procname var_name = + let cv, typ, _ = list_find (fun (arg, typ, _) -> arg = Mangled.from_string var_name) context.captured_vars in + let var = Sil.mk_pvar cv procname in + (var, typ) + + let lookup_var_globals context procname name = + let var_name = Mangled.from_string name in + let global_var = CGlobal_vars.find var_name in + let var = CGlobal_vars.var_get_name global_var in + Printing.log_out ~fmt:" ...Variable '%s' found in globals!!\n" (Sil.pvar_to_string var); + let typ = CGlobal_vars.var_get_typ global_var in + var, typ + + let is_captured_var context name = + list_exists (fun (v, _, _) -> (Mangled.to_string v) = name) context.captured_vars + + let is_global_var context name = + try + let procname = Cfg.Procdesc.get_proc_name context.procdesc in + ignore (lookup_var_globals context procname name); + true + with Not_found -> false + + let print_locals context = + let print_stack var_name stack = + Stack.iter + (fun (var_name, typ, level) -> + Printing.log_out ~fmt:"var item %s:" (Mangled.to_string var_name); + Printing.log_out ~fmt:"%s" (Sil.typ_to_string typ); + Printing.log_out ~fmt:"- %s \n%!" (string_of_int level)) stack in + Printing.log_out "LOCAL VARS:%s\n"; + StringMap.iter print_stack context.local_vars_stack + + let print_pointer_vars context = + let print_pointer_var pointer var = + Printing.log_out ~fmt:"%s ->" pointer; + Printing.log_out ~fmt:" %s\n" (Sil.pvar_to_string var) in + Printing.log_out "POINTER VARS:\n"; + StringMap.iter print_pointer_var context.local_vars_pointer + + let add_pointer_var pointer var context = + Printing.log_out ~fmt:" ...Adding pointer '%s' " pointer; + Printing.log_out ~fmt:"to the map with variable '%s'\n%!" (Sil.pvar_to_string var); + context.local_vars_pointer <- StringMap.add pointer var context.local_vars_pointer + + let find_var_with_pointer context pointer = + try + StringMap.find pointer context.local_vars_pointer + with Not_found -> + (Printing.log_err ~fmt:" ...Variable for pointer %s not found!!\n%!" pointer); + print_pointer_vars context; + assert false + + let lookup_var_locals context procname var_name = + let stack = lookup_var_map context var_name in + let (var_name, typ, level) = Stack.top stack in + Printing.log_out ~fmt:" ...Variable %s found in locals!!\n%!" (Mangled.to_string var_name); + (Sil.mk_pvar var_name procname), typ + + let lookup_var context pointer var_name kind = + let procname = Cfg.Procdesc.get_proc_name context.procdesc in + if (kind = `Var) then + try + Some (fst (lookup_var_locals context procname var_name)) + with Stack.Empty -> + try + Some (fst (lookup_var_globals context procname var_name)) + with Not_found -> + if is_captured_var context var_name then + try (* if it's a captured variable we need to look at the parameters list*) + Some (fst (lookup_var_formals context procname var_name)) + with Not_found -> + Printing.log_err ~fmt:"Variable %s not found!!\n%!" var_name; + print_locals context; + None + else None + else if (kind = `ParmVar) then + try + Some (fst (lookup_var_formals context procname var_name)) + with Not_found -> + let list_to_string = list_to_string (fun (a, typ) -> a^":"^(Sil.typ_to_string typ)) in + Printing.log_err ~fmt:"Warning: Parameter %s not found!!\n%!" var_name; + Printing.log_err ~fmt:"Formals of procdesc %s" (Procname.to_string procname); + Printing.log_err ~fmt:" are %s\n%!" (list_to_string (Cfg.Procdesc.get_formals context.procdesc)); + Printing.print_failure_info pointer; + assert false + else if (kind = `Function || kind = `ImplicitParam) then ( + (* ImplicitParam are 'self' and '_cmd'. These are never defined but they can be referred to in the code. *) + Printing.log_err ~fmt:"Creating a variable for '%s' \n%!" var_name; + Some (Sil.mk_pvar (Mangled.from_string var_name) procname)) + else if (kind = `EnumConstant) then + (Printing.print_failure_info pointer; + assert false) + else (Printing.log_err ~fmt:"WARNING: In lookup_var kind %s not handled. Giving up!\n%!" (Clang_ast_j.string_of_decl_kind kind); + Printing.print_failure_info pointer; + assert false) + + let get_variable_name name = + Mangled.mangled name ((string_of_int(Block.depth ()))) + + let add_local_var context var_name typ pointer is_static = + Printing.log_out ~fmt:" ...Creating var %s" var_name; + Printing.log_out ~fmt:" with pointer %s\n" pointer; + if not (is_global_var context var_name) || is_static then + let var = get_variable_name var_name in + context.local_vars <- context.local_vars@[(var, typ, is_static)] ; + let stack = lookup_var_map context var_name in + let procname = Cfg.Procdesc.get_proc_name context.procdesc in + let pvar = Sil.mk_pvar var procname in + Stack.push (var, typ, Block.depth ()) stack; + context.local_vars_stack <- StringMap.add var_name stack context.local_vars_stack; + add_pointer_var pointer pvar context + + let reset_local_vars context = + context.local_vars <- [] + + let get_local_vars context = + context.local_vars + + let remove_top_level_local_vars context = + let remove_top var_name stack = + try + let (top_var, top_typ, top_level) = Stack.top stack in + if top_level == (Block.depth ()) then + (ignore (Stack.pop stack); + context.local_vars_stack <- + StringMap.add var_name stack context.local_vars_stack) + else () + with Stack.Empty -> () in + StringMap.iter remove_top context.local_vars_stack + + let enter_and_leave_scope context f lstmt = + Block.enter (); + f context lstmt; + remove_top_level_local_vars context; + Block.leave () + + let reset_block = Block.reset + +end + +let create_context tenv cg cfg procdesc ns curr_class is_objc_method cv is_instance = + { tenv = tenv; + cg = cg; + cfg = cfg; + procdesc = procdesc; + curr_class = curr_class; + is_callee_expression = false; + is_objc_method = is_objc_method; + is_instance = is_instance; + namespace = ns; + local_vars = []; + captured_vars = cv; + local_vars_stack = StringMap.empty; + local_vars_pointer = StringMap.empty + } + +let get_cfg context = context.cfg + +let get_cg context = context.cg + +let get_tenv context = context.tenv + +let get_procdesc context = context.procdesc + +let is_objc_method context = context.is_objc_method + +let get_curr_class context = context.curr_class + +let get_curr_class_name curr_class = + match curr_class with + | ContextCls (name, _, _) -> name + | ContextCategory (name, cls) -> cls + | ContextProtocol name -> name + | ContextNoCls -> assert false + +let curr_class_to_string curr_class = + match curr_class with + | ContextCls (name, _, _) -> ("class "^name) + | ContextCategory (name, cls) -> ("category "^name^" of class "^cls) + | ContextProtocol name -> ("protocol "^name) + | ContextNoCls -> "no class" + +let curr_class_compare curr_class1 curr_class2 = + match curr_class1, curr_class2 with + | ContextCls (name1, _, _), ContextCls (name2, _, _) -> + String.compare name1 name2 + | ContextCls (_, _, _), _ -> -1 + | _, ContextCls (_, _, _) -> 1 + | ContextCategory (name1, cls1), ContextCategory (name2, cls2) -> + Utils.pair_compare String.compare String.compare (name1, cls1) (name2, cls2) + | ContextCategory (_, _), _ -> -1 + | _, ContextCategory (_, _) -> 1 + | ContextProtocol name1, ContextProtocol name2 -> + String.compare name1 name2 + | ContextProtocol _, _ -> -1 + | _, ContextProtocol _ -> 1 + | ContextNoCls, ContextNoCls -> 0 + +let curr_class_equal curr_class1 curr_class2 = + curr_class_compare curr_class1 curr_class2 == 0 + +let curr_class_hash curr_class = + match curr_class with + | ContextCls (name, _, _) -> Hashtbl.hash name + | ContextCategory (name, cls) -> Hashtbl.hash (name, cls) + | ContextProtocol name -> Hashtbl.hash name + | ContextNoCls -> Hashtbl.hash "no class" + +let get_qt_curr_class curr_class = + (get_curr_class_name curr_class)^" *" + +let get_captured_vars context = context.captured_vars + + + diff --git a/infer/src/clang/cContext.mli b/infer/src/clang/cContext.mli new file mode 100644 index 000000000..53627009f --- /dev/null +++ b/infer/src/clang/cContext.mli @@ -0,0 +1,74 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Contains current class and current method to be translated as well as local variables, *) +(** and the cg, cfg, and tenv corresponding to the current file. *) + +type varMap +type pointerVarMap + +type curr_class = + | ContextCls of string * string option * string list + (*class name and name of (optional) super class , and a list of protocols *) + | ContextCategory of string * string (* category name and corresponding class *) + | ContextProtocol of string (* category name and corresponding class *) + | ContextNoCls + +type t = + { + tenv : Sil.tenv; + cg : Cg.t; + cfg : Cfg.cfg; + procdesc : Cfg.Procdesc.t; + is_objc_method : bool; + is_instance : bool; + curr_class: curr_class; + is_callee_expression : bool; + namespace: string option; (* contains the name of the namespace if we are in the scope of one*) + mutable local_vars : (Mangled.t * Sil.typ * bool) list; (* (name, type, is_static flag) *) + mutable captured_vars : (Mangled.t * Sil.typ * bool) list; (* (name, type, is_static flag) *) + mutable local_vars_stack : varMap; + mutable local_vars_pointer : pointerVarMap + } + +module LocalVars : +sig + val find_var_with_pointer : t -> string -> Sil.pvar + val lookup_var: t -> string -> string -> Clang_ast_t.decl_kind -> Sil.pvar option + val add_pointer_var : string -> Sil.pvar -> t -> unit + val enter_and_leave_scope : t -> (t -> 'a -> 'b) -> 'a -> unit + val add_local_var : t -> string -> Sil.typ -> string -> bool -> unit + val reset_block : unit -> unit + val add_pointer_var : string -> Sil.pvar -> t -> unit +end + +val get_procdesc : t -> Cfg.Procdesc.t + +val get_cfg : t -> Cfg.cfg + +val get_cg : t -> Cg.t + +val get_curr_class : t -> curr_class + +val get_curr_class_name : curr_class -> string + +val get_qt_curr_class : curr_class -> string + +val curr_class_to_string : curr_class -> string + +val curr_class_compare : curr_class -> curr_class -> int + +val curr_class_equal : curr_class -> curr_class -> bool + +val curr_class_hash : curr_class -> int + +val is_objc_method : t -> bool + +val get_tenv : t -> Sil.tenv + +val create_context : Sil.tenv -> Cg.t -> Cfg.cfg -> Cfg.Procdesc.t -> +string option -> curr_class -> bool -> (Mangled.t * Sil.typ * bool) list -> bool -> t + + diff --git a/infer/src/clang/cEnum_decl.ml b/infer/src/clang/cEnum_decl.ml new file mode 100644 index 000000000..4f20c4df7 --- /dev/null +++ b/infer/src/clang/cEnum_decl.ml @@ -0,0 +1,76 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Translate an enumeration declaration by adding it to the tenv and *) +(** translating the code and adding it to a fake procdesc *) + +open CFrontend_utils +open Clang_ast_t + +let create_empty_procdesc () = + let procname = Procname.from_string "__INFER_$GLOBAL_VAR_env" in + let open Cfg.Procdesc in + let proc_attributes = + { + Sil.access = Sil.Default; + Sil.exceptions = []; + Sil.is_abstract = false; + Sil.is_bridge_method = false; + Sil.is_objc_instance_method = false; + Sil.is_synthetic_method = false; + Sil.language = Sil.C_CPP; + Sil.method_annotation = Sil.method_annotation_empty; + } in + create { + cfg = Cfg.Node.create_cfg (); + name = procname; + is_defined = false; + ret_type = Sil.Tvoid; + formals = []; + locals = []; + captured = []; + loc = Sil.loc_none; + proc_attributes = proc_attributes; + } + +(* We will use global_procdesc for storing global variables. *) +(* Globals will be stored as locals in global_procdesc and they are added*) +(* when traversing the AST. *) +let global_procdesc = ref (create_empty_procdesc ()) + +let rec get_enum_constants context decl_list v = + match decl_list with + | [] -> [] + | EnumConstantDecl(decl_info, name, qual_type, enum_constant_decl_info) :: decl_list' -> + (match enum_constant_decl_info.Clang_ast_t.ecdi_init_expr with + | None -> Printing.log_out (" ...Defining Enum Constant ("^name^", "^(string_of_int v)); + (Mangled.from_string name, Sil.Cint (Sil.Int.of_int v)) + :: get_enum_constants context decl_list' (v + 1) + | Some stmt -> + let e = CGen_trans.CTransImpl.expression_trans context stmt + "WARNING: Expression in Enumeration constant not found\n" in + let const = (match Prop.exp_normalize_prop Prop.prop_emp e with + | Sil.Const c -> c + | _ -> (* This is a hack to avoid failing in some strange definition of Enum *) + Sil.Cint Sil.Int.zero) in + Printing.log_out ~fmt:" ...Defining Enum Constant ('%s', " name; + Printing.log_out ~fmt:"'%s')\n" (Sil.exp_to_string (Sil.Const const)); + (Mangled.from_string name, const) :: get_enum_constants context decl_list' v) + | _ -> assert false + +let enum_decl name tenv cfg cg namespace decl_list opt_type = + Printing.log_out ~fmt:"ADDING: EnumDecl '%s'\n" name; + let context' = + CContext.create_context tenv cg cfg !global_procdesc namespace CContext.ContextNoCls + false [] false in + let enum_constants = get_enum_constants context' decl_list 0 in + let name = (match opt_type with (* If the type is defined it's of the kind "enum name" and we take that.*) + | `Type s -> s + | `NoType -> assert false) in + (* Here we could give "enum "^name but I want to check that this the type is always defined *) + let typename = Sil.TN_enum (Mangled.from_string name) in + let typ = Sil.Tenum enum_constants in + Printing.log_out ~fmt:" TN_typename('%s')\n" (Sil.typename_to_string typename); + Sil.tenv_add tenv typename typ diff --git a/infer/src/clang/cEnum_decl.mli b/infer/src/clang/cEnum_decl.mli new file mode 100644 index 000000000..7bbf43e22 --- /dev/null +++ b/infer/src/clang/cEnum_decl.mli @@ -0,0 +1,10 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Translate an enumeration declaration by adding it to the tenv and *) +(** translating the code and adding it to a fake procdesc *) + +val enum_decl : string -> Sil.tenv -> Cfg.cfg -> Cg.t -> string option -> +Clang_ast_t.decl list -> Clang_ast_t.opt_type -> unit diff --git a/infer/src/clang/cField_decl.ml b/infer/src/clang/cField_decl.ml new file mode 100644 index 000000000..86ebe9105 --- /dev/null +++ b/infer/src/clang/cField_decl.ml @@ -0,0 +1,95 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Utility module to retrieve fields of structs of classes *) + +open Utils +open CFrontend_utils +open CFrontend_utils.General_utils +open Clang_ast_t + +module L = Logging + +let mk_class_field_name class_name field_name = + Ident.create_fieldname (Mangled.mangled field_name (class_name^"_"^field_name)) 0 + +let rec get_fields_super_classes tenv super_class = + Printing.log_out ~fmt:" ... Getting fields of superclass '%s'\n" (Sil.typename_to_string super_class); + match Sil.tenv_lookup tenv super_class with + | None -> [] + | Some Sil.Tstruct (fields, _, _, _, (Sil.Class, sc):: _, _, _) -> + let sc_fields = get_fields_super_classes tenv (Sil.TN_csu (Sil.Class, sc)) in + append_no_duplicates_fields fields sc_fields + | Some Sil.Tstruct (fields, _, _, _, _, _, _) -> fields + | Some _ -> [] + +let fields_superclass tenv interface_decl_info = + match interface_decl_info.Clang_ast_t.otdi_super with + | Some dr -> + (match dr.Clang_ast_t.dr_name with + | Some sc -> get_fields_super_classes tenv (CTypes.mk_classname sc) + | _ -> []) + | _ -> [] + +let get_field_www name_field fl = + let rec scan_fields nn ll = + match ll with + | [] -> [] + | (n, t, _):: ll' -> Printing.log_out ~fmt:">>>>>Searching for field '%s'." (Ident.fieldname_to_string n); + Printing.log_out ~fmt:" Seen '%s'.\n" nn; + if (Ident.fieldname_to_string n) = nn then + [(n, t)] + else scan_fields nn ll' in + CTrans_utils.extract_item_from_singleton (scan_fields name_field fl) + "WARNING: In MemberExpr there must be only one type defininf for the struct. Returning (NO_FIELD_NAME, Tvoid)\n" + (Ident.create_fieldname (Mangled.from_string "NO_FIELD_NAME") 0, Sil.Tvoid) + +let rec build_sil_field tenv class_name field_name qual_type prop_atts = + let fname = mk_class_field_name class_name field_name in + let typ = CTypes_decl.qual_type_to_sil_type tenv qual_type in + let item_annotations = match prop_atts with + | None -> Sil.item_annotation_empty + | Some atts -> [({ Sil.class_name = Config.property_attributes; Sil.parameters = atts }, true)] in + fname, typ, item_annotations + +(* From an ivar look for its property and if it finds it returns its attributes *) +let ivar_property curr_class ivar = + match ObjcProperty_decl.Property.find_property_name_from_ivar curr_class ivar with + | Some pname' -> + (Printing.log_out ~fmt: "Found property name from ivar: '%s'" pname'; + try + let _, atts, _, _, _, _ = ObjcProperty_decl.Property.find_property curr_class pname' in + let atts_str = list_map Clang_ast_j.string_of_property_attribute atts in + Some atts_str + with Not_found -> + Printing.log_out ~fmt: "Didn't find property for pname '%s'" pname'; + None) + | None -> Printing.log_out ~fmt: "Didn't find property for ivar '%s'" ivar; + None + +(* Given a list of declarations in an interface returns a list of fields *) +let rec get_fields tenv curr_class decl_list = + let class_name = CContext.get_curr_class_name curr_class in + match decl_list with + | [] -> [] + | ObjCIvarDecl(decl_info, field_name, qual_type, field_decl_info, obj_c_ivar_decl_info) :: decl_list' -> + let fields = get_fields tenv curr_class decl_list' in + (* Doing a post visit here. Adding Ivar after all the declaration have been visited so that *) + (* ivar names will be added in the property list. *) + Printing.log_out ~fmt:" ...Adding Instance Variable '%s' \n" field_name; + let prop_attributes = ivar_property curr_class field_name in + let (fname, typ, ia) = build_sil_field tenv class_name field_name qual_type prop_attributes in + Printing.log_out ~fmt:" ...Resulting sil field: (%s) with attributes:\n" ((Ident.fieldname_to_string fname) ^":"^(Sil.typ_to_string typ)); + list_iter (fun (ia', _) -> + list_iter (fun a -> Printing.log_out ~fmt: " '%s' \n" a) ia'.Sil.parameters) ia; + (fname, typ, ia):: fields + + | ObjCPropertyImplDecl(decl_info, property_impl_decl_info):: decl_list' -> + let property_fields_decl = + ObjcProperty_decl.prepare_dynamic_property curr_class decl_info property_impl_decl_info in + get_fields tenv curr_class (property_fields_decl @ decl_list') + + | (d : Clang_ast_t.decl):: decl_list' -> + get_fields tenv curr_class decl_list' diff --git a/infer/src/clang/cField_decl.mli b/infer/src/clang/cField_decl.mli new file mode 100644 index 000000000..28c5525e2 --- /dev/null +++ b/infer/src/clang/cField_decl.mli @@ -0,0 +1,14 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Utility module to retrieve fields of structs of classes *) + +val get_fields : Sil.tenv -> CContext.curr_class -> Clang_ast_t.decl list -> +(Ident.fieldname * Sil.typ * Sil.item_annotation) list + +val fields_superclass : Sil.tenv -> Clang_ast_t.obj_c_interface_decl_info -> +(Ident.fieldname * Sil.typ * Sil.item_annotation) list + +val mk_class_field_name : string -> string -> Ident.fieldname \ No newline at end of file diff --git a/infer/src/clang/cFrontend.ml b/infer/src/clang/cFrontend.ml new file mode 100644 index 000000000..769919f8c --- /dev/null +++ b/infer/src/clang/cFrontend.ml @@ -0,0 +1,144 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Translate one file into a cfg. Create a tenv, cg and cfg file for a source file *) +(** given its ast in json format. Translate the json file into a cfg by adding all *) +(** the type and class declarations to the tenv, adding all the functions and methods *) +(** declarations as procdescs to the cfg, and adding the control flow graph of all the *) +(** code of those functions and methods to the cfg *) + +module L = Logging + +open Utils +open CFrontend_utils +open CGen_trans +open Clang_ast_t + +(* Translate one global declaration *) +let rec translate_one_declaration tenv cg cfg namespace dec = + let ns_suffix = Ast_utils.namespace_to_string namespace in + let info = Clang_ast_proj.get_decl_tuple dec in + CLocation.update_curr_file info; + let source_range = info.Clang_ast_t.di_source_range in + let should_translate_enum = CLocation.should_translate_enum source_range in + match dec with + | FunctionDecl(di, name, qt, fdecl_info) -> + CMethod_declImpl.function_decl tenv cfg cg namespace false di name qt fdecl_info [] None CContext.ContextNoCls + | TypedefDecl (decl_info, name, opt_type, typedef_decl_info) -> + CTypes_decl.do_typedef_declaration tenv namespace + decl_info name opt_type typedef_decl_info + (* Currently C/C++ record decl treated in the same way *) + | CXXRecordDecl (decl_info, record_name, opt_type, decl_list, decl_context_info, record_decl_info) + | RecordDecl (decl_info, record_name, opt_type, decl_list, decl_context_info, record_decl_info) -> + CTypes_decl.do_record_declaration tenv namespace + decl_info record_name opt_type decl_list decl_context_info record_decl_info + + | VarDecl(decl_info, name, t, _) -> + CVar_decl.global_var_decl tenv namespace decl_info name t + + | ObjCInterfaceDecl(decl_info, name, decl_list, decl_context_info, obj_c_interface_decl_info) -> + let curr_class = + ObjcInterface_decl.interface_declaration tenv name decl_list obj_c_interface_decl_info in + CMethod_declImpl.process_methods tenv cg cfg curr_class namespace decl_list + + | ObjCProtocolDecl(decl_info, name, decl_list, decl_context_info, obj_c_protocol_decl_info) -> + let curr_class = ObjcProtocol_decl.protocol_decl tenv name decl_list in + CMethod_declImpl.process_methods tenv cg cfg curr_class namespace decl_list + + | ObjCCategoryDecl(decl_info, name, decl_list, decl_context_info, category_decl_info) -> + let curr_class = + ObjcCategory_decl.category_decl tenv name category_decl_info decl_list in + CMethod_declImpl.process_methods tenv cg cfg curr_class namespace decl_list + + | ObjCCategoryImplDecl(decl_info, name, decl_list, decl_context_info, category_impl_info) -> + let curr_class = + ObjcCategory_decl.category_impl_decl tenv name decl_info category_impl_info decl_list in + CMethod_declImpl.process_methods tenv cg cfg curr_class namespace decl_list + + | ObjCImplementationDecl(decl_info, class_name, decl_list, decl_context_info, idi) -> + let curr_class = + ObjcInterface_decl.interface_impl_declaration tenv class_name decl_list idi in + CMethod_declImpl.process_methods tenv cg cfg curr_class namespace decl_list + + | EnumDecl(decl_info, name, opt_type, decl_list, decl_context_info, enum_decl_info) + when should_translate_enum -> + CEnum_decl.enum_decl name tenv cfg cg namespace decl_list opt_type + + | LinkageSpecDecl(decl_info, decl_list, decl_context_info) -> + Printing.log_out "ADDING: LinkageSpecDecl decl list\n"; + list_iter (translate_one_declaration tenv cg cfg namespace) decl_list + | NamespaceDecl(decl_info, name, decl_list, decl_context_info, _) -> + let name = ns_suffix^name in + list_iter (translate_one_declaration tenv cg cfg (Some name)) decl_list + | EmptyDecl _ -> + Printing.log_out "Passing from EmptyDecl. Treated as skip\n"; + | dec -> + Printing.log_stats ~fmt:"\nWARNING: found Declaration %s skipped\n" (Ast_utils.string_of_decl dec) + +(** Preprocess declarations to create method signatures of function declarations. *) +let preprocess_one_declaration tenv cg cfg dec = + let info = Clang_ast_proj.get_decl_tuple dec in + CLocation.update_curr_file info; + match dec with + | FunctionDecl(di, name, qt, fdecl_info) -> + ignore (CMethod_declImpl.create_function_signature di fdecl_info name qt false None) + | _ -> () + +(* Translates a file by translating the ast into a cfg. *) +let compute_icfg tenv source_file ast = + match ast with + | TranslationUnitDecl(_, decl_list, _) -> + CFrontend_config.global_translation_unit_decls:= decl_list; + Printing.log_out "\n Start creating icfg\n"; + let cg = Cg.create () in + let cfg = Cfg.Node.create_cfg () in + list_iter (preprocess_one_declaration tenv cg cfg) decl_list; + list_iter (translate_one_declaration tenv cg cfg None) decl_list; + Printing.log_out "\n Finished creating icfg\n"; + (cg, cfg) + | _ -> assert false (* NOTE: Assumes that an AST alsways starts with a TranslationUnitDecl *) + +let init_global_state source_file = + Sil.curr_language := Sil.C_CPP; + DB.current_source := source_file; + DB.Results_dir.init (); + Ident.reset_name_generator (); + CMethod_signature.reset_map (); + CGlobal_vars.reset_map (); + CFrontend_config.global_translation_unit_decls := []; + ObjcProperty_decl.reset_property_table (); + CFrontend_utils.General_utils.reset_block_counter () + +let do_source_file source_file ast = + let tenv = Sil.create_tenv () in + CTypes_decl.add_predefined_types tenv; + init_global_state source_file; + CLocation.init_curr_source_file source_file; + Config.nLOC := FileLOC.file_get_loc (DB.source_file_to_string source_file); + Printing.log_out ~fmt:"\n Start building call/cfg graph for '%s'....\n" + (DB.source_file_to_string source_file); + let call_graph, cfg = compute_icfg tenv (DB.source_file_to_string source_file) ast in + Printing.log_out ~fmt:"\n End building call/cfg graph for '%s'.\n" + (DB.source_file_to_string source_file); + (* This part below is a boilerplate in every frontends. *) + (* This could be moved in the cfg_infer module *) + let source_dir = DB.source_dir_from_source_file !DB.current_source in + let tenv_file = DB.source_dir_get_internal_file source_dir ".tenv" in + let cfg_file = DB.source_dir_get_internal_file source_dir ".cfg" in + let cg_file = DB.source_dir_get_internal_file source_dir ".cg" in + Cfg.add_removetemps_instructions cfg; + Preanal.doit cfg tenv; + Cfg.add_abstraction_instructions cfg; + Cg.store_to_file cg_file call_graph; + Cfg.store_cfg_to_file cfg_file true cfg; + (*Logging.out "Tenv %a@." Sil.pp_tenv tenv;*) + (*Printing.print_tenv tenv;*) + (*Printing.print_procedures cfg; *) + Sil.store_tenv_to_file tenv_file tenv; + if !CFrontend_config.stats_mode then Cfg.check_cfg_connectedness cfg; + if !CFrontend_config.stats_mode || !CFrontend_config.debug_mode || !CFrontend_config.testing_mode then + (Dotty.print_icfg_dotty cfg []; + Cg.save_call_graph_dotty None Specs.get_specs call_graph) + diff --git a/infer/src/clang/cFrontend.mli b/infer/src/clang/cFrontend.mli new file mode 100644 index 000000000..e1a16789f --- /dev/null +++ b/infer/src/clang/cFrontend.mli @@ -0,0 +1,13 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Translate one file into a cfg. Create a tenv, cg and cfg file for a source file *) +(** given its ast in json format. Translate the json file into a cfg by adding all *) +(** the type and class declarations to the tenv, adding all the functions and methods *) +(** declarations as procdescs to the cfg, and adding the control flow graph of all the *) +(** code of those functions and methods to the cfg *) + + +val do_source_file : DB.source_file -> Clang_ast_t.decl -> unit \ No newline at end of file diff --git a/infer/src/clang/cFrontend_config.ml b/infer/src/clang/cFrontend_config.ml new file mode 100644 index 000000000..ddbf7bd4b --- /dev/null +++ b/infer/src/clang/cFrontend_config.ml @@ -0,0 +1,117 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Module that contains constants and variables used in the frontend *) + +let no_translate_libs = ref true + +let testing_mode = ref false + +let array_with_objects_count_m = "arrayWithObjects:count:" + +let dict_with_objects_and_keys_m = "dictionaryWithObjectsAndKeys:" + +let string_with_utf8_m = "stringWithUTF8String:" + +let nsstring_cl = "NSString" + +let nsobject_cl = "NSObject" + +let nsautorelease_pool_cl = "NSAutoreleasePool" + +let id_cl = "id" + +let self = "self" + +let alloc = "alloc" + +let malloc = "malloc" + +let static = "static" + +let source_file : string option ref = ref None + +let ast_file : string option ref = ref None + +let json = ref "" + +let debug_mode = ref false + +let stats_mode = ref false + +let models_mode = ref false + +type lang = + | C + | CPP + | OBJC + | OBJCPP + +let language = ref OBJC (* Default is objc, since it's the default for clang (at least in Mac OS) *) + +let lang_from_string lang_string = + let lang = + if lang_string = "c" then C + else if lang_string = "objective-c" then OBJC + else if lang_string = "c++" then CPP + else if lang_string = "objective-c++" then OBJCPP + else assert false in + language := lang + +let emtpy_name_category ="EMPTY_NAME_CATEGORY_FOR_" + +let objc_object = "objc_object" + +let objc_class = "objc_class" + +let class_type = "Class" + +let global_translation_unit_decls : Clang_ast_t.decl list ref = ref [] + +let retain = "retain" + +let release = "release" + +let drain = "drain" + +let autorelease = "autorelease" + +let copy = "copy" + +let mutableCopy = "mutableCopy" + +let new_str = "new" + +let init = "init" + +let temp_var = "infer" + +let pointer_prefix = "internal" + +let void = "void" + +let class_method = "class" + +let cf_non_null_alloc ="__cf_non_null_alloc" + +let cf_alloc ="__cf_alloc" + +let cf_bridging_release = "CFBridgingRelease" + +let cf_bridging_retain = "CFBridgingRetain" + +let cf_autorelease = "CFAutorelease" + +let builtin_expect = "__builtin_expect" + +let assert_fail = "__assert_fail" + +let assert_rtn = "__assert_rtn" + +let handleFailureInMethod = "handleFailureInMethod:object:file:lineNumber:description:" + +let handleFailureInFunction = "handleFailureInFunction:file:lineNumber:description:" + +let fbAssertWithSignalAndLogFunctionHelper = "FBAssertWithSignalAndLogFunctionHelper" diff --git a/infer/src/clang/cFrontend_config.mli b/infer/src/clang/cFrontend_config.mli new file mode 100644 index 000000000..8e9a03948 --- /dev/null +++ b/infer/src/clang/cFrontend_config.mli @@ -0,0 +1,114 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Module that contains constants and variables used in the frontend *) + +val global_translation_unit_decls : Clang_ast_t.decl list ref + +(** arguments of InferClang *) + +val debug_mode : bool ref + +val stats_mode : bool ref + +val models_mode : bool ref + +val source_file : string option ref + +type lang = + | C + | CPP + | OBJC + | OBJCPP + +val lang_from_string : string -> unit + +val language : lang ref + +val ast_file : string option ref + +val no_translate_libs : bool ref + +val testing_mode : bool ref + +(** constants *) + +val json : string ref + +val objc_object : string + +val id_cl : string + +val self : string + +val nsstring_cl : string + +val nsobject_cl : string + +val nsautorelease_pool_cl : string + +val string_with_utf8_m : string + +val alloc : string + +val malloc : string + +val static : string + +val array_with_objects_count_m : string + +val dict_with_objects_and_keys_m : string + +val emtpy_name_category : string + +val objc_class : string + +val class_type : string + +val retain : string + +val release : string + +val drain : string + +val autorelease : string + +val copy : string + +val mutableCopy : string + +val new_str : string + +val init : string + +val temp_var : string + +val pointer_prefix : string + +val void : string + +val class_method : string + +val cf_non_null_alloc : string + +val cf_alloc : string + +val cf_bridging_release : string + +val cf_bridging_retain : string + +val cf_autorelease : string + +val builtin_expect : string + +val fbAssertWithSignalAndLogFunctionHelper : string + +val assert_fail : string + +val assert_rtn : string + +val handleFailureInMethod : string + +val handleFailureInFunction : string diff --git a/infer/src/clang/cFrontend_utils.ml b/infer/src/clang/cFrontend_utils.ml new file mode 100644 index 000000000..760e76213 --- /dev/null +++ b/infer/src/clang/cFrontend_utils.ml @@ -0,0 +1,362 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Module for utility functions for the whole frontend. Includes functions for printing, *) +(** for transformations of ast nodes and general utility functions such as functions on lists *) + +open Utils +open Clang_ast_t + +module L = Logging +module F = Format + +module Printing = +struct + let log_out ?fmt s = + if !CFrontend_config.debug_mode then + match fmt with + | Some fmt' -> + Format.printf fmt' s + | None -> Format.printf "%s" s + + let log_err ?fmt s = + if !CFrontend_config.debug_mode then + match fmt with + | Some fmt' -> + Format.eprintf fmt' s + | None -> Format.eprintf "%s" s + + let log_stats ?fmt s = + if !CFrontend_config.stats_mode || + !CFrontend_config.debug_mode then + match fmt with + | Some fmt' -> + Format.printf fmt' s + | None -> Format.eprintf "%s" s + + let print_tenv tenv = + Sil.tenv_iter (fun typname typ -> + match typname with + | Sil.TN_csu (Sil.Class, _) | Sil.TN_csu (Sil.Protocol, _) -> + (match typ with (Sil.Tstruct (fields, static_fields, _, cls, super_classes, methods, iann)) -> + (print_endline ( + (Sil.typename_to_string typname)^"\n"^ + "---> superclass and protocols "^(list_to_string (fun (csu, x) -> + let nsu = Sil.TN_csu (csu, x) in + "\t"^(Sil.typename_to_string nsu)^"\n") super_classes)^ + "---> methods "^(list_to_string (fun x ->"\t"^(Procname.to_string x)^"\n") methods)^" "^ + "\t---> static fields "^(list_to_string (fun (fieldname, typ, _) -> + "\t "^(Ident.fieldname_to_string fieldname)^" "^ + (Sil.typ_to_string typ)^"\n") static_fields)^ + "\t---> fields "^(list_to_string (fun (fieldname, typ, _) -> + "\t "^(Ident.fieldname_to_string fieldname)^" "^ + (Sil.typ_to_string typ)^"\n") fields + ) + ) + ) + | _ -> ()) + | _ -> () + ) tenv + + let print_tenv_struct_unions tenv = + Sil.tenv_iter (fun typname typ -> + match typname with + | Sil.TN_csu (Sil.Struct, _) | Sil.TN_csu (Sil.Union, _) -> + (match typ with + | (Sil.Tstruct (fields, static_fields, _, cls, super_classes, methods, iann)) -> + (print_endline ( + (Sil.typename_to_string typname)^"\n"^ + "\t---> fields "^(list_to_string (fun (fieldname, typ, _) -> + match typ with + | Sil.Tvar tname -> "tvar"^(Sil.typename_to_string tname) + | Sil.Tstruct (_, _, _, _, _, _, _) | _ -> + "\t struct "^(Ident.fieldname_to_string fieldname)^" "^ + (Sil.typ_to_string typ)^"\n") fields + ) + ) + ) + | _ -> ()) + | Sil.TN_typedef typname -> + print_endline ((Mangled.to_string typname)^"-->"^(Sil.typ_to_string typ)) + | _ -> () + ) tenv + + let print_procedures cfg = + let procs = Cfg.get_all_procs cfg in + print_endline + (list_to_string (fun pdesc -> + let pname = Cfg.Procdesc.get_proc_name pdesc in + "name> "^ + (Procname.to_string pname) ^ + " defined? " ^ (string_of_bool (Cfg.Procdesc.is_defined pdesc)) ^ "\n") + procs) + + let print_failure_info pointer = + L.err "AST Element> %s IN FILE> %s @.@." pointer !CFrontend_config.json + + let print_nodes nodes = + list_iter (fun node -> print_endline (Cfg.Node.get_description Utils.pe_text node)) nodes + + let instrs_to_string instrs = + let pp fmt () = Format.fprintf fmt "%a" (Sil.pp_instr_list Utils.pe_text) instrs in + pp_to_string pp () + +end + +module Ast_utils = +struct + + let string_of_decl decl = + let name = Clang_ast_proj.get_decl_kind_string decl in + let info = Clang_ast_proj.get_decl_tuple decl in + "<\"" ^ name ^ "\"> '" ^ info.Clang_ast_t.di_pointer ^ "'" + + let string_of_unary_operator_kind = function + | `PostInc -> "PostInc" + | `PostDec -> "PostDec" + | `PreInc -> "PreInc" + | `PreDec -> "PreDec" + | `AddrOf -> "AddrOf" + | `Deref -> "Deref" + | `Plus -> "Plus" + | `Minus -> "Minus" + | `Not -> "Not" + | `LNot -> "LNot" + | `Real -> "Real" + | `Imag -> "Imag" + | `Extension -> "Extension" + + let string_of_stmt stmt = + let name = Clang_ast_proj.get_stmt_kind_string stmt in + let info, _ = Clang_ast_proj.get_stmt_tuple stmt in + "<\"" ^ name ^ "\"> '" ^ info.Clang_ast_t.si_pointer ^ "'" + + let get_stmts_from_stmt stmt = + match stmt with + | OpaqueValueExpr(_, lstmt, _, opaque_value_expr_info) -> + (match opaque_value_expr_info.Clang_ast_t.ovei_source_expr with + | Some stmt -> lstmt@[stmt] + | _ -> lstmt) + (* given that this has not been translated, looking up for variables *) + (* inside leads to inconsistencies *) + | ObjCAtCatchStmt (stmt_info, stmt_list, obj_c_message_expr_kind) -> + [] + | _ -> snd (Clang_ast_proj.get_stmt_tuple stmt) + + let namespace_to_string namespace = + match namespace with + | None -> "" + | Some ns when ns ="" -> "" + | Some ns -> ns^"::" + + let property_name property_impl_decl_info = + let no_property_name = "WARNING_NO_PROPERTY_NAME" in + match property_impl_decl_info.Clang_ast_t.opidi_property_decl with + | Some decl_ref -> + (match decl_ref.Clang_ast_t.dr_name with + | Some n -> n + | _ -> no_property_name) + | None -> no_property_name + + let property_attribute_compare att1 att2 = + match att1, att2 with + `Readonly, `Readonly -> 0 + | `Readonly, _ -> -1 + | _, `Readonly -> 1 + | `Assign, `Assign -> 0 + | `Assign, _ -> -1 + | _, `Assign -> 1 + | `Readwrite, `Readwrite -> 0 + | `Readwrite, _ -> -1 + | _, `Readwrite -> 1 + | `Retain, `Retain -> 0 + | `Retain, _ -> -1 + | _, `Retain -> 1 + | `Copy, `Copy -> 0 + | `Copy, _ -> -1 + | _, `Copy -> 1 + | `Nonatomic, `Nonatomic -> 0 + | `Nonatomic, _ -> -1 + | _, `Nonatomic -> 1 + | `Atomic, `Atomic -> 0 + | `Atomic, _ -> 1 + | _, `Atomic -> 1 + | `Weak, `Weak -> 0 + | `Weak, _ -> -1 + | _, `Weak -> 1 + | `Strong, `Strong -> 0 + | `Strong, _ -> -1 + | _, `Strong -> 1 + | `Unsafe_unretained, `Unsafe_unretained -> 0 + | `Unsafe_unretained, _ -> -1 + | _, `Unsafe_unretained -> 1 + | `Getter _, `Getter _ -> 0 + | `Getter _, _ -> -1 + | _, `Getter _ -> 1 + | `Setter _, `Setter _ -> 0 + + let property_attribute_eq att1 att2 = + property_attribute_compare att1 att2 = 0 + + let get_memory_management_attributes () = + [`Assign; `Retain; `Copy; `Weak; `Strong; `Unsafe_unretained] + + let is_retain attribute_opt = + match attribute_opt with + | Some attribute -> + attribute = `Retain || attribute = `Strong + | _ -> false + + let is_copy attribute_opt = + match attribute_opt with + | Some attribute -> + attribute = `Copy + | _ -> false + + let rec getter_attribute_opt attributes = + match attributes with + | [] -> None + | attr:: rest -> + match attr with + | `Getter getter -> getter.Clang_ast_t.dr_name + | _ -> (getter_attribute_opt rest) + + let rec setter_attribute_opt attributes = + match attributes with + | [] -> None + | attr:: rest -> + match attr with + | `Setter setter -> setter.Clang_ast_t.dr_name + | _ -> (setter_attribute_opt rest) + + let pointer_counter = ref 0 + + let get_fresh_pointer () = + pointer_counter := !pointer_counter + 1; + CFrontend_config.pointer_prefix^(string_of_int (!pointer_counter)) + +let type_from_unary_expr_or_type_trait_expr_info info = + match info.uttei_qual_type with + | Some qt -> Some qt + | None -> None + +end + +(* Global counter for anonymous block*) +let block_counter = ref 0 + +(* Returns a fresh index for a new anonymous block *) +let get_fresh_block_index () = + block_counter := !block_counter +1; + !block_counter + +module General_utils = +struct + + let rec swap_elements_list l = + match l with + | el1:: el2:: rest -> + el2:: el1:: (swap_elements_list rest) + | [] -> [] + | _ -> assert false + + let rec string_from_list l = + match l with + | [] -> "" + | [item] -> item + | item:: l' -> item^" "^(string_from_list l') + + let get_fun_body fdecl_info = fdecl_info.Clang_ast_t.fdi_body + + let rec append_no_duplicates eq list1 list2 = + match list2 with + | el:: rest2 -> + if (list_mem eq el list1) then + (append_no_duplicates eq list1 rest2) + else (append_no_duplicates eq list1 rest2)@[el] + | [] -> list1 + + let append_no_duplicates_csu list1 list2 = + append_no_duplicates Sil.csu_name_equal list1 list2 + + let append_no_duplicates_methods list1 list2 = + append_no_duplicates Procname.equal list1 list2 + + let append_no_duplicated_vars list1 list2 = + let eq (m1, t1) (m2, t2) = (Mangled.equal m1 m2) && (Sil.typ_equal t1 t2) in + append_no_duplicates eq list1 list2 + + let append_no_duplicated_pvars list1 list2 = + let eq (e1, t1) (e2, t2) = (Sil.exp_equal e1 e2) && (Sil.typ_equal t1 t2) in + append_no_duplicates eq list1 list2 + + let append_no_duplicates_fields list1 list2 = + let field_eq (n1, t1, a1) (n2, t2, a2) = + match Ident.fieldname_equal n1 n2, Sil.typ_equal t1 t2, Sil.item_annotation_compare a1 a2 with + | true, true, _ -> true + | _, _, _ -> false in + append_no_duplicates field_eq list1 list2 + + let rec collect_list_tuples l (a, a1, b, c, d) = + match l with + | [] -> (a, a1, b, c, d) + | (a', a1', b', c', d'):: l' -> collect_list_tuples l' (a@a', a1@a1', b@b', c@c', d@d') + + let is_static_var var_decl_info = + match var_decl_info.Clang_ast_t.vdi_storage_class with + | Some sc -> sc = CFrontend_config.static + | _ -> false + +let block_procname_with_index defining_proc i = + Config.anonymous_block_prefix^(Procname.to_string defining_proc)^Config.anonymous_block_num_sep^(string_of_int i) + + (* Makes a fresh name for a block defined inside the defining procedure.*) + (* It updates the global block_counter *) + let mk_fresh_block_procname defining_proc = + let name = block_procname_with_index defining_proc (get_fresh_block_index ()) in + Procname.mangled_objc_block name + + (* Returns the next fresh name for a block defined inside the defining procedure *) + (* It does not update the global block_counter *) + let get_next_block_pvar defining_proc = + let name = block_procname_with_index defining_proc (!block_counter +1) in + Sil.mk_pvar (Mangled.from_string (CFrontend_config.temp_var^"_"^name)) defining_proc + + (* Reset block counter *) + let reset_block_counter () = + block_counter := 0 + + let mk_function_decl_info_from_block block_decl_info = + { + Clang_ast_t.fdi_storage_class = None; + Clang_ast_t.fdi_is_inline = true; (* This value should not matter as we don't use it*) + Clang_ast_t.fdi_is_virtual = false; (* This value should not matter as we don't use it*) + Clang_ast_t.fdi_is_module_private = true; (* This value should not matter as we don't use it*) + Clang_ast_t.fdi_is_pure = false; (* This value should not matter as we don't use it*) + Clang_ast_t.fdi_is_delete_as_written = false; (* This value should not matter as we don't use it*) + Clang_ast_t.fdi_decls_in_prototype_scope =[]; + Clang_ast_t.fdi_parameters = block_decl_info.Clang_ast_t.bdi_parameters; + Clang_ast_t.fdi_cxx_ctor_initializers = []; + Clang_ast_t.fdi_body = block_decl_info.Clang_ast_t.bdi_body; + } + + let rec zip xs ys = + match xs, ys with + | [], _ + | _, [] -> [] + | x :: xs, y :: ys -> (x, y) :: zip xs ys + + let list_range i j = + let rec aux n acc = + if n < i then acc else aux (n -1) (n :: acc) + in aux j [] ;; + + let replicate n el = list_map (fun i -> el) (list_range 0 (n -1)) + +end + + + + diff --git a/infer/src/clang/cFrontend_utils.mli b/infer/src/clang/cFrontend_utils.mli new file mode 100644 index 000000000..c054a3d86 --- /dev/null +++ b/infer/src/clang/cFrontend_utils.mli @@ -0,0 +1,103 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Module for utility functions for the whole frontend. Includes functions for printing, *) +(** for transformations of ast nodes and general utility functions such as functions on lists *) +open Clang_ast_t + +module Printing : +sig + + val log_out : ?fmt: (string -> unit, Format.formatter, unit) format -> string -> unit + + val log_err : ?fmt: (string -> unit, Format.formatter, unit) format -> string -> unit + + val log_stats : ?fmt: (string -> unit, Format.formatter, unit) format -> string -> unit + + val print_failure_info : string -> unit + + val print_tenv : Sil.tenv -> unit + + val print_tenv_struct_unions : Sil.tenv -> unit + + val print_procedures : Cfg.cfg -> unit + + val print_nodes : Cfg.Node.t list -> unit + + val instrs_to_string : Sil.instr list -> string +end + +module Ast_utils : +sig + val namespace_to_string : string option -> string + + val string_of_stmt : Clang_ast_t.stmt -> string + + val get_stmts_from_stmt : Clang_ast_t.stmt -> Clang_ast_t.stmt list + + val string_of_decl : Clang_ast_t.decl -> string + + val string_of_unary_operator_kind : Clang_ast_t.unary_operator_kind -> string + + val property_name : Clang_ast_t.obj_c_property_impl_decl_info -> string + + val property_attribute_compare : property_attribute -> property_attribute -> int + + val property_attribute_eq : property_attribute -> property_attribute -> bool + + val getter_attribute_opt : property_attribute list -> string option + + val setter_attribute_opt : property_attribute list -> string option + + val get_memory_management_attributes : unit -> Clang_ast_t.property_attribute list + + val is_retain : Clang_ast_t.property_attribute option -> bool + + val is_copy : Clang_ast_t.property_attribute option -> bool + + val get_fresh_pointer : unit -> string + + val type_from_unary_expr_or_type_trait_expr_info : + Clang_ast_t.unary_expr_or_type_trait_expr_info -> Clang_ast_t.qual_type option + +end + +module General_utils : +sig + val string_from_list : string list -> string + + val append_no_duplicates_fields : (Ident.fieldname * Sil.typ * Sil.item_annotation) list -> + (Ident.fieldname * Sil.typ * Sil.item_annotation) list -> (Ident.fieldname * Sil.typ * Sil.item_annotation) list + + val append_no_duplicates_csu : (Sil.csu * Mangled.t) list -> (Sil.csu * Mangled.t) list -> (Sil.csu * Mangled.t) list + + val append_no_duplicates_methods : Procname.t list -> Procname.t list -> Procname.t list + + val append_no_duplicated_vars : (Mangled.t * Sil.typ) list -> (Mangled.t * Sil.typ) list -> (Mangled.t * Sil.typ) list + + val append_no_duplicated_pvars : (Sil.exp * Sil.typ) list -> (Sil.exp * Sil.typ) list -> (Sil.exp * Sil.typ) list + + val collect_list_tuples : ('a list * 'b list * 'c list * 'd list * 'e list) list -> + 'a list * 'b list * 'c list * 'd list * 'e list -> 'a list * 'b list * 'c list * 'd list * 'e list + + val swap_elements_list : 'a list -> 'a list + + val is_static_var : Clang_ast_t.var_decl_info -> bool + + val mk_fresh_block_procname : Procname.t -> Procname.t + + val get_next_block_pvar : Procname.t -> Sil.pvar + + val reset_block_counter : unit -> unit + + val mk_function_decl_info_from_block : Clang_ast_t.block_decl_info -> Clang_ast_t.function_decl_info + + val zip: 'a list -> 'b list -> ('a * 'b) list + + val list_range: int -> int -> int list + + val replicate: int -> 'a -> 'a list + +end diff --git a/infer/src/clang/cGen_trans.ml b/infer/src/clang/cGen_trans.ml new file mode 100644 index 000000000..001097917 --- /dev/null +++ b/infer/src/clang/cGen_trans.ml @@ -0,0 +1,10 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +module rec CTransImpl : CTrans.CTrans = + CTrans.CTrans_funct(CMethod_declImpl) + +and CMethod_declImpl : CMethod_decl.CMethod_decl = + CMethod_decl.CMethod_decl_funct(CTransImpl) \ No newline at end of file diff --git a/infer/src/clang/cGlobal_vars.ml b/infer/src/clang/cGlobal_vars.ml new file mode 100644 index 000000000..21c94f912 --- /dev/null +++ b/infer/src/clang/cGlobal_vars.ml @@ -0,0 +1,53 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +open CFrontend_utils +module L = Logging + +type t = { + _name : Sil.pvar; + _type : Sil.typ +} + +let var_get_name var = + var._name + +let var_get_typ var = + var._type + +module MangledMap = Map.Make (struct + type t = Mangled.t + let compare = Mangled.compare end) + +type varMap = t MangledMap.t + +let varMap = ref MangledMap.empty + +let make_var name typ = + { _name = name; + _type = typ } + +let add name typ = + let name = (Mangled.from_string name) in + let pvar = Sil.mk_pvar_global name in + Printing.log_out ~fmt:"Adding global variable %s !!\n%!" (Sil.pvar_to_string pvar); + let var_el = make_var pvar typ in + varMap := MangledMap.add name var_el !varMap + +let find var = + MangledMap.find var !varMap + +let reset_map () = + varMap := MangledMap.empty + +let print_map () = + let print_item key value = + L.out "%a ->%a:%a@." + Mangled.pp key + (Sil.pp_pvar Utils.pe_text) value._name + (Sil.pp_typ_full Utils.pe_text) value._type in + if !CFrontend_config.debug_mode then + (L.out "GLOBAL VARS:@."; + MangledMap.iter print_item !varMap) diff --git a/infer/src/clang/cGlobal_vars.mli b/infer/src/clang/cGlobal_vars.mli new file mode 100644 index 000000000..429d50ee2 --- /dev/null +++ b/infer/src/clang/cGlobal_vars.mli @@ -0,0 +1,18 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +type t + +val add : string -> Sil.typ -> unit + +val find : Mangled.t -> t + +val reset_map : unit -> unit + +val var_get_name : t -> Sil.pvar + +val var_get_typ : t -> Sil.typ + +val print_map : unit -> unit diff --git a/infer/src/clang/cLocation.ml b/infer/src/clang/cLocation.ml new file mode 100644 index 000000000..99bf3e877 --- /dev/null +++ b/infer/src/clang/cLocation.ml @@ -0,0 +1,130 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Module for function to retrieve the location (file, line, etc) of instructions *) + +open CFrontend_utils +open Utils + +(* The file passed as an argument to InferClang *) +let current_source_file = ref DB.source_file_empty + +(* Inside the json there may be code or type definitions from other files *) +(* than the one passed as an argument. That current file in the translation is saved*) +(* in this variable. *) +let curr_file = ref DB.source_file_empty + +let init_curr_source_file source_file = + current_source_file := source_file + +let source_file_from_path path = + let path = Utils.filename_to_absolute path in + match !Config.project_root with + | Some root -> + (try + DB.rel_source_file_from_abs_path root path + with DB.Path_not_prefix_root -> + DB.source_file_from_string path) + | None -> + if (Filename.is_relative path) then + (Logging.out + "ERROR: Path %s is relative. Please pass either a project root or an absolute path in the -c argument.@." + path; + exit(1)) + else (DB.source_file_from_string path) + +let choose_sloc sloc1 sloc2 prefer_first = + let sloc_bad sloc = + match sloc.Clang_ast_t.sl_file with + | Some f when not (DB.source_file_equal (source_file_from_path f) !curr_file) -> + true + | _ -> false in + if sloc_bad sloc1 then sloc2 + else if prefer_first then sloc1 else sloc2 + +let choose_sloc_to_update_curr_file sloc1 sloc2 = + let sloc_curr_file sloc = + match sloc.Clang_ast_t.sl_file with + | Some f when DB.source_file_equal (source_file_from_path f) !current_source_file -> + true + | _ -> false in + if sloc_curr_file sloc2 then sloc2 + else sloc1 + +let update_curr_file di = + match di.Clang_ast_t.di_source_range with (loc_start, loc_end) -> + let loc = choose_sloc_to_update_curr_file loc_start loc_end in + (match loc.Clang_ast_t.sl_file with + | Some f -> curr_file := source_file_from_path f + | None -> ()) + +let clang_to_sil_location clang_loc parent_line_number procdesc_opt = + let line = match clang_loc.Clang_ast_t.sl_line with + | Some l -> l + | None -> parent_line_number in + let col = match clang_loc.Clang_ast_t.sl_column with + | Some c -> c + | None -> -1 in + let file, nLOC = + match procdesc_opt with + | Some procdesc -> + let proc_loc = Cfg.Procdesc.get_loc procdesc in + if (DB.source_file_equal proc_loc.Sil.file DB.source_file_empty) then + !curr_file, !Config.nLOC + else proc_loc.Sil.file, proc_loc.Sil.nLOC + | None -> + match clang_loc.Clang_ast_t.sl_file with + | Some f -> + let file_db = source_file_from_path f in + let nloc = + if (DB.source_file_equal file_db !current_source_file) then + !Config.nLOC + else -1 in + file_db, nloc + | None -> !curr_file, !Config.nLOC in + { Sil.line = line; Sil.col = col; Sil.file = file; Sil.nLOC = nLOC } + +let should_translate_lib source_range = + if !CFrontend_config.no_translate_libs then + match source_range with (loc_start, loc_end) -> + let loc_start = choose_sloc_to_update_curr_file loc_start loc_end in + let loc = clang_to_sil_location loc_start (-1) None in + DB.source_file_equal loc.Sil.file !DB.current_source + else true + +let should_translate_enum source_range = + if !CFrontend_config.testing_mode then + match source_range with (loc_start, loc_end) -> + let loc_start = choose_sloc_to_update_curr_file loc_start loc_end in + let loc = clang_to_sil_location loc_start (-1) None in + DB.source_file_equal loc.Sil.file !DB.current_source + else true + +let get_sil_location_from_range source_range prefer_first = + match source_range with (sloc1, sloc2) -> + let sloc = choose_sloc sloc1 sloc2 prefer_first in + clang_to_sil_location sloc (-1) None + +let get_sil_location stmt_info parent_line_number context = + match stmt_info.Clang_ast_t.si_source_range with (sloc1, sloc2) -> + let sloc = choose_sloc sloc1 sloc2 true in + clang_to_sil_location sloc parent_line_number (Some (CContext.get_procdesc context)) + +let get_line stmt_info line_number = + match stmt_info.Clang_ast_t.si_source_range with + | (sloc1, sloc2) -> + let sloc = choose_sloc sloc1 sloc2 true in + (match sloc.Clang_ast_t.sl_line with + | Some l -> l + | None -> line_number) + +let check_source_file source_file = + let extensions_allowed = [".m"; ".mm"; ".c"; ".cc"; ".cpp"; ".h"] in + let allowed = list_exists (fun ext -> Filename.check_suffix source_file ext) extensions_allowed in + if not allowed then + (Printing.log_stats + ("\nThe source file "^source_file^ + " should end with "^(Utils.list_to_string (fun x -> x) extensions_allowed)^"\n\n"); + assert false) diff --git a/infer/src/clang/cLocation.mli b/infer/src/clang/cLocation.mli new file mode 100644 index 000000000..3fe5edf79 --- /dev/null +++ b/infer/src/clang/cLocation.mli @@ -0,0 +1,27 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Module for function to retrieve the location (file, line, etc) of instructions *) + +val clang_to_sil_location : Clang_ast_t.source_location -> int -> Cfg.Procdesc.t option -> +Sil.location + +val get_sil_location : Clang_ast_t.stmt_info -> int -> CContext.t -> Sil.location + +val get_line : Clang_ast_t.stmt_info -> int -> int + +val should_translate_lib : Clang_ast_t.source_range -> bool + +val should_translate_enum : Clang_ast_t.source_range -> bool + +val update_curr_file : Clang_ast_t.decl_info -> unit + +val init_curr_source_file : DB.source_file -> unit + +val check_source_file : string -> unit + +val source_file_from_path : string -> DB.source_file + +val get_sil_location_from_range : Clang_ast_t.source_range -> bool -> Sil.location diff --git a/infer/src/clang/cMain.ml b/infer/src/clang/cMain.ml new file mode 100644 index 000000000..684c38c3a --- /dev/null +++ b/infer/src/clang/cMain.ml @@ -0,0 +1,120 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(* Take as input an ast file and a C or ObjectiveC file such that the ast file +corresponds to the compilation of the C file with clang. +Parse the ast file into a data structure and translates it into a cfg. *) + +module L = Logging + +open Clang_ast_j +open CFrontend_config +open CFrontend_utils + +let arg_desc = + let base_arg = + let options_to_keep = ["-results_dir"] in + Config.dotty_cfg_libs := false; (* default behavior for this frontend *) + let filter arg_desc = + List.filter (fun desc -> let (option_name, _, _, _) = desc in List.mem option_name options_to_keep) arg_desc in + let desc = + (filter Utils.base_arg_desc) @ + [ + "-c", + Arg.String (fun cfile -> source_file := Some cfile), + Some "cfile", + "C File to translate"; + "-x", + Arg.String (fun lang -> CFrontend_config.lang_from_string lang), + Some "cfile", + "Language (c, objective-c, c++, objc-++)"; + "-ast", + Arg.String (fun file -> ast_file := Some file), + Some "file", + "AST file for the translation"; + "-dotty_cfg_libs", + Arg.Unit (fun _ -> Config.dotty_cfg_libs := true), + None, + "Prints the cfg of the code coming from the libraries"; + "-no_headers", + Arg.Unit (fun _ -> CFrontend_config.no_translate_libs := true), + None, + "Do not translate code in header files (default)"; + "-headers", + Arg.Unit (fun _ -> CFrontend_config.no_translate_libs := false), + None, + "Translate code in header files"; + "-testing_mode", + Arg.Unit (fun _ -> CFrontend_config.testing_mode := true), + None, + "Mode for testing, where no libraries are translated, including enums defined in the libraries"; + "-debug", + Arg.Unit (fun _ -> CFrontend_config.debug_mode := true), + None, + "Enables debug mode"; + "-stats", + Arg.Unit (fun _ -> CFrontend_config.stats_mode := true), + None, + "Enables stats mode"; + "-project_root", + Arg.String (fun s -> + Config.project_root := Some (Utils.filename_to_absolute s)), + Some "dir", + "Toot directory of the project"; + "-fobjc-arc", + Arg.Unit (fun s -> Config.arc_mode := true), + None, + "Translate with Objective-C Automatic Reference Counting (ARC)"; + "-models_mode", + Arg.Unit (fun _ -> CFrontend_config.models_mode := true), + None, + "Mode for computing the models"; + ] in + Utils.Arg2.create_options_desc false "Parsing Options" desc in + base_arg + +let usage = + "\nUsage: InferClang -c C Files -ast AST Files -results_dir [options] \n" + +let print_usage_exit () = + Utils.Arg2.usage arg_desc usage; + exit(1) + +let () = + Utils.Arg2.parse arg_desc (fun arg -> ()) usage + +(* This function reads the json file in fname, validates it, and encoded in the AST data structure*) +(* defined in Clang_ast_t. *) +let validate_decl_from_file fname = + Ag_util.Json.from_file Clang_ast_j.read_decl fname + +let validate_decl_from_stdin () = + Ag_util.Json.from_channel Clang_ast_j.read_decl stdin + +let do_run source_path ast_path = + try + let ast_filename, ast_decl = + match ast_path with + | Some path -> path, validate_decl_from_file path + | None -> "stdin of " ^ source_path, validate_decl_from_stdin () in + CFrontend_config.json := ast_filename; + CLocation.check_source_file source_path; + let source_file = CLocation.source_file_from_path source_path in + print_endline ("Start translation of AST from " ^ !CFrontend_config.json); + CFrontend.do_source_file source_file ast_decl; + print_endline ("End translation AST file " ^ !CFrontend_config.json ^ "... OK!") + with + (Yojson.Json_error s) as exc -> Printing.log_err ~fmt:"%s\n" s; + raise exc + +let _ = + Config.print_types:= true; + if Option.is_none !source_file then + (Printing.log_err "Incorrect command line arguments\n"; + print_usage_exit ()) + else + match !source_file with + | Some path -> do_run path !ast_file + | None -> assert false diff --git a/infer/src/clang/cMain.mli b/infer/src/clang/cMain.mli new file mode 100644 index 000000000..d5d6b29c9 --- /dev/null +++ b/infer/src/clang/cMain.mli @@ -0,0 +1,12 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Main module of InferClang. Take as input AST files produced by clang during compilation *) +(** and their corresponding C/C++/ObjectiveC source files. *) +(** Parse the arguments, parse and validate the input AST into a data structure *) +(** and translates it into a cfg. *) + + +val do_run : string -> string option -> unit diff --git a/infer/src/clang/cMethod_decl.ml b/infer/src/clang/cMethod_decl.ml new file mode 100644 index 000000000..faa7edf0c --- /dev/null +++ b/infer/src/clang/cMethod_decl.ml @@ -0,0 +1,176 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Process methods or functions declarations by adding them to the cfg. *) + +open Utils +open CFrontend_utils +open Clang_ast_t + +module L = Logging + +module type CMethod_decl = sig + val process_methods : Sil.tenv -> Cg.t -> Cfg.cfg -> CContext.curr_class -> string option -> + Clang_ast_t.decl list -> unit + + val function_decl : Sil.tenv -> Cfg.cfg -> Cg.t -> string option -> bool -> Clang_ast_t.decl_info -> + string -> Clang_ast_t.qual_type -> Clang_ast_t.function_decl_info -> (Mangled.t * Sil.typ * bool) list -> Procname.t option -> CContext.curr_class -> unit + + val create_function_signature : Clang_ast_t.decl_info -> Clang_ast_t.function_decl_info -> string -> + Clang_ast_t.qual_type -> bool -> Procname.t option -> Clang_ast_t.stmt option * CMethod_signature.method_signature +end + +module CMethod_decl_funct(T: CModule_type.CTranslation) : CMethod_decl = +struct + + let method_body_to_translate di ms body = + match body with + | Some body -> + if not (CLocation.should_translate_lib (CMethod_signature.ms_get_loc ms)) + then None else Some body + | None -> body + + type function_method_decl_info = + | Func_decl_info of Clang_ast_t.function_decl_info * string + | Meth_decl_info of Clang_ast_t.obj_c_method_decl_info * string + + let is_instance_method function_method_decl_info is_instance is_anonym_block = + if is_anonym_block then is_instance + else ( + match function_method_decl_info with + | Func_decl_info (function_decl_info, _) -> false + | Meth_decl_info (method_decl_info, _) -> + method_decl_info.Clang_ast_t.omdi_is_instance_method) + + let get_parameters function_method_decl_info = + let par_to_ms_par par = + match par with + | ParmVarDecl(decl_info, name, qtype, var_decl_info) -> + Printing.log_out ~fmt:"Adding param '%s' " name; + Printing.log_out ~fmt:"with pointer %s@." decl_info.Clang_ast_t.di_pointer; + (name, CTypes.get_type qtype) + | _ -> assert false in + match function_method_decl_info with + | Func_decl_info (function_decl_info, _) -> + list_map par_to_ms_par function_decl_info.Clang_ast_t.fdi_parameters + | Meth_decl_info (method_decl_info, class_name) -> + let pars = list_map par_to_ms_par method_decl_info.Clang_ast_t.omdi_parameters in + if (is_instance_method function_method_decl_info false false) then + ("self", class_name):: pars + else pars + + let get_return_type function_method_decl_info = + match function_method_decl_info with + | Func_decl_info (_, qt) -> qt + | Meth_decl_info (method_decl_info, _) -> + let qt = method_decl_info.Clang_ast_t.omdi_result_type in + CTypes.get_type qt + + let build_method_signature decl_info procname function_method_decl_info is_instance is_anonym_block = + let source_range = decl_info.Clang_ast_t.di_source_range in + let qt = get_return_type function_method_decl_info in + let is_instance_method = is_instance_method function_method_decl_info is_instance is_anonym_block in + let parameters = get_parameters function_method_decl_info in + CMethod_signature.make_ms procname parameters qt source_range is_instance_method + + let create_function_signature di fdecl_info name qt is_instance anonym_block_opt = + let procname, is_anonym_block = + match anonym_block_opt with + | Some block -> block, true + | None -> CMethod_trans.mk_procname_from_function name (CTypes.get_type qt), false in + let ms = build_method_signature di procname + (Func_decl_info (fdecl_info, CTypes.get_type qt)) is_instance is_anonym_block in + (match method_body_to_translate di ms fdecl_info.Clang_ast_t.fdi_body with + | Some body -> Some body, ms + | None -> None, ms) + + let model_exists procname = + Specs.summary_exists_in_models procname && not !CFrontend_config.models_mode + + (* Translates the method/function's body into nodes of the cfg. *) + let add_method tenv cg cfg class_decl_opt procname namespace instrs is_objc_method is_instance + captured_vars is_anonym_block = + Printing.log_out + ~fmt:"\n\n>>---------- ADDING METHOD: '%s' ---------<<\n" (Procname.to_string procname); + try + (match Cfg.Procdesc.find_from_name cfg procname with + | Some procdesc -> + if (Cfg.Procdesc.is_defined procdesc && not (model_exists procname)) then + (let context = + CContext.create_context tenv cg cfg procdesc namespace class_decl_opt + is_objc_method captured_vars is_instance in + CVar_decl.get_fun_locals context instrs; + let local_vars = list_map (fun (n, t, _) -> n, t) context.CContext.local_vars in + let start_node = Cfg.Procdesc.get_start_node procdesc in + let exit_node = Cfg.Procdesc.get_exit_node procdesc in + Cfg.Procdesc.append_locals procdesc local_vars; + Cfg.Node.add_locals_ret_declaration start_node local_vars; + Printing.log_out + ~fmt:"\n\n>>---------- Start translating the function: '%s' ---------<<" + (Procname.to_string procname); + let meth_body_nodes = T.instructions_trans context instrs exit_node in + if (not is_anonym_block) then CContext.LocalVars.reset_block (); + Cfg.Node.set_succs_exn start_node meth_body_nodes []; + Cg.add_node (CContext.get_cg context) (Cfg.Procdesc.get_proc_name procdesc)) + | None -> ()) + with + | Not_found -> () + | CTrans_utils.Self.SelfClassException _ -> + assert false (* this shouldn't happen, because self or [a class] should always be arguments of functions. This is to make sure I'm not wrong. *) + | Assert_failure (file, line, column) -> + print_endline ("Fatal error: exception Assert_failure("^ + file^", "^(string_of_int line)^", "^(string_of_int column)^")"); + Cfg.Procdesc.remove cfg procname true; + CMethod_trans.create_external_procdesc cfg procname is_objc_method None; + () + + let function_decl tenv cfg cg namespace is_instance di name qt fdecl_info captured_vars anonym_block_opt curr_class = + Printing.log_out ~fmt:"\nFound FunctionDecl '%s'. Processing...\n" name; + Printing.log_out "\nResetting the goto_labels hashmap...\n"; + CTrans_utils.GotoLabel.reset_all_labels (); (* C Language Std 6.8.6.1-1 *) + match create_function_signature di fdecl_info name qt is_instance anonym_block_opt with + | Some body, ms -> (* Only in the case the function declaration has a defined body we create a procdesc *) + let procname = CMethod_signature.ms_get_name ms in + CMethod_trans.create_local_procdesc cfg tenv ms [body] captured_vars false; + let is_instance = CMethod_signature.ms_is_instance ms in + let is_anonym_block = Option.is_some anonym_block_opt in + let is_objc_method = is_anonym_block in + let curr_class = if is_anonym_block then curr_class else CContext.ContextNoCls in + add_method tenv cg cfg curr_class procname namespace [body] is_objc_method is_instance + captured_vars is_anonym_block + | None, ms -> CMethod_signature.add ms + + let process_objc_method_decl tenv cg cfg namespace curr_class decl_info method_name method_decl_info = + let class_name = CContext.get_curr_class_name curr_class in + let procname = CMethod_trans.mk_procname_from_method class_name method_name in + let method_decl = Meth_decl_info (method_decl_info, class_name) in + let ms = build_method_signature decl_info procname method_decl false false in + Printing.log_out ~fmt:" ....Processing implementation for method '%s'\n" (Procname.to_string procname); + (match method_body_to_translate decl_info ms method_decl_info.Clang_ast_t.omdi_body with + | Some body -> + let is_instance = CMethod_signature.ms_is_instance ms in + CMethod_trans.create_local_procdesc cfg tenv ms [body] [] is_instance; + add_method tenv cg cfg curr_class procname namespace [body] true is_instance [] false + | None -> + CMethod_signature.add ms) + + let rec process_one_method_decl tenv cg cfg curr_class namespace dec = + match dec with + | ObjCMethodDecl(decl_info, method_name, method_decl_info) -> + process_objc_method_decl tenv cg cfg namespace curr_class decl_info method_name method_decl_info + + | ObjCPropertyImplDecl(decl_info, property_impl_decl_info) -> + let prop_methods = ObjcProperty_decl.make_getter_setter cfg curr_class decl_info property_impl_decl_info in + list_iter (process_one_method_decl tenv cg cfg curr_class namespace) prop_methods + + | EmptyDecl _ | ObjCIvarDecl _ -> () + | d -> Printing.log_err + ~fmt:"\nWARNING: found Method Declaration '%s' skipped. NEED TO BE FIXED\n\n" (Ast_utils.string_of_decl d); + () + + let process_methods tenv cg cfg curr_class namespace decl_list = + list_iter (process_one_method_decl tenv cg cfg curr_class namespace) decl_list + +end diff --git a/infer/src/clang/cMethod_decl.mli b/infer/src/clang/cMethod_decl.mli new file mode 100644 index 000000000..53a132871 --- /dev/null +++ b/infer/src/clang/cMethod_decl.mli @@ -0,0 +1,28 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Process methods or functions declarations by adding them to the cfg. *) + +module CMethod_decl_funct(T: CModule_type.CTranslation) : sig + val process_methods : Sil.tenv -> Cg.t -> Cfg.cfg -> CContext.curr_class -> string option -> + Clang_ast_t.decl list -> unit + + val function_decl : Sil.tenv -> Cfg.cfg -> Cg.t -> string option -> bool -> Clang_ast_t.decl_info -> + string -> Clang_ast_t.qual_type -> Clang_ast_t.function_decl_info -> (Mangled.t * Sil.typ * bool) list -> Procname.t option -> CContext.curr_class -> unit + + val create_function_signature : Clang_ast_t.decl_info -> Clang_ast_t.function_decl_info -> string -> + Clang_ast_t.qual_type -> bool -> Procname.t option -> Clang_ast_t.stmt option * CMethod_signature.method_signature +end + +module type CMethod_decl = sig + val process_methods : Sil.tenv -> Cg.t -> Cfg.cfg -> CContext.curr_class -> string option -> + Clang_ast_t.decl list -> unit + + val function_decl : Sil.tenv -> Cfg.cfg -> Cg.t -> string option -> bool -> Clang_ast_t.decl_info -> + string -> Clang_ast_t.qual_type -> Clang_ast_t.function_decl_info -> (Mangled.t * Sil.typ * bool) list -> Procname.t option -> CContext.curr_class -> unit + + val create_function_signature : Clang_ast_t.decl_info -> Clang_ast_t.function_decl_info -> string -> + Clang_ast_t.qual_type -> bool -> Procname.t option -> Clang_ast_t.stmt option * CMethod_signature.method_signature +end diff --git a/infer/src/clang/cMethod_signature.ml b/infer/src/clang/cMethod_signature.ml new file mode 100644 index 000000000..ac14d9e08 --- /dev/null +++ b/infer/src/clang/cMethod_signature.ml @@ -0,0 +1,67 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Define the signature of a method consisting of its name, its arguments, *) +(** return type, location and whether its an instance method. *) + +type method_signature = { + _name : Procname.t; + _args : (string * string) list; (* (name, type) *) + _ret_type : string; + _loc : Clang_ast_t.source_range; + _is_instance : bool +} + +let ms_get_name ms = + ms._name + +let ms_get_args ms = + ms._args + +let ms_get_ret_type ms = + ms._ret_type + +let ms_get_loc ms = + ms._loc + +let ms_is_instance ms = + ms._is_instance + +type methodMap = method_signature Procname.Map.t + +let methodMap = ref Procname.Map.empty + +let make_ms procname args ret_type loc is_instance = + let meth_signature = { + _name = procname; + _args = args; + _ret_type = ret_type; + _loc = loc; + _is_instance = is_instance } in + meth_signature + +let replace_name_ms ms name = + let meth_signature = { + _name = name; + _args = ms._args; + _ret_type = ms._ret_type; + _loc = ms._loc; + _is_instance = ms._is_instance } in + meth_signature + +let ms_to_string ms = + "Method "^(Procname.to_string ms._name)^" "^ + (Utils.list_to_string (fun (s1, s2) -> s1^", "^s2) ms._args)^"->"^ms._ret_type^" "^ + Clang_ast_j.string_of_source_range ms._loc + +let find ms = + Procname.Map.find ms !methodMap + +let add ms = + try ignore (find ms._name) + with Not_found -> methodMap := Procname.Map.add ms._name ms !methodMap + +let reset_map () = + methodMap := Procname.Map.empty diff --git a/infer/src/clang/cMethod_signature.mli b/infer/src/clang/cMethod_signature.mli new file mode 100644 index 000000000..01e777e97 --- /dev/null +++ b/infer/src/clang/cMethod_signature.mli @@ -0,0 +1,32 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Define the signature of a method consisting of its name, its arguments, *) +(** return type, location and whether its an instance method. *) + +type method_signature + +val add : method_signature -> unit + +val find : Procname.t -> method_signature + +val reset_map : unit -> unit + +val ms_get_name : method_signature -> Procname.t + +val ms_get_args : method_signature -> (string * string) list + +val ms_get_ret_type : method_signature -> string + +val ms_get_loc : method_signature -> Clang_ast_t.source_range + +val ms_is_instance : method_signature -> bool + +val make_ms : Procname.t -> (string * string) list -> string -> Clang_ast_t.source_range -> +bool -> method_signature + +val replace_name_ms : method_signature -> Procname.t -> method_signature + +val ms_to_string : method_signature -> string diff --git a/infer/src/clang/cMethod_trans.ml b/infer/src/clang/cMethod_trans.ml new file mode 100644 index 000000000..7128e3833 --- /dev/null +++ b/infer/src/clang/cMethod_trans.ml @@ -0,0 +1,253 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Methods for creating a procdesc from a method or function declaration *) +(** and for resolving a method call and finding the right callee *) + +open Utils +open CFrontend_utils +open CContext + +module L = Logging + +(** When the methoc call is MCStatic, means that it is a class method. *) +(** When it is MCVirtual, it means that it is an instance method and that *) +(** the method to be called will be determined at runtime. If it is MCNoVirtual *) +(** it means that it is an instance method but that the method to be called will *) +(** be determined at compile time *) +type method_call_type = + | MCVirtual + | MCNoVirtual + | MCStatic + +let mk_procname_from_function name type_name = + let type_name_crc = CRC.crc16 type_name in + Procname.mangled_cpp name type_name_crc + +let mk_procname_from_method class_name method_name = + Procname.mangled_objc class_name method_name + +let rec resolve_method_class tenv class_name method_name = + let type_name = Sil.TN_csu (Sil.Class, class_name) in + match Sil.tenv_lookup tenv type_name with + | Some (Sil.Tstruct (_, _, Sil.Class, cls, super_classes, methods, iann)) -> + Some type_name + | _ -> None + +let resolve_method tenv class_name method_name = + let class_name_mangled = Mangled.from_string class_name in + match resolve_method_class tenv class_name_mangled method_name with + | Some (Sil.TN_csu (Sil.Class, class_name)) -> + let class_method_name = mk_procname_from_method (Mangled.to_string class_name) method_name in + (try let ms = CMethod_signature.find class_method_name in + Some ms + with Not_found -> None) + | _ -> None + +let get_superclass_curr_class context = + let retrive_super cname super_opt = + let iname = Sil.TN_csu (Sil.Class, Mangled.from_string cname) in + Printing.log_out ~fmt:"Checking for superclass = '%s'\n\n%!" (Sil.typename_to_string iname); + match Sil.tenv_lookup (CContext.get_tenv context) iname with + | Some Sil.Tstruct(_, _, _, _, (_, super_name):: _, _, _) -> + Mangled.to_string super_name + | _ -> + Printing.log_err ~fmt:"NOT FOUND superclass = '%s'\n\n%!" (Sil.typename_to_string iname); + (match super_opt with + | Some super -> super + | _ -> assert false) in + match CContext.get_curr_class context with + | CContext.ContextCls (cname, super_opt, _) -> + retrive_super cname super_opt + | CContext.ContextCategory (_, cls) -> + retrive_super cls None + | CContext.ContextNoCls + | CContext.ContextProtocol _ -> assert false + +let get_class_selector_instance context obj_c_message_expr_info act_params = + let selector = obj_c_message_expr_info.Clang_ast_t.omei_selector in + match obj_c_message_expr_info.Clang_ast_t.omei_receiver_kind with + | `Class qt -> (CTypes.get_type qt, selector, MCStatic) + | `Instance -> + (match act_params with + | (instance_obj, Sil.Tptr(t, _)):: _ + | (instance_obj, t):: _ -> + (CTypes.classname_of_type t, selector, MCVirtual) + | _ -> assert false) + | `SuperInstance -> + let superclass = get_superclass_curr_class context in + (superclass, selector, MCNoVirtual) + | `SuperClass -> + let superclass = get_superclass_curr_class context in + (superclass, selector, MCStatic) + +let get_formal_parameters tenv ms = + let rec defined_parameters pl = + match pl with + | [] -> [] + | (name, raw_type):: pl' -> + let qt = + if (name = CFrontend_config.self && CMethod_signature.ms_is_instance ms) then + (Ast_expressions.create_pointer_type raw_type) + else Ast_expressions.create_qual_type raw_type in + let typ = CTypes_decl.qual_type_to_sil_type tenv qt in + (name, typ):: defined_parameters pl' in + defined_parameters (CMethod_signature.ms_get_args ms) + +(* Returns a list of captured variables. *) +(* In order to get the right mangled name we search among *) +(* the local variables of the defining function and it's formals.*) +let captured_vars_from_block_info context cvl = + let formal2captured (s, t) = + (Mangled.from_string s, t, false) in + let find lv n = + try + list_find (fun (n', _, _) -> Mangled.to_string n' = n) lv + with Not_found -> Printing.log_err ~fmt:"Trying to find variable %s@." n; assert false in + let rec f cvl' = + match cvl' with + | [] -> [] + | cv:: cvl'' -> + (match cv.Clang_ast_t.bcv_variable with + | Some dr -> + (match dr.Clang_ast_t.dr_name, dr.Clang_ast_t.dr_qual_type with + | Some n, _ -> + if n = CFrontend_config.self && not context.is_instance then [] + else + (let procdesc_formals = Cfg.Procdesc.get_formals context.procdesc in + (Printing.log_err ~fmt:"formals are %s@." (Utils.list_to_string (fun (x, _) -> x) procdesc_formals)); + let formals = list_map formal2captured procdesc_formals in + [find (context.local_vars @ formals) n]) + | _ -> assert false) + | None -> []) :: f cvl'' in + list_flatten (f cvl) + +let get_return_type tenv ms = + let qt = CMethod_signature.ms_get_ret_type ms in + CTypes_decl.qual_type_to_sil_type tenv + (Ast_expressions.create_qual_type (CTypes.get_function_return_type qt)) + +(** Creates a procedure description. *) +let create_local_procdesc cfg tenv ms fbody captured is_objc_inst_method = + let defined = not ((list_length fbody) == 0) in + let procname = CMethod_signature.ms_get_name ms in + let pname = Procname.to_string procname in + let create_new_procdesc () = + let formals = get_formal_parameters tenv ms in + let captured_str = list_map (fun (s, t, _) -> (Mangled.to_string s, t)) captured in + (* Captured variables for blocks are treated as parameters *) + let formals = captured_str @formals in + let source_range = CMethod_signature.ms_get_loc ms in + Printing.log_out ~fmt: + "\n\n>>------------------------- Start creating a new procdesc for function: '%s' ---------<<\n" pname; + let loc_start = CLocation.get_sil_location_from_range source_range true in + let loc_exit = CLocation.get_sil_location_from_range source_range false in + let ret_type = get_return_type tenv ms in + let captured' = list_map (fun (s, t, _) -> (s, t)) captured in + let procdesc = + (* This part below is a boilerplate and the following list of *) + (* instructions should be moved in the Cfg.Procdesc module *) + let open Cfg.Procdesc in + let proc_attributes = + { + Sil.access = Sil.Default; + Sil.exceptions = []; + Sil.is_abstract = false; + Sil.is_bridge_method = false; + Sil.is_objc_instance_method = is_objc_inst_method; + Sil.is_synthetic_method = false; + Sil.language = Sil.C_CPP; + Sil.method_annotation = Sil.method_annotation_empty; + } in + create { + cfg = cfg; + name = procname; + is_defined = defined; + ret_type = ret_type; + formals = formals; + locals = []; + captured = captured'; + loc = loc_start; + proc_attributes = proc_attributes; + } in + if defined then + (if !Config.arc_mode then + Cfg.Procdesc.set_flag procdesc Mleak_buckets.objc_arc_flag "true"; + let start_kind = Cfg.Node.Start_node procdesc in + let start_node = Cfg.Node.create cfg loc_start start_kind [] procdesc [] in + let exit_kind = Cfg.Node.Exit_node procdesc in + let exit_node = Cfg.Node.create cfg loc_exit exit_kind [] procdesc [] in + Cfg.Procdesc.set_start_node procdesc start_node; + Cfg.Procdesc.set_exit_node procdesc exit_node) in + match Cfg.Procdesc.find_from_name cfg procname with + | Some prevoius_procdesc -> + Printing.log_err ~fmt:"\n\n!!!WARNING: procdesc for %s already defined \n" pname; + if defined && not (Cfg.Procdesc.is_defined prevoius_procdesc) then + (Cfg.Procdesc.remove cfg (Cfg.Procdesc.get_proc_name prevoius_procdesc) true; + create_new_procdesc ()) + | None -> create_new_procdesc () + +(** Create a procdesc for objc methods whose signature cannot be found. *) +let create_external_procdesc cfg procname is_objc_inst_method type_opt = + match Cfg.Procdesc.find_from_name cfg procname with + | Some _ -> () + | None -> + let ret_type, formals = + (match type_opt with + | Some (ret_type, arg_types) -> + ret_type, list_map (fun typ -> ("x", typ)) arg_types + | None -> Sil.Tvoid, []) in + let loc = Sil.loc_none in + let _ = + let open Cfg.Procdesc in + let proc_attributes = + { + Sil.access = Sil.Default; + Sil.exceptions = []; + Sil.is_abstract = false; + Sil.is_bridge_method = false; + Sil.is_objc_instance_method = is_objc_inst_method; + Sil.is_synthetic_method = false; + Sil.language = Sil.C_CPP; + Sil.method_annotation = Sil.method_annotation_empty; + } in + create { + cfg = cfg; + name = procname; + is_defined = false; + ret_type = ret_type; + formals = formals; + locals = []; + captured = []; + loc = loc; + proc_attributes = proc_attributes; + } in + () + +let instance_to_method_call_type instance = + if instance then MCVirtual + else MCStatic + +(*Returns the procname and whether is instance, according to the selector *) +(* information and according to the method signature *) +let get_callee_objc_method context obj_c_message_expr_info act_params = + let (class_name, method_name, mc_type) = + get_class_selector_instance context obj_c_message_expr_info act_params in + let is_instance = mc_type != MCStatic in + match CTrans_models.get_predefined_model_method_signature class_name method_name + mk_procname_from_method with + | Some ms -> + create_local_procdesc context.cfg context.tenv ms [] [] is_instance; + CMethod_signature.ms_get_name ms, MCNoVirtual + | None -> + match resolve_method context.tenv class_name method_name with + | Some callee_ms -> + let is_instance = is_instance || (CMethod_signature.ms_is_instance callee_ms) in + create_local_procdesc context.cfg context.tenv callee_ms [] [] is_instance; + (CMethod_signature.ms_get_name callee_ms), mc_type + | None -> + let callee_pn = mk_procname_from_method class_name method_name in + create_external_procdesc context.cfg callee_pn is_instance None; + callee_pn, mc_type diff --git a/infer/src/clang/cMethod_trans.mli b/infer/src/clang/cMethod_trans.mli new file mode 100644 index 000000000..186e2ef34 --- /dev/null +++ b/infer/src/clang/cMethod_trans.mli @@ -0,0 +1,34 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Methods for creating a procdesc from a method or function declaration +and for resolving a method call and finding the right callee *) + +(** When the methoc call is MCStatic, means that it is a class method. *) +(** When it is MCVirtual, it means that it is an instance method and that *) +(** the method to be called will be determined at runtime. If it is MCNoVirtual *) +(** it means that it is an instance method but that the method to be called will *) +(** be determined at compile time *) +type method_call_type = + | MCVirtual + | MCNoVirtual + | MCStatic + +val get_callee_objc_method : CContext.t -> Clang_ast_t.obj_c_message_expr_info -> (Sil.exp * Sil.typ) list +-> Procname.t * method_call_type + +val create_local_procdesc : Cfg.cfg -> Sil.tenv -> CMethod_signature.method_signature -> +Clang_ast_t.stmt list -> (Mangled.t * Sil.typ * bool) list -> bool -> unit + +val create_external_procdesc : Cfg.cfg -> Procname.t -> bool -> (Sil.typ * Sil.typ list) option -> unit + +val captured_vars_from_block_info : CContext.t -> Clang_ast_t.block_captured_variable list -> (Mangled.t * Sil.typ * bool) list + +val mk_procname_from_method : string -> string -> Procname.t + +val mk_procname_from_function : string -> string -> Procname.t + +val get_class_selector_instance : CContext.t -> Clang_ast_t.obj_c_message_expr_info -> (Sil.exp * Sil.typ) list +-> (string * string * method_call_type) diff --git a/infer/src/clang/cModule_type.ml b/infer/src/clang/cModule_type.ml new file mode 100644 index 000000000..f1856f123 --- /dev/null +++ b/infer/src/clang/cModule_type.ml @@ -0,0 +1,15 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +module type CTranslation = +sig + val instructions_trans : CContext.t -> Clang_ast_t.stmt list -> Cfg.Node.t -> Cfg.Node.t list +end + +module type CMethod_declaration = +sig + val function_decl : Sil.tenv -> Cfg.cfg -> Cg.t -> string option -> bool -> Clang_ast_t.decl_info -> + string -> Clang_ast_t.qual_type -> Clang_ast_t.function_decl_info -> (Mangled.t * Sil.typ * bool) list -> Procname.t option -> CContext.curr_class -> unit +end diff --git a/infer/src/clang/cTrans.ml b/infer/src/clang/cTrans.ml new file mode 100644 index 000000000..197d98e0e --- /dev/null +++ b/infer/src/clang/cTrans.ml @@ -0,0 +1,1824 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Translates instructions: (statements and expressions) from the ast into sil *) + +open CLocation +open CContext +open Utils +open CTrans_utils +open CFrontend_utils +open CFrontend_utils.General_utils +open Clang_ast_t +open CFrontend_config + +open CTrans_utils.Nodes +module L = Logging + +module type CTrans = sig +(** Translates instructions: (statements and expressions) from the ast into sil *) + +(** It receives the context, a list of statements and the exit node and it returns a list of cfg nodes *) +(** that reporesent the translation of the stmts into sil. *) + val instructions_trans : CContext.t -> Clang_ast_t.stmt list -> Cfg.Node.t -> Cfg.Node.t list + + (** It receives the context and a statement and a warning string and returns the translated sil expression *) + (** that represents the translation of the stmts into sil. *) + val expression_trans : CContext.t -> Clang_ast_t.stmt -> string -> Sil.exp + +end + +module CTrans_funct(M: CModule_type.CMethod_declaration) : CTrans = +struct + + let add_autorelease_call context exp typ sil_loc = + let method_name = Procname.clang_get_method (Cfg.Procdesc.get_proc_name context.procdesc) in + if !Config.arc_mode && + not (CTrans_utils.is_owning_name method_name) && + ObjcInterface_decl.is_pointer_to_objc_class context.CContext.tenv typ then + let fname = SymExec.ModelBuiltins.__set_autorelease_attribute in + let ret_id = Ident.create_fresh Ident.knormal in + let stmt_call = Sil.Call([ret_id], (Sil.Const (Sil.Cfun fname)), [(exp, typ)], sil_loc, Sil.cf_default) in + ([ret_id], [stmt_call]) + else ([], []) + + let rec is_block_expr s = + match s with + | BlockExpr _ -> true + (* the block can be wrapped in ExprWithCleanups or ImplicitCastExpr*) + | ImplicitCastExpr(_, [s'], _, _) + | ExprWithCleanups(_, [s'], _, _) -> is_block_expr s' + | _ -> false + + let objc_exp_of_type_block fun_exp_stmt = + let is_block_qt qt = + match Str.split (Str.regexp_string "(^)") qt.Clang_ast_t.qt_raw with + | [_; _] -> true + | _ -> false in + match fun_exp_stmt with + | ImplicitCastExpr(_, _, ei, _) when is_block_qt ei.Clang_ast_t.ei_qual_type -> true + | _ -> false + + (* This function add in tenv a class representing an objc block. *) + (* An object of this class has type:*) + (* name_of_block |-> {capture_var1:typ_of_capture_var1,... capture_varn:typ_of_capture_varn} *) + (* It allocates one element and sets its fields with the current values of the *) + (* captured variables. This allocated instance is used to detect retain cycles involving the block.*) + let allocate_block trans_state block_name captured_vars loc = + let tenv = trans_state.context.tenv in + let procdesc = trans_state.context.procdesc in + let procname = Cfg.Procdesc.get_proc_name procdesc in + let mk_field_from_captured_var (vname, typ, b) = + let fname = CField_decl.mk_class_field_name block_name (Mangled.to_string vname) in + let item_annot = Sil.item_annotation_empty in + fname, typ, item_annot in + let fields = list_map mk_field_from_captured_var captured_vars in + Printing.log_out ~fmt:"Block %s field:\n" block_name; + list_iter (fun (fn, ft, _) -> + Printing.log_out ~fmt:"-----> field: '%s'\n" (Ident.fieldname_to_string fn)) fields; + let mblock = Mangled.from_string block_name in + let block_type = Sil.Tstruct(fields, [], Sil.Class, Some mblock, [], [], []) in + let block_name = Sil.TN_csu(Sil.Class, mblock) in + Sil.tenv_add tenv block_name block_type; + let trans_res = CTrans_utils.alloc_trans trans_state loc (Ast_expressions.dummy_stmt_info ()) block_type true in + let id_block = match trans_res.exps with + | [(Sil.Var id, t)] -> id + | _ -> assert false in + let block_var = Sil.mk_pvar mblock procname in + let declare_block_local = + Sil.Declare_locals([(block_var, Sil.Tptr(block_type, Sil.Pk_pointer))], loc) in + (* Adds Nullify of the temp block variable in the predecessors of the exit node. *) + let pred_exit = Cfg.Node.get_preds (Cfg.Procdesc.get_exit_node procdesc) in + let block_nullify_instr = + if pred_exit = [] then + [Sil.Nullify(block_var, loc, true)] + else (list_iter (fun n -> let loc = Cfg.Node.get_loc n in + Cfg.Node.append_instrs_temps n [Sil.Nullify(block_var, loc, true)] []) pred_exit; + []) in + let set_instr = Sil.Set(Sil.Lvar block_var, block_type, Sil.Var id_block, loc) in + let ids, captured_instrs = list_split (list_map (fun (vname, typ, _) -> + let id = Ident.create_fresh Ident.knormal in + id, Sil.Letderef(id, Sil.Lvar (Sil.mk_pvar vname procname), typ, loc) + ) captured_vars) in + let fields_ids = list_combine fields ids in + let set_fields = list_map (fun ((f, t, _), id) -> + Sil.Set(Sil.Lfield(Sil.Var id_block, f, block_type), t, Sil.Var id, loc)) fields_ids in + (declare_block_local :: trans_res.instrs) @ [set_instr] @ captured_instrs @ set_fields @ block_nullify_instr , id_block:: ids + + (* From a list of expression extract blocks from tuples and *) + (* returns block names and assignment to temp vars *) + let extract_block_from_tuple procname exps loc = + let insts = ref [] in + let ids = ref [] in + let is_function_name t e = + match e with + | Sil.Const(Sil.Cfun bn) -> + let bn'= Procname.to_string bn in + let bn''= Mangled.from_string bn' in + let block = Sil.Lvar(Sil.mk_pvar bn'' procname) in + let id = Ident.create_fresh Ident.knormal in + ids := id::!ids; + insts := Sil.Letderef(id, block, t, loc)::!insts; + [(Sil.Var id, t)] + | _ -> [(e, t)] in + let get_function_name t el = list_flatten(list_map (is_function_name t) el) in + let rec f es = + match es with + | [] -> [] + | (Sil.Const(Sil.Ctuple el), (Sil.Tptr((Sil.Tfun _), _ ) as t)):: es' -> + get_function_name t el @ (f es') + | e:: es' -> e:: f es' in + (f exps, !insts, !ids) + + (* If e is a block and the calling node has the priority then *) + (* we need to release the priority to allow*) + (* creation of nodes inside the block.*) + (* At the end of block translation, we need to get the proirity back.*) + (* the parameter f will be called with function instruction *) + let exec_with_block_priority_exception f trans_state e stmt_info = + if (is_block_expr e) && (PriorityNode.own_priority_node trans_state.priority stmt_info) then + f { trans_state with priority = Free } e + else f trans_state e + + (* This is the standard way of dealing with self:Class or a call [a class]. We translate it as sizeof() *) + (* The only time when we want to translate those expressions differently is when they are the first argument of *) + (* method calls. In that case they are not translated as expressions, but we take the type and create a static *) + (* method call from it. This is done in objcMessageExpr_trans. *) + let exec_with_self_exception f trans_state stmt = + try + f trans_state stmt + with Self.SelfClassException class_name -> + let typ = CTypes_decl.type_name_to_sil_type trans_state.context.tenv class_name in + { empty_res_trans with + exps = [(Sil.Sizeof(typ, Sil.Subtype.exact), typ)]} + + (* Execute translation of e forcing to release priority (if it's not free) and then setting it back.*) + (* This is used in conditional operators where we need to force the priority to be free for the *) + (* computation of the expressions*) + let exec_with_priority_exception trans_state e f = + if PriorityNode.is_priority_free trans_state then + f trans_state e + else f { trans_state with priority = Free } e + + let breakStmt_trans trans_state = + match trans_state.continuation with + | Some bn -> { empty_res_trans with root_nodes = bn.break } + | _ -> assert false + + let continueStmt_trans trans_state = + match trans_state.continuation with + | Some bn -> { empty_res_trans with root_nodes = bn.continue } + | _ -> assert false + + let stringLiteral_trans trans_state stmt_info expr_info str = + Printing.log_out ~fmt:"Passing from StringLiteral '%s'\n" stmt_info.Clang_ast_t.si_pointer; + let typ = CTypes_decl.get_type_from_expr_info expr_info trans_state.context.tenv in + let exp = Sil.Const (Sil.Cstr (str)) in + { empty_res_trans with exps = [(exp, typ)]} + + (* FROM CLANG DOCS: "Implements the GNU __null extension, which is a name for a null pointer constant *) + (* that has integral type (e.g., int or long) and is the same size and alignment as a pointer. The __null *) + (* extension is typically only used by system headers, which define NULL as __null in C++ rather than using 0 *) + (* (which is an integer that may not match the size of a pointer)". So we implement it as the constant zero *) + let gNUNullExpr_trans trans_state stmt_info expr_info = + Printing.log_out ~fmt:"Passing from GNUNullExpr '%s'\n" stmt_info.Clang_ast_t.si_pointer; + let typ = CTypes_decl.get_type_from_expr_info expr_info trans_state.context.tenv in + let exp = Sil.Const (Sil.Cint (Sil.Int.zero)) in + { empty_res_trans with exps = [(exp, typ)]} + + let objCSelectorExpr_trans trans_state stmt_info expr_info selector = + stringLiteral_trans trans_state stmt_info expr_info selector + + let objCEncodeExpr_trans trans_state stmt_info expr_info qual_type = + Printing.log_out ~fmt:"Passing from ObjCEncodeExpr '%s'\n" stmt_info.Clang_ast_t.si_pointer; + stringLiteral_trans trans_state stmt_info expr_info (CTypes.get_type qual_type) + + let objCProtocolExpr_trans trans_state stmt_info expr_info decl_ref = + Printing.log_out ~fmt:"Passing from ObjCProtocolExpr '%s'\n" stmt_info.Clang_ast_t.si_pointer; + let name = (match decl_ref.Clang_ast_t.dr_name with + | Some s -> s + | _ -> "") in + stringLiteral_trans trans_state stmt_info expr_info name + + let characterLiteral_trans trans_state stmt_info expr_info n = + Printing.log_out ~fmt:"Passing from CharacterLiteral '%s'\n" stmt_info.Clang_ast_t.si_pointer; + let typ = CTypes_decl.get_type_from_expr_info expr_info trans_state.context.tenv in + let exp = Sil.Const (Sil.Cint (Sil.Int.of_int n)) in + { empty_res_trans with exps = [(exp, typ)]} + + let floatingLiteral_trans trans_state stmt_info expr_info float_string = + Printing.log_out ~fmt:"Passing from FloatingLiteral '%s'\n" stmt_info.Clang_ast_t.si_pointer; + let typ = CTypes_decl.get_type_from_expr_info expr_info trans_state.context.tenv in + let exp = Sil.Const (Sil.Cfloat (float_of_string float_string)) in + { empty_res_trans with exps = [(exp, typ)]} + + (* Note currently we don't have support for different qual *) + (* type like long, unsigned long, etc *) + and integerLiteral_trans trans_state stmt_info expr_info integer_literal_info = + Printing.log_out ~fmt:"Passing from IntegerLiteral '%s'\n" stmt_info.Clang_ast_t.si_pointer; + let typ = CTypes_decl.get_type_from_expr_info expr_info trans_state.context.tenv in + let i = try + int_of_string (integer_literal_info.Clang_ast_t.ili_value) + with _ -> (Random.int 10000) +1 in + let exp = Sil.Const (Sil.Cint (Sil.Int.of_int i)) in + (* In case of overflow we return a random number. To avoid 0 we add plus one.*) + { empty_res_trans with exps = [(exp, typ)]} + + let nullStmt_trans succ_nodes stmt_info = + Printing.log_out ~fmt:"Passing from NullStmt '%s'.\n" stmt_info.Clang_ast_t.si_pointer; + { empty_res_trans with root_nodes = succ_nodes } + + (* The stmt seems to be always empty *) + let unaryExprOrTypeTraitExpr_trans trans_state stmt_info expr_info unary_expr_or_type_trait_expr_info = + Printing.log_out ~fmt:"Passing from UnaryExprOrTypeTraitExpr '%s' \n" stmt_info.Clang_ast_t.si_pointer; + let typ = CTypes_decl.qual_type_to_sil_type trans_state.context.tenv expr_info.Clang_ast_t.ei_qual_type in + match unary_expr_or_type_trait_expr_info.Clang_ast_t.uttei_kind with + | `SizeOf -> + let qt = Ast_utils.type_from_unary_expr_or_type_trait_expr_info unary_expr_or_type_trait_expr_info in + let sizeof_typ = + match qt with + | Some qt -> CTypes_decl.qual_type_to_sil_type trans_state.context.tenv qt + | None -> typ in (* Some default type since the type is missing *) + { empty_res_trans with exps = [(Sil.Sizeof(sizeof_typ, Sil.Subtype.exact), sizeof_typ)]} + | k -> Printing.log_stats + ~fmt:"\nWARNING: Missing translation of Uniry_Expression_Or_Trait of kind: %s . Expression ignored, returned -1... \n" + (Clang_ast_j.string_of_unary_expr_or_type_trait_kind k); + { empty_res_trans with exps =[(Sil.exp_minus_one, typ)]} + + (* search the label into the hashtbl - create a fake node eventually *) + (* connect that node with this stmt *) + let gotoStmt_trans trans_state stmt_info label_name = + Printing.log_out ~fmt:"\nPassing from `GotoStmt '%s'\n" stmt_info.Clang_ast_t.si_pointer; + let sil_loc = get_sil_location stmt_info trans_state.parent_line_number trans_state.context in + let root_node' = GotoLabel.find_goto_label trans_state.context label_name sil_loc in + { empty_res_trans with root_nodes = [root_node']; leaf_nodes = trans_state.succ_nodes } + + let declRefExpr_trans trans_state stmt_info expr_info decl_ref_expr_info d = + Printing.log_out ~fmt:"Passing from DeclRefExpr '%s' \n" stmt_info.Clang_ast_t.si_pointer; + let context = trans_state.context in + let typ = CTypes_decl.qual_type_to_sil_type context.tenv expr_info.Clang_ast_t.ei_qual_type in + let name = get_name_decl_ref_exp_info decl_ref_expr_info stmt_info in + let procname = Cfg.Procdesc.get_proc_name context.procdesc in + let is_function = + match get_decl_kind decl_ref_expr_info with + | `Function -> true + | _ -> false in + if is_enumeration_constant d then ( + let const_exp = (match CTypes.search_enum_type_by_name context.tenv name with + | Some v -> + let ce = Sil.Const v in + Printing.log_out ~fmt:" ....Found enum constant '%s', " name; + Printing.log_out ~fmt:"replacing with integer '%s' \n" (Sil.exp_to_string ce); ce + | None -> + Printing.log_stats + ~fmt:" WARNING: Found enum constant '%s', but its value was not found in the tenv. Returning 0.\n" name; + (Sil.Const(Sil.Cint Sil.Int.zero))) in + { root_nodes = []; leaf_nodes = []; ids = []; instrs = []; exps = [(const_exp, typ)]} + ) else if is_function then ( + let name = + if name = CFrontend_config.builtin_expect then ("infer"^CFrontend_config.builtin_expect) + else name in + let qt = CTypes.get_raw_qual_type_decl_ref_exp_info decl_ref_expr_info in + let pname, type_opt = + match qt with + | Some v -> + CMethod_trans.mk_procname_from_function name v, CTypes_decl.parse_func_type name v + | None -> Procname.from_string name, None in + CMethod_trans.create_external_procdesc context.cfg pname false type_opt; + let address_of_function = not context.CContext.is_callee_expression in + (* If we are not translating a callee expression, then the address of the function is being taken.*) + (* As e.g. in fun_ptr = foo; *) + let non_mangled_func_name = + if name = CFrontend_config.malloc && + (!CFrontend_config.language = CFrontend_config.OBJC || + !CFrontend_config.language = CFrontend_config.OBJCPP) then + SymExec.ModelBuiltins.malloc_no_fail + else Procname.from_string name in + let is_builtin = SymExec.function_is_builtin non_mangled_func_name in + if is_builtin then (* malloc, free, exit, scanf, ... *) + { empty_res_trans with exps = [(Sil.Const (Sil.Cfun non_mangled_func_name), typ)]} + else + begin + if address_of_function then Cfg.set_procname_priority context.cfg pname; + { empty_res_trans with exps = [(Sil.Const (Sil.Cfun pname), typ)]} + end + ) else ( + let pvar = + if not (Utils.string_is_prefix pointer_prefix stmt_info.si_pointer) then + try + CContext.LocalVars.find_var_with_pointer context stmt_info.Clang_ast_t.si_pointer + with _ -> assert false + else Sil.mk_pvar (Mangled.from_string name) procname in + let e = Sil.Lvar pvar in + let exps = + if Self.is_var_self pvar (CContext.is_objc_method context) then + if (CTypes.is_class typ) then + raise (Self.SelfClassException (CContext.get_curr_class_name trans_state.context.curr_class)) + else + let typ = CTypes.add_pointer_to_typ + (CTypes_decl.get_type_curr_class context.tenv (CContext.get_curr_class context)) in + [(e, typ)] + else [(e, typ)] in + Printing.log_out ~fmt:"\n\n PVAR ='%s'\n\n" (Sil.pvar_to_string pvar); + { empty_res_trans with exps = exps } + ) + + let rec labelStmt_trans trans_state stmt_info stmt_list label_name = + Printing.log_out ~fmt:"\nPassing from `LabelStmt '%s' \n" stmt_info.Clang_ast_t.si_pointer; + (* go ahead with the translation *) + let res_trans = match stmt_list with + | [stmt] -> + instruction trans_state stmt + | _ -> assert false (* expected a stmt or at most a compoundstmt *) in + (* create the label root node into the hashtbl *) + let sil_loc = get_sil_location stmt_info trans_state.parent_line_number trans_state.context in + let root_node' = GotoLabel.find_goto_label trans_state.context label_name sil_loc in + Cfg.Node.set_succs_exn root_node' res_trans.root_nodes []; + { empty_res_trans with root_nodes = [root_node']; leaf_nodes = trans_state.succ_nodes } + + and arraySubscriptExpr_trans trans_state stmt_info expr_info stmt_list = + Printing.log_out + ~fmt:"Passing from ArraySubscriptExpr '%s' \n" stmt_info.Clang_ast_t.si_pointer; + let typ = CTypes_decl.get_type_from_expr_info expr_info trans_state.context.tenv in + let array_stmt, idx_stmt = (match stmt_list with + | [a; i] -> a, i (* Assumption: the statement list contains 2 elements, + the first is the array expr and the second the index *) + | _ -> assert false) in (* Let's get notified if the assumption is wrong...*) + let line_number = get_line stmt_info trans_state.parent_line_number in + let trans_state'= { trans_state with parent_line_number = line_number } in + let res_trans_a = instruction trans_state' array_stmt in + let res_trans_idx = instruction trans_state' idx_stmt in + let (a_exp, a_typ) = extract_exp_from_list res_trans_a.exps + "WARNING: In ArraySubscriptExpr there was a problem in translating array exp.\n" in + let (i_exp, i_typ) = extract_exp_from_list res_trans_idx.exps + "WARNING: In ArraySubscriptExpr there was a problem in translating index exp.\n" in + let array_exp = Sil.Lindex (a_exp, i_exp) in + + let root_nodes = + if res_trans_a.root_nodes <> [] + then res_trans_a.root_nodes + else res_trans_idx.root_nodes in + let leaf_nodes = + if res_trans_idx.leaf_nodes <> [] + then res_trans_idx.leaf_nodes + else res_trans_a.leaf_nodes in + + if res_trans_idx.root_nodes <> [] + then + list_iter + (fun n -> Cfg.Node.set_succs_exn n res_trans_idx.root_nodes []) + res_trans_a.leaf_nodes; + + (* Note the order of res_trans_idx.ids @ res_trans_a.ids is important. *) + (* We expect to use only res_trans_idx.ids in construction of other operation. *) + (* res_trans_a.ids is passed to be Removed.*) + { root_nodes = root_nodes; + leaf_nodes = leaf_nodes; + ids = res_trans_idx.ids @ res_trans_a.ids; + instrs = res_trans_a.instrs @ res_trans_idx.instrs; + exps = [(array_exp, typ)]} + + and binaryOperator_trans trans_state binary_operator_info stmt_info expr_info stmt_list = + let bok = (Clang_ast_j.string_of_binary_operator_kind binary_operator_info.Clang_ast_t.boi_kind) in + Printing.log_out ~fmt:"Passing from BinaryOperator '%s' " bok; + Printing.log_out ~fmt:"pointer = '%s' " stmt_info.Clang_ast_t.si_pointer; + Printing.log_out ~fmt:"priority node free = '%s'.\n" (string_of_bool (PriorityNode.is_priority_free trans_state)); + let context = trans_state.context in + let parent_line_number = trans_state.parent_line_number in + let succ_nodes = trans_state.succ_nodes in + let sil_loc = get_sil_location stmt_info parent_line_number context in + let typ = CTypes_decl.qual_type_to_sil_type context.tenv expr_info.Clang_ast_t.ei_qual_type in + (match stmt_list with + | [s1; ImplicitCastExpr (stmt, [CompoundLiteralExpr (cle_stmt_info, stmts, expr_info)], _, cast_expr_info)] -> + let di, line_number = get_decl_ref_info s1 parent_line_number in + let line_number = get_line cle_stmt_info line_number in + let trans_state' = { trans_state with parent_line_number = line_number } in + let res_trans_tmp = initListExpr_trans trans_state' stmt_info expr_info di stmts in + { res_trans_tmp with leaf_nodes =[]} + | [s1; s2] -> (* Assumption: We expect precisely 2 stmt corresponding to the 2 operands*) + let rhs_owning_method = CTrans_utils.is_owning_method s2 in + (* NOTE: we create a node only if required. In that case this node *) + (* becomes the successor of the nodes that may be created when *) + (* translating the operands. *) + let trans_state_pri = PriorityNode.try_claim_priority_node trans_state stmt_info in + let line_number = get_line stmt_info parent_line_number in + let trans_state'' = { trans_state_pri with parent_line_number = line_number; succ_nodes =[]} in + let res_trans_e1 = exec_with_self_exception instruction trans_state'' s1 in + let res_trans_e2 = + (* translation of s2 is done taking care of block special case *) + exec_with_block_priority_exception (exec_with_self_exception instruction) trans_state'' s2 stmt_info in + let (sil_e1, sil_typ1) = extract_exp_from_list res_trans_e1.exps "\nWARNING: Missing LHS operand in BinOp. Returning -1. Fix needed...\n" in + let (sil_e2, sil_typ2) = extract_exp_from_list res_trans_e2.exps "\nWARNING: Missing RHS operand in BinOp. Returning -1. Fix needed...\n" in + let exp_op, instr, ids_bin = + CArithmetic_trans.binary_operation_instruction context binary_operator_info sil_e1 typ sil_e2 sil_loc rhs_owning_method in + let instrs = res_trans_e1.instrs@res_trans_e2.instrs@instr in + let ids = res_trans_e1.ids@res_trans_e2.ids@ids_bin in + + (* Create a node if the priority if free and there are instructions *) + let creating_node = + (PriorityNode.own_priority_node trans_state_pri.priority stmt_info) && + (list_length instrs >0) in + + let instrs_after_assign, assign_ids, exp_to_parent = + if (is_binary_assign_op binary_operator_info) + && ((not creating_node) || (is_return_temp trans_state.continuation)) then ( + (* We are in this case when an assignment is inside *) + (* another operator that creates a node. Eg. another *) + (* assignment. *) + (* As no node is created here ids are passed to the parent *) + let id = Ident.create_fresh Ident.knormal in + let res_instr = [Sil.Letderef (id, sil_e1, sil_typ1, sil_loc)] in + instrs@res_instr, ids@[id], Sil.Var id + ) else ( + instrs, ids, exp_op) in + + let instruction_to_ancestor, ids_to_ancestor, succ_nodes' = + if creating_node then ( + let node_kind = + Cfg.Node.Stmt_node ("BinaryOperatorStmt: "^ + (CArithmetic_trans.bin_op_to_string binary_operator_info)) in + let node_bin_op = create_node node_kind [] [] sil_loc context in + Cfg.Node.set_succs_exn node_bin_op succ_nodes []; + let succ_nodes'' = [node_bin_op] in + (* If a node was created, ids are passed to the parent*) + (* if the binop is in the translation of a condition.*) + (* Otherwise ids are added to the node. *) + (* ids_parent/ids_nodes are the list of ids for the parent/node respectively.*) + (* They are computed with continuation which tells us *) + (* if we are translating a condition or not *) + let ids_parent = ids_to_parent trans_state.continuation assign_ids in + let ids_node = ids_to_node trans_state.continuation assign_ids in + list_iter (fun n -> Cfg.Node.append_instrs_temps n instrs_after_assign ids_node) succ_nodes''; + [], ids_parent, succ_nodes'' + ) else ( + instrs_after_assign, assign_ids, succ_nodes) in + + let e1_has_nodes = res_trans_e1.root_nodes <> [] + and e2_has_nodes = res_trans_e2.root_nodes <> [] in + + let e1_succ_nodes = + if e2_has_nodes then res_trans_e2.root_nodes else succ_nodes' in + list_iter (fun n -> Cfg.Node.set_succs_exn n e1_succ_nodes []) res_trans_e1.leaf_nodes; + list_iter (fun n -> Cfg.Node.set_succs_exn n succ_nodes' []) res_trans_e2.leaf_nodes; + + let root_nodes_to_ancestor = match e1_has_nodes, e2_has_nodes with + | false, false -> succ_nodes' + | true, _ -> res_trans_e1.root_nodes + | false, true -> res_trans_e2.root_nodes in + + let leaf_nodes_to_ancestor = + if creating_node then succ_nodes' + else if e2_has_nodes then res_trans_e2.leaf_nodes + else res_trans_e1.leaf_nodes in + + Printing.log_out ~fmt:"....BinaryOperator '%s' " bok; + Printing.log_out ~fmt:"has ids_to_ancestor |ids_to_ancestor|=%s " + (string_of_int (list_length ids_to_ancestor)); + Printing.log_out ~fmt:" |nodes_e1|=%s .\n" + (string_of_int (list_length res_trans_e1.root_nodes)); + Printing.log_out ~fmt:" |nodes_e2|=%s .\n" + (string_of_int (list_length res_trans_e2.root_nodes)); + list_iter (fun id -> Printing.log_out ~fmt:" ... '%s'\n" + (Ident.to_string id)) ids_to_ancestor; + { root_nodes = root_nodes_to_ancestor; + leaf_nodes = leaf_nodes_to_ancestor; + ids = ids_to_ancestor; + instrs = instruction_to_ancestor; + exps = [(exp_to_parent, sil_typ1)] } + | _ -> assert false) (* Binary operator should have two operands*) + + and callExpr_trans trans_state si stmt_list expr_info = + let pln = trans_state.parent_line_number in + let context = trans_state.context in + let function_type = CTypes_decl.get_type_from_expr_info expr_info context.tenv in + Printing.log_out ~fmt:"Passing from CallExpr '%s'.\n" si.Clang_ast_t.si_pointer; + let procname = Cfg.Procdesc.get_proc_name context.procdesc in + let sil_loc = get_sil_location si pln context in + let fun_exp_stmt, params_stmt = (match stmt_list with (* First stmt is the function expr and the rest are params*) + | fe:: params -> fe, params + | _ -> assert false) in + let trans_state_pri = PriorityNode.try_claim_priority_node trans_state si in + (* claim priority if no ancestors has claimed priority before *) + let line_number = get_line si pln in + let context_callee = { context with CContext.is_callee_expression = true } in + let trans_state_callee = { trans_state_pri with context = context_callee; parent_line_number = line_number; succ_nodes = []} in + let is_call_to_block = objc_exp_of_type_block fun_exp_stmt in + let res_trans_callee = instruction trans_state_callee fun_exp_stmt in + (* As we may have nodes coming from different parameters we need to *) + (* call instruction for each parameter and collect the results *) + (* afterwards. The 'instructions' function does not do that *) + let trans_state_param = + { trans_state_pri with parent_line_number = line_number; succ_nodes = [] } in + let res_trans_par = + let l = list_map (fun i -> exec_with_self_exception instruction trans_state_param i) params_stmt in + let rt = collect_res_trans (res_trans_callee :: l) in + { rt with exps = list_tl rt.exps } in + let (sil_fe, typ_fe) = extract_exp_from_list res_trans_callee.exps + "WARNING: The translation of fun_exp did not return an expression. Returning -1. NEED TO BE FIXED" in + let sil_fe, is_cf_retain_release = CTrans_models.builtin_predefined_model fun_exp_stmt sil_fe in + if CTrans_models.is_assert_log sil_fe then + if Config.report_assertion_failure then + CTrans_utils.trans_assertion_failure sil_loc context + else + CTrans_utils.trans_assume_false sil_loc context trans_state.succ_nodes + else + let callee_pname_opt = + match sil_fe with + | Sil.Const (Sil.Cfun pn) -> + Some pn + | _ -> None (* function pointer *) in + let act_params = if list_length res_trans_par.exps = list_length params_stmt then + res_trans_par.exps + else (Printing.log_err + "WARNING: stmt_list and res_trans_par.exps must have same size. NEED TO BE FIXED\n\n"; + fix_param_exps_mismatch params_stmt res_trans_par.exps) in + let act_params = if is_cf_retain_release then + (Sil.Const (Sil.Cint Sil.Int.one), Sil.Tint Sil.IBool):: act_params + else act_params in + match CTrans_utils.builtin_trans trans_state_pri sil_loc si function_type callee_pname_opt with + | Some builtin -> builtin + | None -> + let ret_id, call_instr = + match cast_trans context act_params sil_loc callee_pname_opt function_type with + | Some (id, instr, _) -> [id], instr + | None -> + let ret_id = if (Sil.typ_equal function_type Sil.Tvoid) then [] + else [Ident.create_fresh Ident.knormal] in + let call_flags = { Sil.cf_virtual = false; Sil.cf_noreturn = false; Sil.cf_is_objc_block = is_call_to_block; } in + let call_instr = Sil.Call(ret_id, sil_fe, act_params, sil_loc, call_flags) in + ret_id, call_instr in + let ids = res_trans_par.ids@ret_id in + let instrs = res_trans_par.instrs @ [call_instr] in + let nname = "Call "^(Sil.exp_to_string sil_fe) in + let res_trans_tmp = { res_trans_par with ids = ids; instrs = instrs; exps =[]} in + let res_trans_to_parent = + PriorityNode.compute_results_to_parent trans_state_pri sil_loc nname si res_trans_tmp in + (match callee_pname_opt with + | Some callee_pname -> + if not (SymExec.function_is_builtin callee_pname) then + begin + Cg.add_edge context.cg procname callee_pname; + try + let callee_ms = CMethod_signature.find callee_pname in + CMethod_trans.create_local_procdesc context.cfg context.tenv callee_ms [] [] false + with Not_found -> + CMethod_trans.create_external_procdesc context.cfg callee_pname false None + end + | None -> ()); + (match ret_id with + | [] -> { res_trans_to_parent with exps =[] } + | [ret_id'] -> { res_trans_to_parent with exps =[(Sil.Var ret_id', function_type)] } + | _ -> assert false) (* by construction of red_id, we cannot be in this case *) + + and objCMessageExpr_trans trans_state si obj_c_message_expr_info stmt_list expr_info = + Printing.log_out ~fmt:"Passing from ObjMessageExpr '%s'.\n" si.Clang_ast_t.si_pointer; + let context = trans_state.context in + let parent_line_number = trans_state.parent_line_number in + let sil_loc = get_sil_location si parent_line_number context in + let selector, receiver_kind = get_selector_receiver obj_c_message_expr_info in + let is_alloc_or_new = (selector = CFrontend_config.alloc) || (selector = CFrontend_config.new_str) in + Printing.log_out ~fmt:"\n!!!!!!! Calling with selector = '%s' " selector; + Printing.log_out ~fmt:" receiver_kind= '%s'\n\n" (Clang_ast_j.string_of_receiver_kind receiver_kind); + let method_type = CTypes_decl.get_type_from_expr_info expr_info context.tenv in + let ret_id = if Sil.typ_equal method_type Sil.Tvoid then [] + else [Ident.create_fresh Ident.knormal] in + let trans_state_pri = PriorityNode.try_claim_priority_node trans_state si in + let line_number = get_line si parent_line_number in + let trans_state_param = + { trans_state_pri with parent_line_number = line_number; succ_nodes = [] } in + let obj_c_message_expr_info, res_trans_par = + (match stmt_list with + | stmt:: rest -> + let obj_c_message_expr_info, fst_res_trans = + (try + let fst_res_trans = instruction trans_state_param stmt in + obj_c_message_expr_info, fst_res_trans + with Self.SelfClassException class_name -> + let obj_c_message_expr_info = Ast_expressions.make_obj_c_message_expr_info_class selector + (Ast_expressions.create_qual_type class_name) in + obj_c_message_expr_info, empty_res_trans) in + let l = list_map (fun i -> exec_with_self_exception instruction trans_state_param i) rest in + obj_c_message_expr_info, collect_res_trans (fst_res_trans :: l) + | [] -> obj_c_message_expr_info, empty_res_trans) in + let (class_type, _, _) = CMethod_trans.get_class_selector_instance context obj_c_message_expr_info res_trans_par.exps in + if (selector = CFrontend_config.class_method && CTypes.is_class method_type) then + raise (Self.SelfClassException class_type) + else if is_alloc_or_new then + new_or_alloc_trans trans_state_pri sil_loc si class_type selector + else if (CTrans_models.is_handleFailureInMethod selector) then + if Config.report_assertion_failure then + CTrans_utils.trans_assertion_failure sil_loc context + else + CTrans_utils.trans_assume_false sil_loc context trans_state.succ_nodes + else + let procname = Cfg.Procdesc.get_proc_name context.procdesc in + let callee_name, method_call_type = + CMethod_trans.get_callee_objc_method context obj_c_message_expr_info res_trans_par.exps in + let res_trans_par = Self.add_self_parameter_for_super_instance context procname sil_loc + obj_c_message_expr_info res_trans_par in + let is_virtual = method_call_type = CMethod_trans.MCVirtual in + Cg.add_edge context.cg procname callee_name; + let call_flags = { Sil.cf_virtual = is_virtual; Sil.cf_noreturn = false; Sil.cf_is_objc_block = false; } in + let param_exps, instr_block_param, ids_block_param = extract_block_from_tuple procname res_trans_par.exps sil_loc in + let stmt_call = Sil.Call(ret_id, (Sil.Const (Sil.Cfun callee_name)), param_exps, sil_loc, call_flags) in + let nname = "Message Call: "^selector in + let res_trans_tmp = { + res_trans_par with + ids = ret_id @ res_trans_par.ids @ids_block_param ; + instrs = res_trans_par.instrs@instr_block_param@[stmt_call]; + exps =[] + } in + let res_trans_to_parent = + PriorityNode.compute_results_to_parent trans_state_pri sil_loc nname si res_trans_tmp in + (match ret_id with + | [] -> { res_trans_to_parent with exps = [] } + | [ret_id'] -> { res_trans_to_parent with exps = [(Sil.Var ret_id', method_type)] } + | _ -> assert false) (* by construction of red_id, we cannot be in this case *) + + and dispatch_function_trans trans_state stmt_info stmt_list ei n = + Printing.log_out "\n Call to a dispatch function treated as special case...\n"; + let procname = Cfg.Procdesc.get_proc_name trans_state.context.procdesc in + let pvar = CFrontend_utils.General_utils.get_next_block_pvar procname in + CContext.LocalVars.add_pointer_var stmt_info.Clang_ast_t.si_pointer pvar trans_state.context; + let transformed_stmt, qt = + Ast_expressions.translate_dispatch_function (Sil.pvar_to_string pvar) stmt_info stmt_list ei n in + let typ = CTypes_decl.qual_type_to_sil_type trans_state.context.tenv qt in + let loc = get_sil_location stmt_info trans_state.parent_line_number trans_state.context in + let res_state = instruction trans_state transformed_stmt in + (* Add declare locals to the first node *) + list_iter (fun n -> Cfg.Node.prepend_instrs_temps n [Sil.Declare_locals([(pvar, typ)], loc)] []) res_state.root_nodes; + let preds = list_flatten (list_map (fun n -> Cfg.Node.get_preds n) trans_state.succ_nodes) in + (* Add nullify of the temp block var to the last node (predecessor or the successor nodes)*) + list_iter (fun n -> Cfg.Node.append_instrs_temps n [Sil.Nullify(pvar, loc, true)] []) preds; + res_state + + and compoundStmt_trans trans_state stmt_info stmt_list = + Printing.log_out ~fmt:"Passing from CompoundStmt '%s'.\n" stmt_info.Clang_ast_t.si_pointer; + let line_number = get_line stmt_info trans_state.parent_line_number in + let trans_state' = { trans_state with parent_line_number = line_number } in + instructions trans_state' (list_rev stmt_list) + + and conditionalOperator_trans trans_state stmt_info stmt_list expr_info = + let context = trans_state.context in + let parent_line_number = trans_state.parent_line_number in + let succ_nodes = trans_state.succ_nodes in + let procname = Cfg.Procdesc.get_proc_name context.procdesc in + let mk_temp_var id = + Sil.mk_pvar (Mangled.from_string ("SIL_temp_conditional___"^(string_of_int id))) procname in + Printing.log_out ~fmt:"Passing from ConditionalOperator '%s' \n" stmt_info.Clang_ast_t.si_pointer; + let sil_loc = get_sil_location stmt_info parent_line_number context in + let line_number = get_line stmt_info parent_line_number in + (* We have two different kind of join type for conditional operator. *) + (* If it's a simple conditional operator then we use a standard join *) + (* node. If it's a nested conditional operator then we need to *) + (* assign the temp variable of the inner conditional to the outer *) + (* conditional. In that case we use a statement node for join. This *) + (* node make the assignment of the temp variables. *) + let compute_join_node typ = + let join_node' = match succ_nodes with + | [n] when is_join_node n -> + let n'= create_node (Cfg.Node.Stmt_node "Temp Join Node") [] [] sil_loc context in + let id = Ident.create_fresh Ident.knormal in + let pvar = mk_temp_var (Cfg.Node.get_id n) in + let pvar' = mk_temp_var (Cfg.Node.get_id n') in + let ilist =[Sil.Letderef (id, Sil.Lvar pvar', typ, sil_loc); + Sil.Declare_locals([(pvar, typ)], sil_loc); + Sil.Set (Sil.Lvar pvar, typ, Sil.Var id, sil_loc); + Sil.Nullify(pvar', sil_loc, true)] in + Cfg.Node.append_instrs_temps n' ilist [id]; n' + | _ -> create_node (Cfg.Node.Join_node) [] [] sil_loc context in + Cfg.Node.set_succs_exn join_node' succ_nodes []; + join_node' in + let do_branch branch stmt typ prune_nodes join_node pvar = + let trans_state' = { trans_state with succ_nodes = [join_node]; parent_line_number = line_number } in + let res_trans_b = + exec_with_priority_exception trans_state' stmt instruction in + let node_b = res_trans_b.root_nodes in + let (e', e'_typ) = extract_exp_from_list res_trans_b.exps + "\nWARNING: Missing branch expression for Conditional operator. Need to be fixed\n" in + (* If e' is the address of a prog var, we need to get its value in a temp before assign it to temp_var*) + let e'', instr_e'', id_e'' = match e' with + | Sil.Lvar _ -> + let id = Ident.create_fresh Ident.knormal in + Sil.Var id,[Sil.Letderef (id, e', typ, sil_loc)], [id] + | _ -> e', [], [] in + let set_temp_var = [Sil.Declare_locals([(pvar, typ)], sil_loc); Sil.Set (Sil.Lvar pvar, typ, e'', sil_loc)] in + let nodes_branch = (match node_b, is_join_node join_node with + | [], _ -> let n = create_node (Cfg.Node.Stmt_node "ConditinalStmt Branch" ) (res_trans_b.ids@id_e'') (res_trans_b.instrs @ instr_e'' @ set_temp_var) sil_loc context in + Cfg.Node.set_succs_exn n [join_node] []; + [n] + | _, true -> + list_iter + (fun n' -> + (* If there is a node with instructions we need to only *) + (* add the set of the temp variable *) + if not (is_prune_node n') then + Cfg.Node.append_instrs_temps n' + (res_trans_b.instrs @ instr_e''@ set_temp_var) + (res_trans_b.ids @ id_e'') + ) node_b; + node_b + | _, false -> node_b) in + let prune_nodes_t, prune_nodes_f = list_partition is_true_prune_node prune_nodes in + let prune_nodes' = if branch then prune_nodes_t else prune_nodes_f in + list_iter (fun n -> Cfg.Node.set_succs_exn n nodes_branch []) prune_nodes' in + (match stmt_list with + | [cond; exp1; exp2] -> + let typ = + CTypes_decl.qual_type_to_sil_type context.tenv expr_info.Clang_ast_t.ei_qual_type in + let join_node = compute_join_node typ in + let pvar = mk_temp_var (Cfg.Node.get_id join_node) in + let continuation' = mk_cond_continuation trans_state.continuation in + let trans_state' = { trans_state with continuation = continuation'; parent_line_number = line_number; succ_nodes =[]} in + let res_trans_cond = exec_with_priority_exception trans_state' cond cond_trans in + (* Note: by contruction prune nodes are leafs_nodes_cond *) + do_branch true exp1 typ res_trans_cond.leaf_nodes join_node pvar; + do_branch false exp2 typ res_trans_cond.leaf_nodes join_node pvar; + let id = Ident.create_fresh Ident.knormal in + let instrs =[Sil.Letderef (id, Sil.Lvar pvar, typ, sil_loc); Sil.Nullify (pvar, sil_loc, true)] in + { root_nodes = res_trans_cond.root_nodes; + leaf_nodes = [join_node]; + ids = [id]; + instrs = instrs; + exps = [(Sil.Var id, typ)] + } + | _ -> assert false) + + (* Translate a condition for if/loops statement. It shorts-circuit and/or. *) + (* The invariant is that the translation of a condition always contains (at least) *) + (* the prune nodes. Moreover these are always the leaf nodes of the translation. *) + and cond_trans trans_state cond = + let context = trans_state.context in + let parent_line_number = trans_state.parent_line_number in + let si, _ = Clang_ast_proj.get_stmt_tuple cond in + let sil_loc = get_sil_location si parent_line_number context in + let mk_prune_node b e ids ins = + create_prune_node b e ids ins sil_loc (Sil.Ik_if) context in + let extract_exp el = + extract_exp_from_list el + "\nWARNING: Missing expression for Conditional operator. Need to be fixed" in + (* this function translate cond without doing shortcircuit *) + let no_short_circuit_cond () = + Printing.log_out " No short-circuit condition\n"; + let res_trans_cond = + if is_null_stmt cond then { + empty_res_trans with exps = [(Sil.Const (Sil.Cint Sil.Int.one), (Sil.Tint Sil.IBool))] + } + (* Assumption: If it's a null_stmt, it is a loop with no bound, so we set condition to 1 *) + else + instruction trans_state cond in + let e', instrs' = define_condition_side_effects context res_trans_cond.exps res_trans_cond.instrs sil_loc in + let prune_t = mk_prune_node true e' res_trans_cond.ids instrs' in + let prune_f = mk_prune_node false e' res_trans_cond.ids instrs' in + list_iter (fun n' -> Cfg.Node.set_succs_exn n' [prune_t; prune_f] []) res_trans_cond.leaf_nodes; + let rnodes = if (list_length res_trans_cond.root_nodes) = 0 then + [prune_t; prune_f] + else res_trans_cond.root_nodes in + { root_nodes = rnodes; leaf_nodes =[prune_t; prune_f]; ids = res_trans_cond.ids; instrs = instrs'; exps = e' } in + + (* This function translate (s1 binop s2) doing shortcircuit for '&&' and '||' *) + (* At the high level it does cond_trans s1; cond_trans s2; glue_nodes *) + (* The glue_nodes partitions the prune nodes of s1's translation.*) + (* Some of them need to go to the statement to be executed after the *) + (* condition (prune_to_short_c) and others to the root nodes of the *) + (* translation of s2 (i.e., the case when we need to fully evaluate*) + (* the condition to decide its truth value). *) + let short_circuit binop s1 s2 = + let res_trans_s1 = cond_trans trans_state s1 in + let prune_nodes_t, prune_nodes_f = list_partition is_true_prune_node res_trans_s1.leaf_nodes in + let res_trans_s2 = cond_trans trans_state s2 in + (* prune_to_s2 is the prune node that is connected with the root node of the *) + (* translation of s2.*) + (* prune_to_short_c is the prune node that is connected directly with the branch *) + (* where the control flow goes in case of short circuit *) + let prune_to_s2, prune_to_short_c = (match binop with + | Sil.LAnd -> prune_nodes_t, prune_nodes_f + | Sil.LOr -> prune_nodes_f, prune_nodes_t + | _ -> assert false) in + list_iter (fun n -> Cfg.Node.set_succs_exn n res_trans_s2.root_nodes []) prune_to_s2; + let root_nodes_to_parent = + if (list_length res_trans_s1.root_nodes) = 0 then res_trans_s1.leaf_nodes else res_trans_s1.root_nodes in + let (exp1, typ1) = extract_exp res_trans_s1.exps in + let (exp2, typ2) = extract_exp res_trans_s2.exps in + let e_cond = Sil.BinOp (binop, exp1, exp2) in + { root_nodes = root_nodes_to_parent; + leaf_nodes = prune_to_short_c@res_trans_s2.leaf_nodes; + ids = res_trans_s1.ids@res_trans_s2.ids; + instrs = res_trans_s1.instrs@res_trans_s2.instrs; + exps = [(e_cond, typ1)] } in + Printing.log_out "Translating Condition for Conditional/Loop \n"; + match cond with + | BinaryOperator(si, [s1; s2], expr_info, boi) -> + (match boi.Clang_ast_t.boi_kind with + | `LAnd -> short_circuit (Sil.LAnd) s1 s2 + | `LOr -> short_circuit (Sil.LOr) s1 s2 + | _ -> no_short_circuit_cond ()) + | ParenExpr(_,[s], _) -> (* condition can be wrapped in parenthesys *) + cond_trans trans_state s + | _ -> no_short_circuit_cond () + + and ifStmt_trans trans_state stmt_info stmt_list = + Printing.log_out ~fmt:"Passing from IfStmt '%s' \n" stmt_info.Clang_ast_t.si_pointer; + let context = trans_state.context in + let pln = trans_state.parent_line_number in + let succ_nodes = trans_state.succ_nodes in + let sil_loc = get_sil_location stmt_info pln context in + let line_number = get_line stmt_info pln in + let join_node = create_node (Cfg.Node.Join_node) [] [] sil_loc context in + Cfg.Node.set_succs_exn join_node succ_nodes []; + let trans_state' = { trans_state with parent_line_number = line_number; succ_nodes = [join_node]} in + let do_branch branch stmt_branch prune_nodes = + (* leaf nodes are ignored here as they will be already attached to join_node *) + let res_trans_b = instruction trans_state' stmt_branch in + let nodes_branch = (match res_trans_b.root_nodes with + | [] -> [create_node (Cfg.Node.Stmt_node "IfStmt Branch" ) res_trans_b.ids res_trans_b.instrs sil_loc context] + | _ -> res_trans_b.root_nodes) in + let prune_nodes_t, prune_nodes_f = list_partition is_true_prune_node prune_nodes in + let prune_nodes' = if branch then prune_nodes_t else prune_nodes_f in + list_iter (fun n -> Cfg.Node.set_succs_exn n nodes_branch []) prune_nodes'; + res_trans_b.ids in + (match stmt_list with + | [null_stmt; cond; stmt1; stmt2] -> (* Note: for the moment we don't do anything with the null_stmt/decl*) + (* set the flat to inform that we are translating a condition of a if *) + let continuation' = mk_cond_continuation trans_state.continuation in + let trans_state'' = { trans_state with continuation = continuation'; succ_nodes = []; parent_line_number = line_number } in + let res_trans_cond = cond_trans trans_state'' cond in + (* Note: by contruction prune nodes are leafs_nodes_cond *) + let ids_t = do_branch true stmt1 res_trans_cond.leaf_nodes in + let ids_f = do_branch false stmt2 res_trans_cond.leaf_nodes in + { root_nodes = res_trans_cond.root_nodes; leaf_nodes =[join_node]; ids = res_trans_cond.ids@ids_t@ids_f; instrs =[]; exps =[] } + | _ -> assert false) + + (* Assumption: the CompoundStmt can be made of different stmts, not just CaseStmts *) + and switchStmt_trans trans_state stmt_info switch_stmt_list = + Printing.log_out ~fmt:"\nPassing from `SwitchStmt '%s' \n" stmt_info.Clang_ast_t.si_pointer; + let context = trans_state.context in + let pln = trans_state.parent_line_number in + let succ_nodes = trans_state.succ_nodes in + let continuation = trans_state.continuation in + let sil_loc = get_sil_location stmt_info pln context in + (match switch_stmt_list with + | [_; cond; CompoundStmt(stmt_info, stmt_list)] -> + let trans_state_pri = PriorityNode.try_claim_priority_node trans_state stmt_info in + let trans_state' ={ trans_state_pri with succ_nodes = []} in + let res_trans_cond = instruction trans_state' cond in + let switch_special_cond_node = + create_node (Cfg.Node.Stmt_node "Switch_stmt") [] res_trans_cond.instrs sil_loc context in + let trans_state_no_pri = if PriorityNode.own_priority_node trans_state_pri.priority stmt_info then + { trans_state_pri with priority = Free } + else trans_state_pri in + let (switch_e_cond', switch_e_cond'_typ) = + extract_exp_from_list res_trans_cond.exps + "\nWARNING: The condition of the SwitchStmt is not singleton. Need to be fixed\n" in + let switch_exit_point = succ_nodes in + let continuation' = + match continuation with + | Some cont -> Some { cont with break = switch_exit_point } + | None -> Some { break = switch_exit_point; continue = []; return_temp = false } in + let trans_state'' = { trans_state_no_pri with continuation = continuation'} in + let merge_into_cases stmt_list = (* returns list_of_cases * before_any_case_instrs *) + let rec aux rev_stmt_list acc cases = + (match rev_stmt_list with + | CaseStmt(info, a :: b :: (CaseStmt x) :: c) :: rest -> (* case x: case y: ... *) + if c <> [] then assert false; (* empty case with nested case, then followed by some instructions *) + let rest' = [CaseStmt(info, a :: b :: [])] @ rest in + let rev_stmt_list' = (CaseStmt x) :: rest' in + aux rev_stmt_list' acc cases + | CaseStmt(info, a :: b :: (DefaultStmt x) :: c) :: rest -> + (* case x: default: ... *) + if c <> [] then assert false; (* empty case with nested case, then followed by some instructions *) + let rest' = [CaseStmt(info, a :: b :: [])] @ rest in + let rev_stmt_list' = (DefaultStmt x) :: rest' in + aux rev_stmt_list' acc cases + | DefaultStmt(info, (CaseStmt x) :: c) :: rest -> (* default: case x: ... *) + if c <> [] then assert false; (* empty case with nested case, then followed by some instructions *) + let rest' = [DefaultStmt(info, [])] @ rest in + let rev_stmt_list' = (CaseStmt x) :: rest' in + aux rev_stmt_list' acc cases + | CaseStmt(info, a :: b :: c) :: rest -> + aux rest [] (CaseStmt(info, a :: b :: c@acc):: cases) + | DefaultStmt(info, c) :: rest -> (* default is always the last in the list *) + aux rest [] (DefaultStmt(info, c@acc) :: cases) + | x :: rest -> + aux rest (x:: acc) cases + | [] -> + cases, acc) in + aux (list_rev stmt_list) [] [] in + let list_of_cases, pre_case_stmts = merge_into_cases stmt_list in + let rec connected_instruction rev_instr_list successor_nodes = + (* returns the entry point of the translated set of instr *) + match rev_instr_list with + | [] -> successor_nodes + | instr :: rest -> + let trans_state''' = { trans_state'' with succ_nodes = successor_nodes } in + let res_trans_instr = instruction trans_state''' instr in + let instr_entry_points = res_trans_instr.root_nodes in + connected_instruction rest instr_entry_points in + let rec translate_and_connect_cases cases next_nodes next_prune_nodes = + let create_prune_nodes_for_case case = + match case with + | CaseStmt(stmt_info, case_const:: _:: _) -> + let trans_state_pri = + PriorityNode.try_claim_priority_node trans_state'' stmt_info in + let res_trans_case_const = instruction trans_state_pri case_const in + let e_const = res_trans_case_const.exps in + let e_const' = + match e_const with + | [(head, typ)] -> head + | _ -> assert false in + let sil_eq_cond = Sil.BinOp(Sil.Eq, switch_e_cond', e_const') in + let sil_loc = get_sil_location stmt_info pln context in + let true_prune_node = + create_prune_node true [(sil_eq_cond, switch_e_cond'_typ)] + res_trans_case_const.ids res_trans_case_const.instrs + sil_loc (Sil.Ik_switch) context in + let false_prune_node = + create_prune_node false [(sil_eq_cond, switch_e_cond'_typ)] + res_trans_case_const.ids res_trans_case_const.instrs + sil_loc (Sil.Ik_switch) context in + (true_prune_node, false_prune_node) + | _ -> assert false in + match cases with (* top-down to handle default cases *) + | [] -> next_nodes, next_prune_nodes + | CaseStmt(stmt_info, _ :: _ :: case_content) as case :: rest -> + let last_nodes, last_prune_nodes = translate_and_connect_cases rest next_nodes next_prune_nodes in + let case_entry_point = connected_instruction (list_rev case_content) last_nodes in + (* connects between cases, then continuation has priority about breaks *) + let prune_node_t, prune_node_f = create_prune_nodes_for_case case in + Cfg.Node.set_succs_exn prune_node_t case_entry_point []; + Cfg.Node.set_succs_exn prune_node_f last_prune_nodes []; + case_entry_point, [prune_node_t; prune_node_f] + | DefaultStmt(stmt_info, default_content) :: rest -> + let sil_loc = get_sil_location stmt_info pln context in + let placeholder_entry_point = + create_node (Cfg.Node.Stmt_node "DefaultStmt_placeholder") [] [] sil_loc context in + let last_nodes, last_prune_nodes = translate_and_connect_cases rest next_nodes [placeholder_entry_point] in + let default_entry_point = connected_instruction (list_rev default_content) last_nodes in + Cfg.Node.set_succs_exn placeholder_entry_point default_entry_point []; + default_entry_point, last_prune_nodes + | _ -> assert false in + let top_entry_point, top_prune_nodes = translate_and_connect_cases list_of_cases succ_nodes succ_nodes in + let _ = connected_instruction (list_rev pre_case_stmts) top_entry_point in + Cfg.Node.set_succs_exn switch_special_cond_node top_prune_nodes []; + let top_nodes = + match res_trans_cond.root_nodes with + | [] -> (* here if no root or if the translation of cond needed priority *) + [switch_special_cond_node] + | _ -> + list_iter (fun n' -> Cfg.Node.set_succs_exn n' [switch_special_cond_node] []) res_trans_cond.leaf_nodes; + res_trans_cond.root_nodes in + list_iter (fun n' -> Cfg.Node.append_instrs_temps n' [] res_trans_cond.ids) succ_nodes; (* succ_nodes will remove the temps *) + { root_nodes = top_nodes; leaf_nodes = succ_nodes; ids = []; instrs = []; exps =[]} + | _ -> assert false) + + and stmtExpr_trans trans_state stmt_info stmt_list expr_info = + let context = trans_state.context in + Printing.log_out ~fmt:"Passing from StmtExpr '%s'.\n" stmt_info.Clang_ast_t.si_pointer; + let stmt = extract_stmt_from_singleton stmt_list "ERROR: StmtExpr should have only one statement.\n" in + let res_trans_stmt = instruction trans_state stmt in + let idl = res_trans_stmt.ids in + let exps'= list_rev res_trans_stmt.exps in + (match exps' with + | (last, typ):: _ -> + (* The StmtExpr contains a single CompoundStmt node, which it evaluates and *) + (* takes the value of the last subexpression.*) + (* Exp returned by StmtExpr is always a RValue. So we need to assign to a temp and return the temp.*) + let id = Ident.create_fresh Ident.knormal in + let loc = get_sil_location stmt_info trans_state.parent_line_number context in + let instr' = Sil.Letderef (id, last, typ, loc) in + { root_nodes = res_trans_stmt.root_nodes; + leaf_nodes = res_trans_stmt.leaf_nodes; + ids = id:: idl; + instrs = res_trans_stmt.instrs@[instr']; + exps = [(Sil.Var id, typ)]} + | _ -> assert false) + + and loop_instruction trans_state loop_kind stmt_info = + let outer_continuation = trans_state.continuation in + let context = trans_state.context in + let pln = trans_state.parent_line_number in + let succ_nodes = trans_state.succ_nodes in + let sil_loc = get_sil_location stmt_info pln context in + let line_number = get_line stmt_info pln in + let join_node = create_node (Cfg.Node.Join_node) [] [] sil_loc context in + let continuation = Some { break = succ_nodes; continue = [join_node]; return_temp = false } in + (* set the flat to inform that we are translating a condition of a if *) + let continuation_cond = mk_cond_continuation outer_continuation in + let init_incr_nodes = + match loop_kind with + | Loops.For (init, cond, incr, body) -> + let trans_state' = { trans_state with succ_nodes = [join_node]; continuation = continuation; parent_line_number = line_number } in + let res_trans_init = instruction trans_state' init in + let res_trans_incr = instruction trans_state' incr in + Some (res_trans_init.root_nodes, res_trans_incr.root_nodes) + | _ -> None in + let cond_stmt = Loops.get_cond loop_kind in + let cond_line_number = get_line (fst (Clang_ast_proj.get_stmt_tuple cond_stmt)) line_number in + let trans_state_cond = { trans_state with continuation = continuation_cond; parent_line_number = cond_line_number; succ_nodes = [] } in + let res_trans_cond = cond_trans trans_state_cond cond_stmt in + let body_succ_nodes = + match loop_kind with + | Loops.For _ -> (match init_incr_nodes with | Some (nodes_init, nodes_incr) -> nodes_incr | None -> assert false) + | Loops.While _ -> [join_node] + | Loops.DoWhile _ -> res_trans_cond.root_nodes in + let body_continuation = match continuation, init_incr_nodes with + | Some c, Some (nodes_init, nodes_incr) -> + Some { c with continue = nodes_incr } + | _ -> continuation in + let res_trans_body = + let trans_state_body = + { trans_state with + succ_nodes = body_succ_nodes; + continuation = body_continuation; + parent_line_number = line_number } in + instruction trans_state_body (Loops.get_body loop_kind) in + let join_succ_nodes = + match loop_kind with + | Loops.For _ | Loops.While _ -> res_trans_cond.root_nodes + | Loops.DoWhile _ -> res_trans_body.root_nodes in + (* Note: prune nodes are by contruction the res_trans_cond.leaf_nodes *) + let prune_nodes_t, prune_nodes_f = list_partition is_true_prune_node res_trans_cond.leaf_nodes in + let prune_t_succ_nodes = + match loop_kind with + | Loops.For _ | Loops.While _ -> res_trans_body.root_nodes + | Loops.DoWhile _ -> [join_node] in + Cfg.Node.set_succs_exn join_node join_succ_nodes []; + list_iter (fun n -> Cfg.Node.set_succs_exn n prune_t_succ_nodes []) prune_nodes_t; + list_iter (fun n -> Cfg.Node.set_succs_exn n succ_nodes []) prune_nodes_f; + let root_nodes = + match loop_kind with + | Loops.For _ -> + (match init_incr_nodes with | Some (nodes_init, nodes_incr) -> nodes_init | None -> assert false) + | Loops.While _ | Loops.DoWhile _ -> [join_node] in + root_nodes, prune_nodes_f + + and forStmt_trans trans_state init cond incr body stmt_info = + let for_kind = Loops.For (init, cond, incr, body) in + let root_nodes, leaf_nodes = loop_instruction trans_state for_kind stmt_info in + { empty_res_trans with root_nodes = root_nodes; leaf_nodes = leaf_nodes } + + and whileStmt_trans trans_state cond body stmt_info = + let while_kind = Loops.While (cond, body) in + let root_nodes, leaf_nodes = loop_instruction trans_state while_kind stmt_info in + { empty_res_trans with root_nodes = root_nodes; leaf_nodes = leaf_nodes } + + and doStmt_trans trans_state stmt_info cond body = + let dowhile_kind = Loops.DoWhile (cond, body) in + let root_nodes, leaf_nodes = loop_instruction trans_state dowhile_kind stmt_info in + { empty_res_trans with root_nodes = root_nodes; leaf_nodes = leaf_nodes } + + and objCForCollectionStmt_trans trans_state cond body stmt_info = + let cond = Ast_expressions.make_nondet_exp stmt_info in + let while_kind = Loops.While (cond, body) in + let root_nodes, leaf_nodes = loop_instruction trans_state while_kind stmt_info in + { empty_res_trans with root_nodes = root_nodes; leaf_nodes = leaf_nodes } + + (* Assumption: We expect precisely 2 stmt corresponding to the 2 operands. *) + and compoundAssignOperator trans_state stmt_info binary_operator_info expr_info stmt_list = + let context = trans_state.context in + let pln = trans_state.parent_line_number in + Printing.log_out ~fmt:"Passing from CompoundAssignOperator '%s'" stmt_info.Clang_ast_t.si_pointer; + Printing.log_out ~fmt:"'%s' .\n" + (Clang_ast_j.string_of_binary_operator_kind binary_operator_info.Clang_ast_t.boi_kind); + (* claim priority if no ancestors has claimed priority before *) + let trans_state_pri = PriorityNode.try_claim_priority_node trans_state stmt_info in + let sil_loc = get_sil_location stmt_info pln context in + let line_number = get_line stmt_info pln in + let sil_typ = + CTypes_decl.qual_type_to_sil_type context.tenv expr_info.Clang_ast_t.ei_qual_type in + (match stmt_list with + | [s1; s2] -> + let trans_state' = { trans_state_pri with succ_nodes = []; parent_line_number = line_number } in + let res_trans_s1 = instruction trans_state' s1 in + let res_trans_s2 = instruction trans_state' s2 in + let (lhs_e, lhs_typ) = extract_exp_from_list res_trans_s1.exps + "\nWARNING: Missing LHS operand in Compount Assign operator. Need Fixing.\n" in + let (sil_e2, sil_typ2) = extract_exp_from_list res_trans_s2.exps + "\nWARNING: Missing RHS operand in Compount Assign operator. Need Fixing.\n" in + let id_op, exp_op, instr_op = CArithmetic_trans.compound_assignment_binary_operation_instruction + binary_operator_info lhs_e sil_typ sil_e2 sil_loc in + let ids = res_trans_s1.ids@res_trans_s2.ids@id_op in + let instrs = res_trans_s1.instrs@res_trans_s2.instrs@instr_op in + let res_trans_tmp = { res_trans_s2 with ids = ids; instrs = instrs; exps =[]} in + let res_trans_to_parent = + PriorityNode.compute_results_to_parent trans_state_pri sil_loc "ComppoundAssignStmt" stmt_info res_trans_tmp in + + let trans_s1_succs = + if res_trans_to_parent.root_nodes <> [] + then res_trans_to_parent.root_nodes + else trans_state_pri.succ_nodes in + list_iter + (fun n -> Cfg.Node.set_succs_exn n trans_s1_succs []) + res_trans_s1.leaf_nodes; + + let instrs_to_parent', ids_to_parent', exp_to_parent' = + compute_instr_ids_exp_to_parent stmt_info res_trans_to_parent.instrs res_trans_to_parent.ids + [(exp_op, sil_typ)] lhs_e sil_typ sil_loc trans_state_pri.priority in + + let root_nodes = + if res_trans_s1.root_nodes <> [] + then res_trans_s1.root_nodes + else res_trans_to_parent.root_nodes in + { root_nodes = root_nodes; + leaf_nodes = res_trans_to_parent.leaf_nodes; + ids = ids_to_parent'; + instrs = instrs_to_parent'; + exps = exp_to_parent' } + | _ -> assert false) (* Compound assign statement should have two operands*) + + and initListExpr_trans trans_state stmt_info expr_info di_pointer stmts = + let context = trans_state.context in + let succ_nodes = trans_state.succ_nodes in + let rec collect_right_hand_exprs ts stmt = match stmt with + | InitListExpr (stmt_info , stmts , expr_info) -> list_flatten (list_map (collect_right_hand_exprs ts) stmts) + | _ -> + let trans_state' = { ts with succ_nodes = []} in + let res_trans_stmt = instruction trans_state' stmt in + let (exp, typ) = extract_exp_from_list res_trans_stmt.exps + "WARNING: in InitListExpr we expect the translation of each stmt to return one expression.\n" in + let is_owning_method = CTrans_utils.is_owning_method stmt in + let is_method_call = CTrans_utils.is_method_call stmt in + [(res_trans_stmt.ids, res_trans_stmt.instrs, exp, is_method_call, is_owning_method, typ)] in + let rec collect_left_hand_exprs e typ tns = match typ with + | (Sil.Tvar tn) -> + (match Sil.tenv_lookup context.tenv tn with + | Some (Sil.Tstruct _ as str) -> collect_left_hand_exprs e str tns + | Some ((Sil.Tvar typename) as tvar) -> + if (StringSet.mem (Sil.typename_to_string typename) tns) then ([[(e, typ)]]) + else (collect_left_hand_exprs e tvar (StringSet.add (Sil.typename_to_string typename) tns)); + | _ -> [[(e, typ)]] (*This case is an error, shouldn't happen.*)) + | (Sil.Tstruct (struct_fields, _, _, _, _, _, _) as type_struct) -> + let lh_exprs = list_map ( fun (fieldname, fieldtype, _) -> + Sil.Lfield (e, fieldname, type_struct) ) + struct_fields in + let lh_types = list_map ( fun (fieldname, fieldtype, _) -> fieldtype) + struct_fields in + list_map (fun (e, t) -> list_flatten (collect_left_hand_exprs e t tns)) (zip lh_exprs lh_types) + | Sil.Tarray (arrtyp, Sil.Const(Sil.Cint(n))) -> + let size = Sil.Int.to_int n in + let indices = list_range 0 (size - 1) in + let index_constants = list_map + (fun i -> (Sil.Const (Sil.Cint (Sil.Int.of_int i)))) + indices in + let lh_exprs = list_map + (fun index_expr -> Sil.Lindex (e, index_expr)) + index_constants in + let lh_types = replicate size arrtyp in + list_map (fun (e, t) -> list_flatten (collect_left_hand_exprs e t tns)) (zip lh_exprs lh_types) + | _ -> [ [(e, typ)] ] in + let trans_state_pri = PriorityNode.try_claim_priority_node trans_state stmt_info in + let var_type = CTypes_decl.qual_type_to_sil_type context.tenv expr_info.ei_qual_type in + let pvar = CContext.LocalVars.find_var_with_pointer context di_pointer in + let lh = list_flatten (collect_left_hand_exprs (Sil.Lvar pvar) var_type Utils.StringSet.empty) in + let rh = list_flatten (list_map (collect_right_hand_exprs trans_state_pri) stmts ) in + if (list_length rh != list_length lh) then ( + (* If the right hand expressions are not as many as the left hand expressions something's wrong *) + { empty_res_trans with root_nodes = succ_nodes } + ) else ( + (* Creating new instructions by assigning right hand side to left hand side expressions *) + let sil_loc = get_sil_location stmt_info trans_state_pri.parent_line_number context in + let big_zip = list_map + (fun ( (lh_exp, lh_t), (_, _, rh_exp, is_method_call, rhs_owning_method, rh_t) ) -> + let is_pointer_object = ObjcInterface_decl.is_pointer_to_objc_class context.CContext.tenv rh_t in + if !Config.arc_mode && (is_method_call || is_pointer_object) then + (* In arc mode, if it's a method call or we are initializing with a pointer to objc class *) + (* we need to add retain/release *) + let (e, instrs, ids) = + CArithmetic_trans.assignment_arc_mode context lh_exp lh_t rh_exp sil_loc rhs_owning_method true in + ([(e, lh_t)], instrs, ids) + else + ([], [Sil.Set (lh_exp, lh_t, rh_exp, sil_loc)], [])) + (zip lh rh) in + let rh_instrs = list_flatten ( list_map (fun (_, instrs, _, _, _, _) -> instrs) rh) in + let assign_instrs = list_flatten(list_map (fun (_, instrs, _) -> instrs) big_zip) in + let assign_ids = list_flatten(list_map (fun (_, _, ids) -> ids) big_zip) in + let instructions = list_append (rh_instrs) assign_instrs in + let rh_ids = list_flatten ( list_map (fun (ids, _, _, _, _, _) -> ids) rh) in + let ids = list_append (rh_ids) assign_ids in + if PriorityNode.own_priority_node trans_state_pri.priority stmt_info then ( + let node_kind = Cfg.Node.Stmt_node "InitListExp" in + let node = create_node node_kind (ids) (instructions) sil_loc context in + Cfg.Node.set_succs_exn node succ_nodes []; + { root_nodes =[node]; leaf_nodes =[]; ids = rh_ids; instrs = instructions; exps = [(Sil.Lvar pvar, var_type)]} + ) else { root_nodes =[]; leaf_nodes =[]; ids = rh_ids; instrs = instructions; exps = [(Sil.Lvar pvar, var_type)]}) + + and collect_all_decl trans_state var_decls next_nodes stmt_info = + let context = trans_state.context in + let pln = trans_state.parent_line_number in + let do_var_dec (di, var_name, qtype, vdi) next_node = + (match vdi.Clang_ast_t.vdi_init_expr with + | None -> { empty_res_trans with root_nodes = next_node } (* Nothing to do if no init expression *) + | Some (ImplicitValueInitExpr (_, stmt_list, _)) -> + (* Seems unclear what it does, so let's keep an eye on the stmts *) + (* and report a warning if it finds a non empty list of stmts *) + (match stmt_list with + | [] -> () + | _ -> Printing.log_stats "\n!!!!WARNING: found statement <\"ImplicitValueInitExpr\"> with non-empty stmt_list.\n"); + { empty_res_trans with root_nodes = next_node } + | Some (InitListExpr (stmt_info , stmts , expr_info)) + | Some (ExprWithCleanups(_, [InitListExpr (stmt_info , stmts , expr_info)], _, _)) -> + initListExpr_trans trans_state stmt_info expr_info di.Clang_ast_t.di_pointer stmts + | Some ie -> (*For init expr, translate how to compute it and assign to the var*) + let sil_loc = get_sil_location stmt_info pln context in + let trans_state_pri = PriorityNode.try_claim_priority_node trans_state stmt_info in + let next_node = + if PriorityNode.own_priority_node trans_state_pri.priority stmt_info then ( + let node_kind = Cfg.Node.Stmt_node "DeclStmt" in + let node = create_node node_kind [] [] sil_loc context in + Cfg.Node.set_succs_exn node next_node []; + [node] + ) else next_node in + let pvar = CContext.LocalVars.find_var_with_pointer context di.Clang_ast_t.di_pointer in + let line_number = get_line stmt_info pln in + (* if ie is a block the translation need to be done with the block special cases by exec_with_block_priority*) + let res_trans_ie = + let trans_state' = { trans_state_pri with succ_nodes = next_node; parent_line_number = line_number } in + exec_with_block_priority_exception (exec_with_self_exception instruction) trans_state' ie stmt_info in + let root_nodes = res_trans_ie.root_nodes in + let leaf_nodes = res_trans_ie.leaf_nodes in + let (sil_e1', ie_typ) = extract_exp_from_list res_trans_ie.exps + "WARNING: In DeclStmt we expect only one expression returned in recursive call\n" in + let rhs_owning_method = CTrans_utils.is_owning_method ie in + let _, instrs_assign, ids_assign = + if !Config.arc_mode && + (CTrans_utils.is_method_call ie || ObjcInterface_decl.is_pointer_to_objc_class context.CContext.tenv ie_typ) then + (* In arc mode, if it's a method call or we are initializing with a pointer to objc class *) + (* we need to add retain/release *) + let (e, instrs, ids) = + CArithmetic_trans.assignment_arc_mode context (Sil.Lvar pvar) ie_typ sil_e1' sil_loc rhs_owning_method true in + ([(e, ie_typ)], instrs, ids) + else ([], [Sil.Set (Sil.Lvar pvar, ie_typ, sil_e1', sil_loc)], []) in + let ids = res_trans_ie.ids@ids_assign in + let instrs = res_trans_ie.instrs@instrs_assign in + if PriorityNode.own_priority_node trans_state_pri.priority stmt_info then ( + let node = list_hd next_node in + Cfg.Node.append_instrs_temps node instrs ids; + list_iter (fun n -> Cfg.Node.set_succs_exn n [node] []) leaf_nodes; + + let root_nodes = if (list_length root_nodes) = 0 then next_node else root_nodes in + { root_nodes = root_nodes; leaf_nodes =[]; ids = ids; instrs = instrs; exps = [(Sil.Lvar pvar, ie_typ)]} + ) else { root_nodes = root_nodes; leaf_nodes =[]; ids = ids; instrs = instrs; exps =[(Sil.Lvar pvar, ie_typ)]}) in + match var_decls with + | [] -> { empty_res_trans with root_nodes = next_nodes } + | VarDecl(di, n, qt, vdi):: var_decls' -> + (* Var are defined when procdesc is created, here we only take care of initialization*) + let res_trans_vd = collect_all_decl trans_state var_decls' next_nodes stmt_info in + let res_trans_tmp = do_var_dec (di, n, qt, vdi) res_trans_vd.root_nodes in + { root_nodes = res_trans_tmp.root_nodes; leaf_nodes = []; + ids = res_trans_tmp.ids @ res_trans_vd.ids; + instrs = res_trans_tmp.instrs @ res_trans_vd.instrs; + exps = res_trans_tmp.exps @ res_trans_vd.exps } + | CXXRecordDecl _ :: var_decls' (*C++/C record decl treated in the same way *) + | RecordDecl _ :: var_decls' -> + (* Record declaration is done in the beginning when procdesc is defined.*) + collect_all_decl trans_state var_decls' next_nodes stmt_info + | _ -> assert false + + (* stmt_list is ignored because it contains the same instructions as *) + (* the init expression. We use the latter info. *) + and declStmt_trans trans_state decl_list stmt_info = + let succ_nodes = trans_state.succ_nodes in + Printing.log_out ~fmt:"Passing from DeclStmt '%s' \n" stmt_info.Clang_ast_t.si_pointer; + let line_number = get_line stmt_info trans_state.parent_line_number in + let trans_state' = { trans_state with parent_line_number = line_number } in + let res_trans = (match decl_list with + | VarDecl _ :: _ -> (* Case for simple variable declarations*) + collect_all_decl trans_state' decl_list succ_nodes stmt_info + | CXXRecordDecl _ :: var_decls (*C++/C record decl treated in the same way *) + | RecordDecl _:: var_decls -> (* Case for struct *) + collect_all_decl trans_state' decl_list succ_nodes stmt_info + | _ -> + Printing.log_stats + "WARNING: In DeclStmt found an unknown declaration type. RETURNING empty list of declaration. NEED TO BE FIXED"; + empty_res_trans) in + { res_trans with leaf_nodes = []} + + and objCPropertyRefExpr_trans trans_state stmt_info stmt_list = + Printing.log_out ~fmt:"Passing from ObjCPropertyRefExpr '%s' \n" stmt_info.Clang_ast_t.si_pointer; + (match stmt_list with + | [stmt] -> instruction trans_state stmt + | _ -> assert false) + + (* For OpaqueValueExpr we return the translation generated from its source expression*) + and opaqueValueExpr_trans trans_state stmt_info opaque_value_expr_info = + Printing.log_out ~fmt:"Passing from OpaqueValueExpr '%s' \n" stmt_info.Clang_ast_t.si_pointer; + (match opaque_value_expr_info.Clang_ast_t.ovei_source_expr with + | Some stmt -> instruction trans_state stmt + | _ -> assert false) + + (* NOTE: This translation has several hypothesis. Need to be verified when we have more*) + (* experience with this construct. Assert false will help to see if we encounter programs*) + (* that do not conform with this hypothesis.*) + (* Hypotheses:*) + (* 1. stmt_list is composed by 2 parts: the first element is a syntactic description of the*) + (* expression. The rest of the list has a semantic caracterization of the expression and*) + (* defines how that expression is going to be implemented at runtime. *) + (* 2. the semantic description is composed by a list of OpaqueValueExpr that define the *) + (* various expressions involved and one finale expression that define how the final value of*) + (* the PseudoObjectExpr is obtained. All the OpaqueValueExpr will be part of the last expression.*) + (* So they can be skipped. *) + (* For example: 'x.f = a' when 'f' is a property will be translated with a call to f's setter [x f:a]*) + (* the stmt_list will be [x.f = a; x; a; CallToSetter] Among all element of the list we only need*) + (* to translate the CallToSetter which is how x.f = a is actually implemented by the runtime.*) + and pseudoObjectExpr_trans trans_state stmt_info stmt_list = + let line_number = get_line stmt_info trans_state.parent_line_number in + let trans_state' = { trans_state with parent_line_number = line_number } in + Printing.log_out ~fmt:"Passing from PseudoObjectExpr '%s' \n" stmt_info.Clang_ast_t.si_pointer; + let rec do_semantic_elements el = + (match el with + | OpaqueValueExpr _ :: el' -> do_semantic_elements el' + | stmt:: _ -> instruction trans_state' stmt + | _ -> assert false) in + (match stmt_list with + | syntactic_form:: semantic_form -> + do_semantic_elements semantic_form + | _ -> assert false) + + (* Cast expression are treated the same apart from the cast operation kind*) + and cast_exprs_trans trans_state stmt_info stmt_list expr_info cast_expr_info is_objc_bridged = + let context = trans_state.context in + let pln = trans_state.parent_line_number in + Printing.log_out ~fmt:"Passing from CastExpr '%s' \n" stmt_info.Clang_ast_t.si_pointer; + let sil_loc = get_sil_location stmt_info pln context in + let stmt = extract_stmt_from_singleton stmt_list + "WARNING: In CastExpr There must be only one stmt defining the expression to be cast.\n" in + let line_number = get_line stmt_info pln in + let trans_state' = { trans_state with parent_line_number = line_number } in + let res_trans_stmt = instruction trans_state' stmt in + let typ = CTypes_decl.qual_type_to_sil_type context.tenv expr_info.Clang_ast_t.ei_qual_type in + let cast_kind = cast_expr_info.Clang_ast_t.cei_cast_kind in + (* This gives the differnece among cast operations kind*) + let cast_ids, cast_inst, cast_exp = cast_operation context cast_kind res_trans_stmt.exps typ sil_loc is_objc_bridged in + { root_nodes = res_trans_stmt.root_nodes; + leaf_nodes = res_trans_stmt.leaf_nodes; + ids = res_trans_stmt.ids @ cast_ids; + instrs = res_trans_stmt.instrs @ cast_inst; + exps = [(cast_exp, typ)] } + + (* function used in the computation for both Member_Expr and ObjCIVarRefExpr *) + and do_memb_ivar_ref_exp trans_state expr_info exp_stmt sil_loc nfield = + Printing.log_out ~fmt:"!!!!! Dealing with field '%s' @." nfield; + let res_trans_exp_stmt = instruction trans_state exp_stmt in + let (e, class_typ) = extract_exp_from_list res_trans_exp_stmt.exps + "WARNING: in MemberExpr we expect the translation of the stmt to return an expression\n" in + let class_typ = + (match class_typ with + | Sil.Tptr (t, _) -> CTypes_decl.expand_structured_type trans_state.context.tenv t + | t -> t) in + let typ = CTypes_decl.get_type_from_expr_info expr_info trans_state.context.tenv in + let exp = + (match class_typ with + | Sil.Tvoid -> Sil.exp_minus_one + | _ -> + Printing.log_out ~fmt:"Type is '%s' @." (Sil.typ_to_string class_typ); + ( match ObjcInterface_decl.find_field trans_state.context.tenv nfield (Some class_typ) false with + | Some (fn, _, _) -> Sil.Lfield (e, fn, class_typ) + | None -> assert false)) in + { res_trans_exp_stmt with + exps = [(exp, typ)] } + + and objCIvarRefExpr_trans trans_state stmt_info expr_info stmt_list obj_c_ivar_ref_expr_info = + Printing.log_out ~fmt:"Passing from ObjCIvarRefExpr '%s'\n" stmt_info.Clang_ast_t.si_pointer; + let sil_loc = get_sil_location stmt_info trans_state.parent_line_number trans_state.context in + let exp_stmt = extract_stmt_from_singleton stmt_list + "WARNING: in MemberExpr there must be only one stmt defining its expression.\n" in + let name_field = (match obj_c_ivar_ref_expr_info.Clang_ast_t.ovrei_decl_ref.Clang_ast_t.dr_name with + | Some s -> s + | _ -> assert false) in + do_memb_ivar_ref_exp trans_state expr_info exp_stmt sil_loc name_field + + and memberExpr_trans trans_state stmt_info expr_info stmt_list member_expr_info = + Printing.log_out ~fmt:"Passing from MemberExpr '%s'\n" stmt_info.Clang_ast_t.si_pointer; + let sil_loc = get_sil_location stmt_info trans_state.parent_line_number trans_state.context in + let exp_stmt = extract_stmt_from_singleton stmt_list + "WARNING: in MemberExpr there must be only one stmt defining its expression.\n" in + let name_field = member_expr_info.Clang_ast_t.mei_name in + do_memb_ivar_ref_exp trans_state expr_info exp_stmt sil_loc name_field + + and unaryOperator_trans trans_state stmt_info expr_info stmt_list unary_operator_info = + let context = trans_state.context in + let pln = trans_state.parent_line_number in + Printing.log_out ~fmt:"Passing from UnaryOperator '%s'\n" stmt_info.Clang_ast_t.si_pointer; + let sil_loc = get_sil_location stmt_info pln context in + let line_number = get_line stmt_info pln in + let trans_state_pri = PriorityNode.try_claim_priority_node trans_state stmt_info in + let stmt = extract_stmt_from_singleton stmt_list + "WARNING: We expect only one element in stmt list defining the operand in UnaryOperator. NEED FIXING\n" in + let trans_state' = { trans_state_pri with succ_nodes =[]; parent_line_number = line_number } in + let res_trans_stmt = instruction trans_state' stmt in + (* Assumption: the operand does not create a cfg node*) + let (sil_e', _) = extract_exp_from_list res_trans_stmt.exps "\nWARNING: Missing operand in unary operator. NEED FIXING.\n" in + let ret_typ = CTypes_decl.qual_type_to_sil_type context.tenv expr_info.Clang_ast_t.ei_qual_type in + let ids_op, exp_op, instr_op = + CArithmetic_trans.unary_operation_instruction unary_operator_info sil_e' ret_typ sil_loc in + let node_kind = Cfg.Node.Stmt_node "UnaryOperator" in + let ids' = res_trans_stmt.ids@ids_op in + let instrs = res_trans_stmt.instrs @ instr_op in + let root_nodes_to_parent, leaf_nodes_to_parent, ids_to_parent, instr_to_parent, exp_to_parent = + if PriorityNode.own_priority_node trans_state_pri.priority stmt_info then + (* Create a node. *) + let ids_parent = ids_to_parent trans_state_pri.continuation ids' in + let ids_node = ids_to_node trans_state_pri.continuation ids' in + let node = create_node node_kind ids_node instrs sil_loc context in + + Cfg.Node.set_succs_exn node trans_state_pri.succ_nodes []; + list_iter (fun n -> Cfg.Node.set_succs_exn n [node] []) res_trans_stmt.leaf_nodes; + + let root_nodes = + if res_trans_stmt.root_nodes <> [] then res_trans_stmt.root_nodes + else [node] in + let leaf_nodes = [node] in + + root_nodes, leaf_nodes, ids_parent, [], exp_op + else + res_trans_stmt.root_nodes, res_trans_stmt.leaf_nodes, ids', instrs, exp_op in + { root_nodes = root_nodes_to_parent; + leaf_nodes = leaf_nodes_to_parent; + ids = ids_to_parent; + instrs = instr_to_parent; + exps = [(exp_to_parent, ret_typ)] + } + + and returnStmt_trans trans_state stmt_info stmt_list = + let context = trans_state.context in + let pln = trans_state.parent_line_number in + let succ_nodes = trans_state.succ_nodes in + Printing.log_out ~fmt:"Passing from ReturnOperator '%s'.\n" stmt_info.Clang_ast_t.si_pointer; + let sil_loc = get_sil_location stmt_info pln context in + let line_number = get_line stmt_info pln in + let trans_state_pri = PriorityNode.try_claim_priority_node trans_state stmt_info in + let ret_node = create_node (Cfg.Node.Stmt_node "Return Stmt") [] [] sil_loc context in + Cfg.Node.set_succs_exn ret_node [(Cfg.Procdesc.get_exit_node context.procdesc)] []; + let trans_result = (match stmt_list with + | [stmt] -> (* return exp; *) + let trans_state' = { trans_state_pri with succ_nodes = [ret_node]; parent_line_number = line_number } in + let res_trans_stmt = exec_with_self_exception instruction trans_state' stmt in + let (sil_expr, sil_typ) = extract_exp_from_list res_trans_stmt.exps + "WARNING: There should be only one return expression.\n" in + let ret_var = Cfg.Procdesc.get_ret_var context.procdesc in + let ret_type = Cfg.Procdesc.get_ret_type context.procdesc in + let ret_instr = Sil.Set (Sil.Lvar ret_var, ret_type, sil_expr, sil_loc) in + let autorelease_ids, autorelease_instrs = add_autorelease_call context sil_expr ret_type sil_loc in + let instrs = res_trans_stmt.instrs @ [ret_instr] @ autorelease_instrs in + let ids = res_trans_stmt.ids@autorelease_ids in + Cfg.Node.append_instrs_temps ret_node instrs ids; + list_iter (fun n -> Cfg.Node.set_succs_exn n [ret_node] []) res_trans_stmt.leaf_nodes; + let root_nodes_to_parent = + if list_length res_trans_stmt.root_nodes >0 then res_trans_stmt.root_nodes else [ret_node] in + { root_nodes = root_nodes_to_parent; leaf_nodes =[ret_node]; ids = ids; instrs = instrs; exps =[]} + | [] -> (* return; *) + { empty_res_trans with root_nodes =[ret_node]; leaf_nodes =[ret_node]} + | _ -> Printing.log_out + "\nWARNING: Missing translation of Return Expression. Return Statement ignored. Need fixing!\n"; + { empty_res_trans with root_nodes = succ_nodes }) in (* We expect a return with only one expression *) + trans_result + + (* We analyze the content of the expr. We treat ExprWithCleanups as a wrapper. *) + (* It may be that later on (when we treat ARC) some info can be taken from it. *) + (* For ParenExpression we translate its body composed by the stmt_list. *) + (* In paren expression there should be only one stmt that defines the expression *) + and parenExpr_trans trans_state stmt_info stmt_list = + Printing.log_out ~fmt:"Passing from ParenExpr '%s' \n" stmt_info.Clang_ast_t.si_pointer; + let line_number = get_line stmt_info trans_state.parent_line_number in + let trans_state'= { trans_state with parent_line_number = line_number } in + let stmt = extract_stmt_from_singleton stmt_list + "WARNING: In ParenExpression there should be only one stmt.\n" in + instruction trans_state' stmt + + and objCBoxedExpr_trans trans_state info sel stmt_info stmts = + let typ = CTypes_decl.class_from_pointer_type trans_state.context.tenv info.Clang_ast_t.ei_qual_type in + let obj_c_message_expr_info = Ast_expressions.make_obj_c_message_expr_info sel typ in + let message_stmt = ObjCMessageExpr(stmt_info, stmts, info, obj_c_message_expr_info) in + instruction trans_state message_stmt + + and objCArrayLiteral_trans trans_state info stmt_info stmts = + let typ = CTypes_decl.class_from_pointer_type trans_state.context.tenv info.Clang_ast_t.ei_qual_type in + let obj_c_message_expr_info = + Ast_expressions.make_obj_c_message_expr_info CFrontend_config.array_with_objects_count_m typ in + let stmts = stmts@[Ast_expressions.create_nil stmt_info] in + let message_stmt = ObjCMessageExpr(stmt_info, stmts, info, obj_c_message_expr_info) in + instruction trans_state message_stmt + + and objCDictionaryLiteral_trans trans_state info stmt_info stmts = + let typ = CTypes_decl.class_from_pointer_type trans_state.context.tenv info.Clang_ast_t.ei_qual_type in + let obj_c_message_expr_info = + Ast_expressions.make_obj_c_message_expr_info CFrontend_config.dict_with_objects_and_keys_m typ in + let stmts = swap_elements_list stmts in + let stmts = stmts@[Ast_expressions.create_nil stmt_info] in + let message_stmt = ObjCMessageExpr(stmt_info, stmts, info, obj_c_message_expr_info) in + instruction trans_state message_stmt + + and objCStringLiteral_trans trans_state stmt_info stmts info = + let stmts = [Ast_expressions.create_implicit_cast_expr stmt_info stmts + (Ast_expressions.create_char_type ()) `ArrayToPointerDecay] in + let typ = CTypes_decl.class_from_pointer_type trans_state.context.tenv info.Clang_ast_t.ei_qual_type in + let obj_c_message_expr_info = + Ast_expressions.make_obj_c_message_expr_info CFrontend_config.string_with_utf8_m typ in + let message_stmt = ObjCMessageExpr(stmt_info, stmts, info, obj_c_message_expr_info) in + instruction trans_state message_stmt + + (** When objects are autoreleased, they get added a flag AUTORELEASE. All these objects will be + ignored when checking for memory leaks. When the end of the block autoreleasepool is reached, + then those objects are released and the autorelease flag is removed. *) + and objcAutoreleasePool_trans trans_state stmt_info stmts = + let sil_loc = get_sil_location stmt_info trans_state.parent_line_number trans_state.context in + let fname = SymExec.ModelBuiltins.__objc_release_autorelease_pool in + let ret_id = Ident.create_fresh Ident.knormal in + let autorelease_pool_vars = compute_autorelease_pool_vars trans_state.context stmts in + let stmt_call = Sil.Call([ret_id], (Sil.Const (Sil.Cfun fname)), autorelease_pool_vars, sil_loc, Sil.cf_default) in + let node_kind = Cfg.Node.Stmt_node ("Release the autorelease pool") in + let call_node = create_node node_kind ([ret_id]) ([stmt_call]) sil_loc trans_state.context in + Cfg.Node.set_succs_exn call_node trans_state.succ_nodes []; + let trans_state'={ trans_state with continuation = None; succ_nodes =[call_node] } in + instructions trans_state' stmts + + (* Assumption: stmt_list contains 2 items, the first can be ObjCMessageExpr or ParenExpr *) + (* We ignore this item since we don't deal with the concurrency problem yet *) + (* For the same reason we also ignore the stmt_info that is related with the ObjCAtSynchronizedStmt construct *) + (* Finally we recursively work on the CompoundStmt, the second item of stmt_list *) + and objCAtSynchronizedStmt_trans trans_state stmt_info stmt_list = + Printing.log_out ~fmt:"Passing from `ObjCAtSynchronizedStmt '%s' \n" stmt_info.Clang_ast_t.si_pointer; + (match stmt_list with + | [_; compound_stmt] -> instruction trans_state compound_stmt + | _ -> assert false) + + and blockExpr_trans trans_state stmt_info expr_info decl = + Printing.log_out ~fmt:"Passing from BlockExpr '%s' \n" stmt_info.Clang_ast_t.si_pointer; + let context = trans_state.context in + let pln = trans_state.parent_line_number in + let procname = Cfg.Procdesc.get_proc_name context.procdesc in + let loc = + (match stmt_info.Clang_ast_t.si_source_range with (l1, l2) -> + CLocation.clang_to_sil_location l1 pln (Some context.procdesc)) in + (* Given a mangled name (possibly full) returns a plain mangled name *) + let ensure_plain_mangling m = + Mangled.from_string (Mangled.to_string m) in + (* Given a captured var, return the instruction to assign it to a temp *) + let assign_captured_var cv = + let cvar, typ = (match cv with + | (cvar, typ, false) -> cvar, typ + | (cvar, typ, true) -> (* static case *) + let formals = Cfg.Procdesc.get_formals context.procdesc in + let cvar' = ensure_plain_mangling cvar in + (* we check if cvar' is a formal. In that case we need this plain mangled name *) + (* otherwise it's a static variable defined among the locals *) + (* and therefore we need the full mangled name *) + let cvar''= + if (list_exists(fun (s, t) -> Mangled.from_string s = cvar') formals) then cvar' + else cvar in + (cvar'', typ)) in + let id = Ident.create_fresh Ident.knormal in + let instr = Sil.Letderef (id, Sil.Lvar (Sil.mk_pvar cvar procname), typ, loc) in + (id, instr) in + (match decl with + | BlockDecl(decl_info, decl_list, decl_context_info, block_decl_info) -> + let qual_type = expr_info.Clang_ast_t.ei_qual_type in + let block_pname = CFrontend_utils.General_utils.mk_fresh_block_procname procname in + let typ = CTypes_decl.qual_type_to_sil_type context.tenv qual_type in + (* We need to set the explicit dependency between the newly created block and the *) + (* defining procedure. We add an edge in the call graph.*) + Cg.add_edge context.cg procname block_pname; + let function_decl_info = CFrontend_utils.General_utils.mk_function_decl_info_from_block block_decl_info in + let static_locals = list_filter (fun (v, t, s) -> s = true) context.local_vars in + (*list_iter (fun (v, _, _) -> L.err "Static Locals %s@." (Mangled.to_string v)) static_locals;*) + let static_formals = list_filter (fun (v, t, s) -> s = true) context.captured_vars in + (*list_iter (fun (v, _, _) -> L.err "Formal Static %s@." (Mangled.to_string v)) static_formals;*) + let static_vars = static_locals @ static_formals in + let captured_vars = + (CMethod_trans.captured_vars_from_block_info context block_decl_info.Clang_ast_t.bdi_captured_variables) in + let all_captured_vars = captured_vars @ static_vars in + let ids_instrs = list_map assign_captured_var all_captured_vars in + let ids, instrs = list_split ids_instrs in + M.function_decl context.tenv context.cfg context.cg context.namespace context.is_instance decl_info + (Procname.to_string block_pname) qual_type function_decl_info all_captured_vars (Some block_pname) context.curr_class; + Cfg.set_procname_priority context.cfg block_pname; + let captured_exps = list_map (fun id -> Sil.Var id) ids in + let tu = Sil.Ctuple ((Sil.Const (Sil.Cfun block_pname)):: captured_exps) in + let alloc_block_instr, ids_block = allocate_block trans_state (Procname.to_string block_pname) all_captured_vars loc in + { empty_res_trans with ids = ids_block @ ids; instrs = alloc_block_instr @ instrs; exps = [(Sil.Const tu, typ)]} + | _ -> assert false) + + (* Translates a clang instruction into SIL instructions. It takes a *) + (* a trans_state containing current info on the translation and it returns *) + (* a result_state.*) + and instruction trans_state instr = + match instr with + | GotoStmt(stmt_info, _, { Clang_ast_t.gsi_label = label_name; _ }) -> + gotoStmt_trans trans_state stmt_info label_name + + | LabelStmt(stmt_info, stmt_list, label_name) -> + Printing.log_out ~fmt:"\nPassing from `LabelStmt '%s' \n" stmt_info.Clang_ast_t.si_pointer; + labelStmt_trans trans_state stmt_info stmt_list label_name + + | ArraySubscriptExpr(stmt_info, stmt_list, expr_info) -> + arraySubscriptExpr_trans trans_state stmt_info expr_info stmt_list + + | BinaryOperator(stmt_info, stmt_list, expr_info, binary_operator_info) -> + binaryOperator_trans trans_state binary_operator_info stmt_info expr_info stmt_list + + | CallExpr(stmt_info, stmt_list, ei) -> + (match is_dispatch_function stmt_list with + | Some block_arg_pos -> + dispatch_function_trans trans_state stmt_info stmt_list ei block_arg_pos + | None -> + callExpr_trans trans_state stmt_info stmt_list ei) + + | ObjCMessageExpr(stmt_info, stmt_list, expr_info, obj_c_message_expr_info) -> + objCMessageExpr_trans trans_state stmt_info obj_c_message_expr_info stmt_list expr_info + + | CompoundStmt (stmt_info, stmt_list) -> + (* No node for this statement. We just collect its statement list*) + compoundStmt_trans trans_state stmt_info stmt_list + + | ConditionalOperator(stmt_info, stmt_list, expr_info) -> + (* Ternary operator "cond ? exp1 : exp2" *) + conditionalOperator_trans trans_state stmt_info stmt_list expr_info + + | IfStmt(stmt_info, stmt_list) -> + ifStmt_trans trans_state stmt_info stmt_list + + | SwitchStmt (stmt_info, switch_stmt_list) -> + switchStmt_trans trans_state stmt_info switch_stmt_list + + | CaseStmt (stmt_info, stmt_list) -> + Printing.log_out "FATAL: Passing from CaseStmt outside of SwitchStmt, terminating.\n"; assert false + + | StmtExpr(stmt_info, stmt_list, expr_info) -> + stmtExpr_trans trans_state stmt_info stmt_list expr_info + + | ForStmt(stmt_info, [init; null_stmt; cond; incr; body]) -> + (* Note: we ignore null_stmt at the moment.*) + forStmt_trans trans_state init cond incr body stmt_info + + | WhileStmt(stmt_info, [_; cond; body]) -> (* Note: we ignore null_stmt at the moment.*) + whileStmt_trans trans_state cond body stmt_info + + | DoStmt(stmt_info, [body; cond]) -> + doStmt_trans trans_state stmt_info cond body + + | ObjCForCollectionStmt(stmt_info, [item; _; body]) -> + objCForCollectionStmt_trans trans_state item body stmt_info + + | NullStmt(stmt_info, stmt_list) -> + nullStmt_trans trans_state.succ_nodes stmt_info + + | CompoundAssignOperator(stmt_info, stmt_list, expr_info, binary_operator_info, caoi) -> + compoundAssignOperator trans_state stmt_info binary_operator_info expr_info stmt_list + + | DeclStmt(stmt_info, stmt_list, decl_list) -> + declStmt_trans trans_state decl_list stmt_info + + | DeclRefExpr(stmt_info, stmt_list, expr_info, decl_ref_expr_info) as d -> + declRefExpr_trans trans_state stmt_info expr_info decl_ref_expr_info d + + | ObjCPropertyRefExpr(stmt_info, stmt_list, expr_info, property_ref_expr_info) -> + objCPropertyRefExpr_trans trans_state stmt_info stmt_list + + | OpaqueValueExpr(stmt_info, stmt_list, expr_info, opaque_value_expr_info) -> + opaqueValueExpr_trans trans_state stmt_info opaque_value_expr_info + + | PseudoObjectExpr(stmt_info, stmt_list, expr_info) -> + pseudoObjectExpr_trans trans_state stmt_info stmt_list + + | UnaryExprOrTypeTraitExpr(stmt_info, stmt_list, expr_info, ei) -> + unaryExprOrTypeTraitExpr_trans trans_state stmt_info expr_info ei + + | ObjCBridgedCastExpr(stmt_info, stmt_list, expr_info, cast_kind, _) -> + cast_exprs_trans trans_state stmt_info stmt_list expr_info cast_kind true + | ImplicitCastExpr(stmt_info, stmt_list, expr_info, cast_kind) + | CStyleCastExpr(stmt_info, stmt_list, expr_info, cast_kind, _) -> + cast_exprs_trans trans_state stmt_info stmt_list expr_info cast_kind false + + | IntegerLiteral(stmt_info, _, expr_info, integer_literal_info) -> + integerLiteral_trans trans_state stmt_info expr_info integer_literal_info + + | StringLiteral(stmt_info, stmt_list, expr_info, str) -> + stringLiteral_trans trans_state stmt_info expr_info str + + | GNUNullExpr(stmt_info, stmt_list, expr_info) -> + gNUNullExpr_trans trans_state stmt_info expr_info + + | ObjCSelectorExpr(stmt_info, stmt_list, expr_info, selector) -> + objCSelectorExpr_trans trans_state stmt_info expr_info selector + + | ObjCEncodeExpr(stmt_info, stmt_list, expr_info, qual_type) -> + objCEncodeExpr_trans trans_state stmt_info expr_info qual_type + + | ObjCProtocolExpr(stmt_info, stmt_list, expr_info, decl_ref) -> + objCProtocolExpr_trans trans_state stmt_info expr_info decl_ref + + | ObjCIvarRefExpr(stmt_info, stmt_list, expr_info, obj_c_ivar_ref_expr_info) -> + objCIvarRefExpr_trans trans_state stmt_info expr_info stmt_list obj_c_ivar_ref_expr_info + + | MemberExpr(stmt_info, stmt_list, expr_info, member_expr_info) -> + memberExpr_trans trans_state stmt_info expr_info stmt_list member_expr_info + + | UnaryOperator(stmt_info, stmt_list, expr_info, unary_operator_info) -> + if is_logical_negation_of_int trans_state.context.tenv expr_info unary_operator_info then + let conditional = Ast_expressions.trans_negation_with_conditional stmt_info expr_info stmt_list in + instruction trans_state conditional + else + unaryOperator_trans trans_state stmt_info expr_info stmt_list unary_operator_info + + | ReturnStmt (stmt_info, stmt_list) -> + returnStmt_trans trans_state stmt_info stmt_list + + (* We analyze the content of the expr. We treat ExprWithCleanups as a wrapper. *) + (* It may be that later on (when we treat ARC) some info can be taken from it. *) + | ExprWithCleanups(stmt_info, stmt_list, expr_info, _) + | ParenExpr(stmt_info, stmt_list, expr_info) -> + parenExpr_trans trans_state stmt_info stmt_list + + | ObjCBoolLiteralExpr (stmt_info, stmts, expr_info, n) + | CharacterLiteral (stmt_info, stmts, expr_info, n) + | CXXBoolLiteralExpr (stmt_info, stmts, expr_info, n) -> + characterLiteral_trans trans_state stmt_info expr_info n + + | FloatingLiteral (stmt_info, stmts, expr_info, float_string) -> + floatingLiteral_trans trans_state stmt_info expr_info float_string + + | ObjCBoxedExpr (stmt_info, stmts, info, sel) -> + objCBoxedExpr_trans trans_state info sel stmt_info stmts + + | ObjCArrayLiteral (stmt_info, stmts, info) -> + objCArrayLiteral_trans trans_state info stmt_info stmts + + | ObjCDictionaryLiteral (stmt_info, stmts, info) -> + objCDictionaryLiteral_trans trans_state info stmt_info stmts + + | ObjCStringLiteral(stmt_info, stmts, info) -> + objCStringLiteral_trans trans_state stmt_info stmts info + + | BreakStmt(stmt_info, lstmt) -> breakStmt_trans trans_state + + | ContinueStmt(stmt_infr, lstmt) -> continueStmt_trans trans_state + + | ObjCAtSynchronizedStmt(stmt_info, stmt_list) -> + objCAtSynchronizedStmt_trans trans_state stmt_info stmt_list + + | ObjCIndirectCopyRestoreExpr (stmt_info, stmt_list, _) -> + instructions trans_state stmt_list + + | BlockExpr(stmt_info, _ , expr_info, decl) -> + blockExpr_trans trans_state stmt_info expr_info decl + + | ObjCAutoreleasePoolStmt (stmt_info, stmts) -> + objcAutoreleasePool_trans trans_state stmt_info stmts + + | ObjCAtTryStmt (stmt_info, stmts) -> + compoundStmt_trans trans_state stmt_info stmts + + | ObjCAtThrowStmt (stmt_info, stmts) -> + returnStmt_trans trans_state stmt_info stmts + + | ObjCAtFinallyStmt (stmt_info, stmts) -> + compoundStmt_trans trans_state stmt_info stmts + + | ObjCAtCatchStmt (stmt_info, stmts, obj_c_message_expr_kind) -> + compoundStmt_trans trans_state stmt_info [] + + | PredefinedExpr (stmt_info, stmts, expr_info, predefined_expr_type) -> + stringLiteral_trans trans_state stmt_info expr_info "" + + | BinaryConditionalOperator (stmt_info, stmts, expr_info) -> + (match stmts with + | [stmt1; ostmt1; ostmt2; stmt2] when contains_opaque_value_expr ostmt1 && contains_opaque_value_expr ostmt2 -> + conditionalOperator_trans trans_state stmt_info [stmt1; stmt1; stmt2] expr_info + | _ -> Printing.log_stats ~fmt: "BinaryConditionalOperator not translated %s @." (Ast_utils.string_of_stmt instr); + assert false) + | s -> (Printing.log_stats + ~fmt:"\n!!!!WARNING: found statement %s. \nACTION REQUIRED: Translation need to be defined. Statement ignored.... \n" + (Ast_utils.string_of_stmt s); + assert false) + + (* Given a translation state, this function traslates a list of clang statements. *) + and instructions trans_state clang_stmt_list = + (* Printing.log_err ~fmt:"\n instruction list %i" (List.length clang_stmt_list); *) + match clang_stmt_list with + | [] -> { empty_res_trans with root_nodes = trans_state.succ_nodes } + | s:: clang_stmt_list' -> + let res_trans_s = instruction trans_state s in + let trans_state' = + if res_trans_s.root_nodes <> [] + then { trans_state with succ_nodes = res_trans_s.root_nodes } + else trans_state in + let res_trans_tail = instructions trans_state' clang_stmt_list' in + { root_nodes = res_trans_tail.root_nodes; + leaf_nodes =[]; + ids = res_trans_s.ids @ res_trans_tail.ids; + instrs = res_trans_s.instrs @ res_trans_tail.instrs; + exps = res_trans_s.exps @ res_trans_tail.exps } + + let expression_trans context stmt warning = + let trans_state = { context = context; succ_nodes =[]; continuation = None; parent_line_number = -1; priority = Free } in + let res_trans_stmt = instruction trans_state stmt in + fst (CTrans_utils.extract_exp_from_list res_trans_stmt.exps warning) + + let instructions_trans context clang_stmt_list exit_node = + let trans_state = { context = context; succ_nodes =[exit_node]; continuation = None; parent_line_number = -1; priority = Free } in + let res_trans = instructions trans_state clang_stmt_list in + res_trans.root_nodes + +end diff --git a/infer/src/clang/cTrans.mli b/infer/src/clang/cTrans.mli new file mode 100644 index 000000000..0f07af634 --- /dev/null +++ b/infer/src/clang/cTrans.mli @@ -0,0 +1,21 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +module type CTrans = sig +(** Translates instructions: (statements and expressions) from the ast into sil *) + +(** It receives the context, a list of statements and the exit node and it returns a list of cfg nodes *) +(** that reporesent the translation of the stmts into sil. *) + val instructions_trans : CContext.t -> Clang_ast_t.stmt list -> Cfg.Node.t -> Cfg.Node.t list + + (** It receives the context and a statement and a warning string and returns the translated sil expression *) + (** that represents the translation of the stmts into sil. *) + val expression_trans : CContext.t -> Clang_ast_t.stmt -> string -> Sil.exp + +end + + +module CTrans_funct(M: CModule_type.CMethod_declaration) : CTrans + diff --git a/infer/src/clang/cTrans_models.ml b/infer/src/clang/cTrans_models.ml new file mode 100644 index 000000000..fd906bee7 --- /dev/null +++ b/infer/src/clang/cTrans_models.ml @@ -0,0 +1,184 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +open Utils +open CFrontend_utils +open Clang_ast_t +open Objc_models +open CFrontend_config + +let is_cf_non_null_alloc funct = + match funct with + | Some procname -> + Procname.to_string procname = CFrontend_config.cf_non_null_alloc + | None -> false + +let is_alloc funct = + match funct with + | Some procname -> + Procname.to_string procname = CFrontend_config.cf_alloc + | None -> false + +let is_alloc_model typ funct = + match funct with + | Some procname -> + if Specs.summary_exists procname then false + else + let funct = Procname.to_string procname in + (* if (Core_foundation_model.is_core_lib_create typ funct) then + print_endline ("\nCore Foundation create not modelled "^(Sil.typ_to_string typ)^" "^(funct));*) + Core_foundation_model.is_core_lib_create typ funct + | None -> false + +let rec get_func_type_from_stmt stmt = + match stmt with + | DeclRefExpr(stmt_info, stmt_list, expr_info, decl_ref_expr_info) -> + Some expr_info.ei_qual_type + | _ -> + match CFrontend_utils.Ast_utils.get_stmts_from_stmt stmt with + | stmt:: rest -> get_func_type_from_stmt stmt + | [] -> None + +let is_retain_predefined_model typ funct = + Core_foundation_model.is_core_lib_retain typ funct + +let is_release_predefined_model typ funct = + Core_foundation_model.is_core_lib_release typ funct || + Core_foundation_model.is_core_graphics_release typ funct + +let is_retain_method funct = + funct = CFrontend_config.retain + +let is_release_method funct = + funct = CFrontend_config.release + +let is_autorelease_method funct = + funct = CFrontend_config.autorelease + +let get_builtinname method_name = + if is_retain_method method_name then + Some SymExec.ModelBuiltins.__objc_retain + else if is_autorelease_method method_name then + Some SymExec.ModelBuiltins.__set_autorelease_attribute + else if is_release_method method_name then + Some SymExec.ModelBuiltins.__objc_release + else None + +(* Not used *) +let is_builtin_expect funct = + funct = CFrontend_config.builtin_expect + +let is_assert_log_s funct = + funct = CFrontend_config.assert_rtn || + funct = CFrontend_config.assert_fail || + funct = CFrontend_config.fbAssertWithSignalAndLogFunctionHelper + +let is_handleFailureInMethod funct = + funct = CFrontend_config.handleFailureInMethod || + funct = CFrontend_config.handleFailureInFunction + +let is_retain_or_release funct = + is_retain_method funct || + is_release_method funct || + is_autorelease_method funct + +let is_toll_free_bridging pn_opt = + match pn_opt with + | Some pn -> + let funct = (Procname.to_string pn) in + funct = CFrontend_config.cf_bridging_release || + funct = CFrontend_config.cf_bridging_retain || + funct = CFrontend_config.cf_autorelease + | None -> false + +(** If the function is a builtin model, return the model, otherwise return the function *) +let builtin_predefined_model fun_stmt sil_fe = + match get_func_type_from_stmt fun_stmt with + | Some exp -> + let typ = CTypes.get_type exp in + (match sil_fe with + | Sil.Const (Sil.Cfun pn) when Specs.summary_exists pn -> sil_fe, false + | Sil.Const (Sil.Cfun pn) when is_retain_predefined_model typ (Procname.to_string pn) -> + Sil.Const (Sil.Cfun SymExec.ModelBuiltins.__objc_retain_cf) , true + | Sil.Const (Sil.Cfun pn) when is_release_predefined_model typ (Procname.to_string pn) -> + Sil.Const (Sil.Cfun SymExec.ModelBuiltins.__objc_release_cf), true + | _ -> sil_fe, false) + | _ -> sil_fe, false + +(** If the function is a builtin model, return the model, otherwise return the function *) +let is_assert_log sil_fe = + match sil_fe with + | Sil.Const (Sil.Cfun pn) when is_assert_log_s (Procname.to_string pn) -> true + | _ -> false + +let is_objc_memory_model_controlled o = + Core_foundation_model.is_objc_memory_model_controlled o + +let get_predefined_ms_method condition class_name method_name mk_procname + arguments return_type builtin = + if condition then + let procname = + match builtin with + | Some procname -> procname + | None -> mk_procname class_name method_name in + let ms = CMethod_signature.make_ms procname arguments return_type + (Ast_expressions.dummy_source_range ()) false in + Some ms + else None + +let get_predefined_ms_stringWithUTF8String class_name method_name mk_procname = + let condition = class_name = nsstring_cl && method_name = string_with_utf8_m in + get_predefined_ms_method condition class_name method_name mk_procname [("x", "char *")] + id_cl None + +let get_predefined_ms_retain_release class_name method_name mk_procname = + let condition = is_retain_or_release method_name in + let return_type = + if is_retain_method method_name || is_autorelease_method method_name + then id_cl else void in + get_predefined_ms_method condition nsobject_cl method_name mk_procname + [(self, class_name)] return_type (get_builtinname method_name) + +let get_predefined_ms_autoreleasepool_init class_name method_name mk_procname = + let condition = (method_name = init) && (class_name = nsautorelease_pool_cl) in + get_predefined_ms_method condition class_name method_name mk_procname + [(self, class_name)] void None + +let get_predefined_ms_nsautoreleasepool_release class_name method_name mk_procname = + let condition = (method_name = release || method_name = drain) && + (class_name = nsautorelease_pool_cl) in + get_predefined_ms_method condition class_name method_name mk_procname [(self, class_name)] + void (Some SymExec.ModelBuiltins.__objc_release_autorelease_pool) + +let get_predefined_model_method_signature class_name method_name mk_procname = + match get_predefined_ms_nsautoreleasepool_release class_name method_name mk_procname with + | Some ms -> Some ms + | None -> + match get_predefined_ms_retain_release class_name method_name mk_procname with + | Some ms -> Some ms + | None -> + match get_predefined_ms_stringWithUTF8String class_name method_name mk_procname with + | Some ms -> Some ms + | None -> get_predefined_ms_autoreleasepool_init class_name method_name mk_procname + +let dispatch_functions = [ + ("_dispatch_once", 1); + ("dispatch_async", 1); + ("dispatch_sync", 1); + ("dispatch_after", 2); + ("dispatch_group_async", 2); + ("dispatch_group_notify", 2); + ("dispatch_group_wait", 2); + ("dispatch_barrier_async", 1); + ] + +let is_dispatch_function_name function_name = + let rec is_dispatch functions = + match functions with + | [] -> None + | (el, block_arg_pos):: rest -> if (el = function_name) then + Some (el, block_arg_pos) + else is_dispatch rest in + is_dispatch dispatch_functions diff --git a/infer/src/clang/cTrans_models.mli b/infer/src/clang/cTrans_models.mli new file mode 100644 index 000000000..98845865e --- /dev/null +++ b/infer/src/clang/cTrans_models.mli @@ -0,0 +1,25 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +val is_cf_non_null_alloc : Procname.t option -> bool + +val is_alloc : Procname.t option -> bool + +val is_alloc_model : Sil.typ -> Procname.t option -> bool + +val is_objc_memory_model_controlled : string -> bool + +val builtin_predefined_model : Clang_ast_t.stmt -> Sil.exp -> Sil.exp * bool + +val is_assert_log : Sil.exp -> bool + +val is_handleFailureInMethod : string -> bool + +val is_toll_free_bridging : Procname.t option -> bool + +val get_predefined_model_method_signature : string -> string -> (string -> string -> Procname.t) -> + CMethod_signature.method_signature option + +val is_dispatch_function_name : string -> (string * int) option diff --git a/infer/src/clang/cTrans_utils.ml b/infer/src/clang/cTrans_utils.ml new file mode 100644 index 000000000..a846cc258 --- /dev/null +++ b/infer/src/clang/cTrans_utils.ml @@ -0,0 +1,660 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Utility methods to support the translation of clang ast constructs into sil instructions. *) + +open Utils +open CFrontend_utils +open CContext +open Clang_ast_t + +module L = Logging + +(* Extract the element of a singleton list. If the list is not a singleton *) +(* It stops the computation giving a warning. We use this because we *) +(* assume in many places that a list is just a singleton. We use the *) +(* warning if to see which assumption was not correct *) +let extract_item_from_singleton l warning_string failure_val = + match l with + | [item] -> item + | _ -> Printing.log_err warning_string; failure_val + +let dummy_exp = (Sil.exp_minus_one, Sil.Tint Sil.IInt) + +(* Extract the element of a singleton list. If the list is not a singleton *) +(* Gives a warning and return -1 as standard value indicating something *) +(* went wrong. *) +let extract_exp_from_list el warning_string = + extract_item_from_singleton el warning_string dummy_exp + +module Nodes = +struct + + let prune_kind b = Cfg.Node.Prune_node(b, Sil.Ik_bexp , ((string_of_bool b)^" Branch")) + + let is_join_node n = + match Cfg.Node.get_kind n with + | Cfg.Node.Join_node -> true + | _ -> false + + let is_prune_node n = + match Cfg.Node.get_kind n with + | Cfg.Node.Prune_node _ -> true + | _ -> false + + let is_true_prune_node n = + match Cfg.Node.get_kind n with + | Cfg.Node.Prune_node(true, _, _) -> true + | _ -> false + + let create_node node_kind temps instrs loc context = + let procdesc = CContext.get_procdesc context in + Cfg.Node.create (CContext.get_cfg context) loc node_kind instrs procdesc temps + + let create_prune_node branch e_cond ids_cond instrs_cond loc ik context = + let (e_cond', _) = extract_exp_from_list e_cond + "\nWARNING: Missing expression for Conditional operator. Need to be fixed" in + let e_cond'' = + if branch then + Sil.BinOp(Sil.Ne, e_cond', Sil.exp_zero) + else + Sil.BinOp(Sil.Eq, e_cond', Sil.exp_zero) in + let instrs_cond'= instrs_cond @ [Sil.Prune(e_cond'', loc, branch, ik)] in + create_node (prune_kind branch) ids_cond instrs_cond' loc context + + (** Check if this binary opertor requires the creation of a node in the cfg. *) + let is_binary_assign_op boi = + match boi.Clang_ast_t.boi_kind with + | `Assign | `MulAssign | `DivAssign | `RemAssign | `AddAssign | `SubAssign + | `ShlAssign | `ShrAssign | `AndAssign | `XorAssign | `OrAssign -> true + | `PtrMemD | `PtrMemI | `Mul | `Div | `Rem | `Add | `Sub | `Shl | `Shr + | `LT | `GT | `LE | `GE | `EQ | `NE | `And | `Xor | `Or | `LAnd | `LOr + | `Comma -> false + + (** Check if this unary opertor requires the creation of a node in the cfg. *) + let need_unary_op_node uoi = + match uoi.Clang_ast_t.uoi_kind with + | `PostInc | `PostDec | `PreInc | `PreDec | `AddrOf | `Deref | `Plus -> true + | `Minus | `Not | `LNot | `Real | `Imag | `Extension -> false + +end + +type str_node_map = (string, Cfg.Node.t) Hashtbl.t + +module GotoLabel = +struct + + (* stores goto labels local to a function, with the relative node in the cfg *) + let goto_label_node_map : str_node_map = Hashtbl.create 17 + + let reset_all_labels () = Hashtbl.reset goto_label_node_map + + let find_goto_label context label sil_loc = + try + Hashtbl.find goto_label_node_map label + with Not_found -> + let node_name = Format.sprintf "GotoLabel_%s" label in + let new_node = Nodes.create_node (Cfg.Node.Skip_node node_name) [] [] sil_loc context in + Hashtbl.add goto_label_node_map label new_node; + new_node +end + +type continuation = { + break: Cfg.Node.t list; + continue: Cfg.Node.t list; + return_temp : bool; (* true if temps should not be removed in the node but returned to ancestors *) +} + +let is_return_temp continuation = + match continuation with + | Some cont -> cont.return_temp + | _ -> false + +let ids_to_parent cont ids = + if is_return_temp cont then ids else [] + +let ids_to_node cont ids = + if is_return_temp cont then [] else ids + +let mk_cond_continuation cont = + match cont with + | Some cont' -> Some { cont' with return_temp = true; } + | None -> Some { break =[]; continue =[]; return_temp = true;} + +type priority_node = + | Free + | Busy of string + +(* A translation state. It provides the translation function with the info*) +(* it need to carry on the tranlsation. *) +type trans_state = { + context: CContext.t; (* current context of the translation *) + succ_nodes: Cfg.Node.t list; (* successor nodes in the cfg *) + continuation: continuation option; (* current continuation *) + parent_line_number: int; (* line numbeer of the parent element in the AST *) + priority: priority_node; +} + +(* A translation result. It is returned by the translation function. *) +type trans_result = { + root_nodes: Cfg.Node.t list; (* Top cfg nodes (root) created by the translation *) + leaf_nodes: Cfg.Node.t list; (* Bottom cfg nodes (leaf) created by the translate *) + ids: Ident.t list; (* list of temp identifiers created that need to be removed by the caller *) + instrs: Sil.instr list; (* list of SIL instruction that need to be placed in cfg nodes of the parent*) + exps: (Sil.exp * Sil.typ) list; (* SIL expressions resulting from the translation of the clang stmt *) +} + +(* Empty result translation *) +let empty_res_trans = { root_nodes =[]; leaf_nodes =[]; ids =[]; instrs =[]; exps =[]} + +(** Collect the results of translating a list of instructions, and link up the nodes created. *) +let collect_res_trans l = + let rec collect l rt = + match l with + | [] -> rt + | rt':: l' -> + let root_nodes = + if rt.root_nodes <> [] then rt.root_nodes + else rt'.root_nodes in + let leaf_nodes = + if rt'.leaf_nodes <> [] then rt'.leaf_nodes + else rt.leaf_nodes in + if rt'.root_nodes <> [] then + list_iter (fun n -> Cfg.Node.set_succs_exn n rt'.root_nodes []) rt.leaf_nodes; + collect l' + { root_nodes = root_nodes; + leaf_nodes = leaf_nodes; + ids = rt.ids@rt'.ids; + instrs = rt.instrs@rt'.instrs; + exps = rt.exps@rt'.exps } in + collect l empty_res_trans + +(* priority_node is used to enforce some kind of policy for creating nodes *) +(* in the cfg. Certain elements of the AST _must_ create nodes therefore *) +(* there is no need for them to use priority_node. Certain elements *) +(* instead need or need not to create a node depending of certain factors. *) +(* When an element of the latter kind wants to create a node it must claim *) +(* priority first (like taking a lock). priority can be claimes only when *) +(* it is free. If an element of AST succedes in claiming priority its id *) +(* (pointer) is recorded in priority. After an element has finished it *) +(* frees the priority. In general an AST element E checks if an ancestor *) +(* has claimed priority. If priority is already claimed E does not have to *) +(* create a node. If priority is free then it means E has to create the *) +(* node. Then E claims priority and release it afterward. *) +module PriorityNode = +struct + + type t = priority_node + + let try_claim_priority_node trans_state stmt_info = + match trans_state.priority with + | Free -> { trans_state with priority = Busy stmt_info.Clang_ast_t.si_pointer } + | _ -> trans_state + + let is_priority_free trans_state = + match trans_state.priority with + | Free -> true + | _ -> false + + let own_priority_node pri stmt_info = + match pri with + | Busy p when p = stmt_info.Clang_ast_t.si_pointer -> true + | _ -> false + + (* Used for function call and method call. It deals with creating or not *) + (* a cfg node depending of owning the priority_node and the nodes, ids, instrs returned *) + (* by the parameters of the call *) + let compute_results_to_parent trans_state loc nd_name stmt_info res_state_param = + let mk_node () = + let ids_node = ids_to_node trans_state.continuation res_state_param.ids in + let node_kind = Cfg.Node.Stmt_node (nd_name) in + Nodes.create_node node_kind ids_node res_state_param.instrs loc trans_state.context in + (* Invariant: if leaf_nodes is empty then the params have not created a node.*) + match res_state_param.leaf_nodes, own_priority_node trans_state.priority stmt_info with + | _, false -> (* The node is created by the parent. We just pass back nodes/leafs params *) + { res_state_param with exps = []} + | [], true -> (* We need to create a node and params did not create a node.*) + let node' = mk_node () in + let ids_parent = ids_to_parent trans_state.continuation res_state_param.ids in + Cfg.Node.set_succs_exn node' trans_state.succ_nodes []; + { root_nodes =[node']; + leaf_nodes =[node']; + ids = ids_parent; + instrs =[]; + exps = []} + | _, true -> + (* We need to create a node but params also created some,*) + (* so we need to pass back the nodes/leafs params*) + let node' = mk_node () in + Cfg.Node.set_succs_exn node' trans_state.succ_nodes []; + let ids_parent = ids_to_parent trans_state.continuation res_state_param.ids in + list_iter (fun n' -> Cfg.Node.set_succs_exn n' [node'] []) res_state_param.leaf_nodes; + { root_nodes = res_state_param.root_nodes; + leaf_nodes = [node']; + ids = ids_parent; + instrs =[]; + exps =[]} + +end + +module Loops = +struct + + type loop_kind = + | For of Clang_ast_t.stmt * Clang_ast_t.stmt * Clang_ast_t.stmt * Clang_ast_t.stmt + (* init, condition, increment and body *) + | While of Clang_ast_t.stmt * Clang_ast_t.stmt (* condition and body *) + | DoWhile of Clang_ast_t.stmt * Clang_ast_t.stmt (* condition and body *) + + let loop_kind_to_if_kind loop_kind = + match loop_kind with + | For _ -> Sil.Ik_for + | While _ -> Sil.Ik_while + | DoWhile _ -> Sil.Ik_dowhile + + let get_body loop_kind = + match loop_kind with + | For (_, _, _, body) | While (_, body) | DoWhile (_, body) -> body + + let get_cond loop_kind = + match loop_kind with + | For (_, cond, _, _) | While (cond, _) | DoWhile (cond, _) -> cond +end + +let create_alloc_instrs context sil_loc function_type is_cf_non_null_alloc = + let fname = if is_cf_non_null_alloc then + SymExec.ModelBuiltins.__objc_alloc_no_fail + else + SymExec.ModelBuiltins.__objc_alloc in + let function_type, function_type_np = + match function_type with + | Sil.Tptr (styp, Sil.Pk_pointer) + | Sil.Tptr (styp, Sil.Pk_objc_weak) + | Sil.Tptr (styp, Sil.Pk_objc_unsafe_unretained) + | Sil.Tptr (styp, Sil.Pk_objc_autoreleasing) -> + function_type, CTypes_decl.expand_structured_type context.tenv styp + | _ -> Sil.Tptr (function_type, Sil.Pk_pointer), function_type in + let sizeof_exp = Sil.Sizeof (function_type_np, Sil.Subtype.exact) in + let exp = (sizeof_exp, function_type) in + let ret_id = Ident.create_fresh Ident.knormal in + let stmt_call = Sil.Call([ret_id], (Sil.Const (Sil.Cfun fname)), [exp], sil_loc, Sil.cf_default) in + (function_type, ret_id, stmt_call, Sil.Var ret_id) + +let alloc_trans trans_state loc stmt_info function_type is_cf_non_null_alloc = + let (function_type, ret_id, stmt_call, exp) = create_alloc_instrs trans_state.context loc function_type is_cf_non_null_alloc in + let res_trans_tmp = { empty_res_trans with ids =[ret_id]; instrs =[stmt_call]} in + let res_trans = + PriorityNode.compute_results_to_parent trans_state loc "Call alloc" stmt_info res_trans_tmp in + { res_trans with exps =[(exp, function_type)]} + +let new_trans trans_state loc stmt_info cls_name function_type = + let (alloc_ret_type, alloc_ret_id, alloc_stmt_call, alloc_exp) = + create_alloc_instrs trans_state.context loc function_type true in + let init_ret_id = Ident.create_fresh Ident.knormal in + let is_instance = true in + let call_flags = { Sil.cf_virtual = is_instance; Sil.cf_noreturn = false; Sil.cf_is_objc_block = false; } in + let pname = CMethod_trans.mk_procname_from_method cls_name CFrontend_config.init in + CMethod_trans.create_external_procdesc trans_state.context.cfg pname is_instance None; + let args = [(Sil.Var alloc_ret_id, alloc_ret_type)] in + let init_stmt_call = Sil.Call([init_ret_id], (Sil.Const (Sil.Cfun pname)), args, loc, call_flags) in + let instrs = [alloc_stmt_call; init_stmt_call] in + let ids = [alloc_ret_id; init_ret_id] in + let res_trans_tmp = { empty_res_trans with ids = ids; instrs = instrs } in + let res_trans = + PriorityNode.compute_results_to_parent trans_state loc "Call new" stmt_info res_trans_tmp in + { res_trans with exps = [(Sil.Var init_ret_id, alloc_ret_type)]} + +let new_or_alloc_trans trans_state loc stmt_info class_name selector = + let function_type = CTypes_decl.type_name_to_sil_type trans_state.context.tenv class_name in + if selector = CFrontend_config.alloc then + alloc_trans trans_state loc stmt_info function_type true + else if selector = CFrontend_config.new_str then + new_trans trans_state loc stmt_info class_name function_type + else assert false + +let create_cast_instrs context exp cast_from_typ cast_to_typ sil_loc = + let ret_id = Ident.create_fresh Ident.knormal in + let cast_typ_no_pointer = + CTypes_decl.expand_structured_type context.tenv (CTypes.remove_pointer_to_typ cast_to_typ) in + let sizeof_exp = Sil.Sizeof (cast_typ_no_pointer, Sil.Subtype.exact) in + let pname = SymExec.ModelBuiltins.__objc_cast in + let args = [(exp, cast_from_typ); (sizeof_exp, Sil.Tvoid)] in + let stmt_call = Sil.Call([ret_id], (Sil.Const (Sil.Cfun pname)), args, sil_loc, Sil.cf_default) in + (ret_id, stmt_call, Sil.Var ret_id) + +let cast_trans context exps sil_loc callee_pname_opt function_type = + if CTrans_models.is_toll_free_bridging callee_pname_opt then + match exps with + | [exp, typ] -> + Some (create_cast_instrs context exp typ function_type sil_loc) + | _ -> assert false + else None + +let builtin_trans trans_state loc stmt_info function_type callee_pname_opt = + if CTrans_models.is_cf_non_null_alloc callee_pname_opt || + CTrans_models.is_alloc_model function_type callee_pname_opt then + Some (alloc_trans trans_state loc stmt_info function_type true) + else if CTrans_models.is_alloc callee_pname_opt then + Some (alloc_trans trans_state loc stmt_info function_type false) + else None + +let cast_operation context cast_kind exps cast_typ sil_loc is_objc_bridged = + let (exp, typ) = extract_exp_from_list exps "" in + if is_objc_bridged then + let id, instr, exp = create_cast_instrs context exp typ cast_typ sil_loc in + [id], [instr], exp + else + match cast_kind with + | `NoOp + | `BitCast + | `IntegralCast + | `IntegralToBoolean -> (* This is treated as a nop by returning the same expressions exps*) + ([],[], exp) + | `LValueToRValue -> + (* Takes an LValue and allow it to use it as RValue. *) + (* So we assign the LValue to a temp and we pass it to the parent.*) + let id = Ident.create_fresh Ident.knormal in + let sil_instr = [Sil.Letderef (id, exp, typ, sil_loc)] in + ([id], sil_instr, Sil.Var id) + | `CPointerToObjCPointerCast -> + ([], [], Sil.Cast(typ, exp)) + | _ -> + Printing.log_err + ~fmt:"\nWARNING: Missing translation for Cast Kind %s. The construct has been ignored...\n" + (Clang_ast_j.string_of_cast_kind cast_kind); + ([],[], exp) + +let trans_assertion_failure sil_loc context = + let assert_fail_builtin = Sil.Const (Sil.Cfun SymExec.ModelBuiltins.__infer_fail) in + let args = [Sil.Const (Sil.Cstr Config.default_failure_name), Sil.Tvoid] in + let call_instr = Sil.Call ([], assert_fail_builtin, args, sil_loc, Sil.cf_default) in + let exit_node = Cfg.Procdesc.get_exit_node (CContext.get_procdesc context) + and failure_node = + Nodes.create_node (Cfg.Node.Stmt_node "Assertion failure") [] [call_instr] sil_loc context in + Cfg.Node.set_succs_exn failure_node [exit_node] []; + { root_nodes = [failure_node]; + leaf_nodes = [failure_node]; + ids = []; + instrs =[]; + exps = [] } + +let trans_assume_false sil_loc context succ_nodes = + let instrs_cond = [Sil.Prune (Sil.exp_zero, sil_loc, true, Sil.Ik_land_lor)] in + let prune_node = Nodes.create_node (Nodes.prune_kind true) [] instrs_cond sil_loc context in + Cfg.Node.set_succs_exn prune_node succ_nodes []; + { root_nodes = [prune_node]; + leaf_nodes = [prune_node]; + ids = []; + instrs = []; + exps = [] } + +let define_condition_side_effects context e_cond instrs_cond sil_loc = + let (e', typ) = extract_exp_from_list e_cond "\nWARNING: Missing expression in IfStmt. Need to be fixed\n" in + match e' with + | Sil.Lvar pvar -> + let id = Ident.create_fresh Ident.knormal in + [(Sil.Var id, typ)], + [Sil.Letderef (id, Sil.Lvar pvar, typ, sil_loc)] + | _ -> [(e', typ)], instrs_cond + +(* Given a list of instuctions, ids, an expression, lhs of an compound *) +(* assignment its type and loc it computes which instructions, ids, and *) +(* expression need to be returned to the AST's parent node. This function *) +(* is used by a compount assignment. The expression e is the result of *) +(* translating the rhs of the assignment *) +let compute_instr_ids_exp_to_parent stmt_info instr ids e lhs typ loc pri = + if PriorityNode.own_priority_node pri stmt_info then( + (* The current AST element has created a node then instr and ids have *) + (* been already included in the node. *) + [], [], e + ) else ( + (* The node will be created by the parent. We pass the instr and ids. *) + (* For the expression we need to save the constend of the lhs in a new *) + (* temp so that can be used by the parent node (for example: x=(y=10)) *) + let id = Ident.create_fresh Ident.knormal in + let res_instr = [Sil.Letderef (id, lhs, typ, loc)] in + instr@res_instr, ids @ [id], [(Sil.Var id, typ)]) + +let fix_param_exps_mismatch params_stmt exps_param = + let diff = list_length params_stmt - list_length exps_param in + let args = if diff >0 then Array.make diff dummy_exp + else assert false in + let exps'= exps_param @ (Array.to_list args) in + exps' + +let get_name_decl_ref_exp_info decl_ref_expr_info si = + match decl_ref_expr_info.Clang_ast_t.drti_decl_ref with + | Some d -> (match d.Clang_ast_t.dr_name with + | Some n -> n + | _ -> assert false) + | _ -> L.err "FAILING WITH %s pointer=%s@.@." + (Clang_ast_j.string_of_decl_ref_expr_info decl_ref_expr_info ) + (Clang_ast_j.string_of_stmt_info si); assert false + +let is_superinstance mei = + match mei.Clang_ast_t.omei_receiver_kind with + | `SuperInstance -> true + | _ -> false + +let get_name_decl_ref_exp stmt = + match stmt with + | `DeclRefExpr(si, _, _, drei) -> + get_name_decl_ref_exp_info drei si + | _ -> assert false + +(* given the type of the enumeration and an enumeration constant (defined *) +(* by stmt), returns the associated value *) +let get_value_enum_constant tenv enum_type stmt = + let constant = get_name_decl_ref_exp stmt in + let typename = Sil.TN_enum(Mangled.from_string enum_type) in + match Sil.tenv_lookup tenv typename with + | Some (Sil.Tenum enum_constants) -> + Printing.log_out ~fmt:">>>Found enum with typename TN_typename('%s')\n" (Sil.typename_to_string typename); + let _, v = try + list_find (fun (c, _) -> Mangled.equal c (Mangled.from_string constant)) enum_constants + with _ -> (Printing.log_err + ~fmt:"Enumeration constant '%s' not found. Cannot continue...\n" constant; assert false) in + v + | _ -> Printing.log_err + ~fmt:"Enum type '%s' not found in tenv. Cannot continue...\n" (Sil.typename_to_string typename); + assert false + +let get_selector_receiver obj_c_message_expr_info = + obj_c_message_expr_info.Clang_ast_t.omei_selector, obj_c_message_expr_info.Clang_ast_t.omei_receiver_kind + +(* Similar to extract_item_from_singleton but for option type *) +let extract_item_from_option op warning_string = + match op with + | Some item -> item + | _ -> Printing.log_err warning_string; assert false + +let is_member_exp stmt = + match stmt with + | MemberExpr _ -> true + | _ -> false + +let is_enumeration_constant stmt = + match stmt with + | DeclRefExpr(_, _, _, drei) -> + (match drei.Clang_ast_t.drti_decl_ref with + | Some d -> (match d.Clang_ast_t.dr_kind with + | `EnumConstant -> true + | _ -> false) + | _ -> false) + | _ -> false + +let is_null_stmt s = + match s with + | NullStmt _ -> true + | _ -> false + +let dummy_id () = + Ident.create_normal (Ident.string_to_name "DUMMY_ID_INFER") 0 + +let extract_stmt_from_singleton stmt_list warning_string = + extract_item_from_singleton stmt_list warning_string (Ast_expressions.dummy_stmt ()) + +let extract_id_from_singleton id_list warning_string = + extract_item_from_singleton id_list warning_string (dummy_id ()) + +let rec get_type_from_exp_stmt stmt = + let do_decl_ref_exp i = + match i.Clang_ast_t.drti_decl_ref with + | Some d -> (match d.Clang_ast_t.dr_qual_type with + | Some n -> n + | _ -> assert false ) + | _ -> assert false in + match stmt with + | CXXOperatorCallExpr(_, _, ei) + | CallExpr(_, _, ei) -> ei.Clang_ast_t.ei_qual_type + | MemberExpr (_, _, ei, _) -> ei.Clang_ast_t.ei_qual_type + | ParenExpr (_, _, ei) -> ei.Clang_ast_t.ei_qual_type + | ArraySubscriptExpr(_, _, ei) -> ei.Clang_ast_t.ei_qual_type + | ObjCIvarRefExpr (_, _, ei, _) -> ei.Clang_ast_t.ei_qual_type + | ObjCMessageExpr (_, _, ei, _ ) -> ei.Clang_ast_t.ei_qual_type + | PseudoObjectExpr(_, _, ei) -> ei.Clang_ast_t.ei_qual_type + | CStyleCastExpr(_, stmt_list, _, _, _) + | UnaryOperator(_, stmt_list, _, _) + | ImplicitCastExpr(_, stmt_list, _, _) -> + get_type_from_exp_stmt (extract_stmt_from_singleton stmt_list "WARNING: We expect only one stmt.") + | DeclRefExpr(_, _, _, info) -> do_decl_ref_exp info + | _ -> Printing.log_err ~fmt:"Failing with: %s \n%!" (Clang_ast_j.string_of_stmt stmt); + Printing.print_failure_info ""; + assert false + +module Self = +struct + + exception SelfClassException of string + + let add_self_parameter_for_super_instance context procname loc mei trans_result = + if is_superinstance mei then + let typ, self_expr, id, ins = + let t' = CTypes.add_pointer_to_typ + (CTypes_decl.get_type_curr_class context.tenv context.curr_class) in + let e = Sil.Lvar (Sil.mk_pvar (Mangled.from_string CFrontend_config.self) procname) in + let id = Ident.create_fresh Ident.knormal in + t', Sil.Var id, [id], [Sil.Letderef (id, e, t', loc)] in + { trans_result with + exps = (self_expr, typ):: trans_result.exps; + ids = id@trans_result.ids; + instrs = ins@trans_result.instrs } + else trans_result + + let is_var_self pvar is_objc_method = + let is_self = Mangled.to_string (Sil.pvar_get_name pvar) = CFrontend_config.self in + is_self && is_objc_method + +end + +let get_decl_kind decl_ref_expr_info = + match decl_ref_expr_info.Clang_ast_t.drti_decl_ref with + | Some decl_ref -> decl_ref.Clang_ast_t.dr_kind + | None -> assert false + +(* From the manual: A selector is in a certain selector family if, ignoring any leading underscores, *) +(* the first component of the selector either consists entirely of the name of *) +(* the method family or it begins with that followed by character other than lower case letter.*) +(* For example: '__perform:with' and 'performWith:' would fall into the 'perform' family (if we had one),*) +(* but 'performing:with' would not. *) +let is_owning_name n = + let is_family fam s'= + if String.length s' < String.length fam then false + else ( + let prefix = Str.string_before s' (String.length fam) in + let suffix = Str.string_after s' (String.length fam) in + prefix = fam && not (Str.string_match (Str.regexp "[a-z]") suffix 0) + ) in + match Str.split (Str.regexp_string ":") n with + | fst:: _ -> + (match Str.split (Str.regexp "['_']+") fst with + | [no_und] + | _:: no_und:: _ -> + is_family CFrontend_config.alloc no_und || + is_family CFrontend_config.copy no_und || + is_family CFrontend_config.new_str no_und || + is_family CFrontend_config.mutableCopy no_und || + is_family CFrontend_config.init no_und + | _ -> assert false) + | _ -> assert false + +let rec is_owning_method s = + match s with + | ObjCMessageExpr(_, _ , _, mei) -> + is_owning_name mei.Clang_ast_t.omei_selector + | _ -> (match snd (Clang_ast_proj.get_stmt_tuple s) with + | [] -> false + | s'':: _ -> is_owning_method s'') + +let rec is_method_call s = + match s with + | ObjCMessageExpr(_, _ , _, mei) -> true + | _ -> (match snd (Clang_ast_proj.get_stmt_tuple s) with + | [] -> false + | s'':: _ -> is_method_call s'') + +let rec get_decl_ref_info s parent_line_number = + match s with + | DeclRefExpr (stmt_info, stmt_list, expr_info, decl_ref_expr_info) -> + let line_number = CLocation.get_line stmt_info parent_line_number in + stmt_info.Clang_ast_t.si_pointer, line_number + | _ -> (match Clang_ast_proj.get_stmt_tuple s with + | stmt_info, [] -> assert false + | stmt_info, s'':: _ -> + let line_number = CLocation.get_line stmt_info parent_line_number in + get_decl_ref_info s'' line_number) + +let rec contains_opaque_value_expr s = + match s with + | OpaqueValueExpr (_, _, _, _) -> true + | _ -> (match snd (Clang_ast_proj.get_stmt_tuple s) with + | [] -> false + | s'':: _ -> contains_opaque_value_expr s'') + +let rec compute_autorelease_pool_vars context stmts = + match stmts with + | [] -> [] + | DeclRefExpr(si, sl, ei, drei):: stmts' -> + let name = get_name_decl_ref_exp_info drei si in + let procname = Cfg.Procdesc.get_proc_name context.procdesc in + let local_vars = Cfg.Procdesc.get_locals context.procdesc in + let mname = try + list_filter (fun (m, t) -> Mangled.to_string m = name) local_vars + with _ -> [] in + (match mname with + | [(m, t)] -> + CFrontend_utils.General_utils.append_no_duplicated_pvars + [(Sil.Lvar (Sil.mk_pvar m procname), t)] (compute_autorelease_pool_vars context stmts') + | _ -> compute_autorelease_pool_vars context stmts') + | s:: stmts' -> + let sl = snd(Clang_ast_proj.get_stmt_tuple s) in + compute_autorelease_pool_vars context (sl@stmts') + +(* checks if a unary operator is a logic negation applied to integers*) +let is_logical_negation_of_int tenv ei uoi = + match CTypes_decl.qual_type_to_sil_type tenv ei.Clang_ast_t.ei_qual_type, uoi.Clang_ast_t.uoi_kind with + | Sil.Tint Sil.IInt,`LNot -> true + | _, _ -> false + +(* Checks if stmt_list is a call to a special dispatch function *) +let is_dispatch_function stmt_list = + match stmt_list with + | ImplicitCastExpr(_,[DeclRefExpr(_, _, _, di)], _, _):: stmts -> + (match di.Clang_ast_t.drti_decl_ref with + | None -> None + | Some d -> + (match d.Clang_ast_t.dr_kind, d.Clang_ast_t.dr_name with + | `Function, Some s -> + (match (CTrans_models.is_dispatch_function_name s) with + | None -> None + | Some (dispatch_function, block_arg_pos) -> + try + (match list_nth stmts block_arg_pos with + | BlockExpr _ -> Some block_arg_pos + | _ -> None) + with Not_found -> None + ) + | _ -> None)) + | _ -> None diff --git a/infer/src/clang/cTrans_utils.mli b/infer/src/clang/cTrans_utils.mli new file mode 100644 index 000000000..278e1255a --- /dev/null +++ b/infer/src/clang/cTrans_utils.mli @@ -0,0 +1,198 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Utility methods to support the translation of clang ast constructs into sil instructions. *) + +type continuation = { + break: Cfg.Node.t list; + continue: Cfg.Node.t list; + return_temp : bool; (* true if temps should not be removed in the node but returned to ancestors *) +} + +type priority_node = + | Free + | Busy of string + +type trans_state = { + context: CContext.t; + succ_nodes: Cfg.Node.t list; + continuation: continuation option; + parent_line_number: int; + priority: priority_node; +} + +type trans_result = { + root_nodes: Cfg.Node.t list; + leaf_nodes: Cfg.Node.t list; + ids: Ident.t list; + instrs: Sil.instr list; + exps: (Sil.exp * Sil.typ) list; +} + +val empty_res_trans: trans_result + +(** Collect the results of translating a list of instructions, and link up the nodes created. *) +val collect_res_trans: trans_result list -> trans_result + +val is_return_temp: continuation option -> bool + +val ids_to_parent: continuation option -> Ident.t list -> Ident.t list + +val ids_to_node: continuation option -> Ident.t list -> Ident.t list + +val mk_cond_continuation : continuation option -> continuation option + +val extract_item_from_singleton : 'a list -> string -> 'a -> 'a + +val extract_exp_from_list : (Sil.exp * Sil.typ) list -> string -> (Sil.exp * Sil.typ) + +val fix_param_exps_mismatch : 'a list -> (Sil.exp * Sil.typ) list -> (Sil.exp * Sil.typ)list + +val get_selector_receiver : Clang_ast_t.obj_c_message_expr_info -> string * Clang_ast_t.receiver_kind + +val define_condition_side_effects : CContext.t -> (Sil.exp * Sil.typ) list -> Sil.instr list -> Sil.location -> +(Sil.exp * Sil.typ) list * Sil.instr list + +val extract_stmt_from_singleton : Clang_ast_t.stmt list -> string -> Clang_ast_t.stmt + +val is_null_stmt : Clang_ast_t.stmt -> bool + +val compute_instr_ids_exp_to_parent : Clang_ast_t.stmt_info -> Sil.instr list -> Ident.t list -> (Sil.exp * Sil.typ) list -> +Sil.exp -> Sil.typ -> Sil.location -> priority_node -> Sil.instr list * Ident.t list * (Sil.exp * Sil.typ) list + +val get_name_decl_ref_exp_info : Clang_ast_t.decl_ref_expr_info -> Clang_ast_t.stmt_info -> string + +val get_decl_kind : Clang_ast_t.decl_ref_expr_info -> Clang_ast_t.decl_kind + +val is_enumeration_constant : Clang_ast_t.stmt -> bool + +val is_member_exp : Clang_ast_t.stmt -> bool + +val get_type_from_exp_stmt : Clang_ast_t.stmt -> Clang_ast_t.qual_type + +val cast_operation : CContext.t -> Clang_ast_t.cast_kind -> (Sil.exp * Sil.typ) list -> Sil.typ -> Sil.location -> +bool -> Ident.t list * Sil.instr list * Sil.exp + +val trans_assertion_failure : Sil.location -> CContext.t -> trans_result + +val trans_assume_false : Sil.location -> CContext.t -> Cfg.Node.t list -> trans_result + +val is_owning_method : Clang_ast_t.stmt -> bool + +val is_owning_name : string -> bool + +val is_method_call : Clang_ast_t.stmt -> bool + +val contains_opaque_value_expr : Clang_ast_t.stmt -> bool + +val get_decl_ref_info : Clang_ast_t.stmt -> int -> string * int + +val builtin_trans : trans_state -> Sil.location -> Clang_ast_t.stmt_info -> +Sil.typ -> Procname.t option -> trans_result option + +val alloc_trans : trans_state -> Sil.location -> Clang_ast_t.stmt_info -> Sil.typ -> bool -> trans_result + +val new_or_alloc_trans : trans_state -> Sil.location -> Clang_ast_t.stmt_info -> string -> string -> trans_result + +val cast_trans : CContext.t -> (Sil.exp * Sil.typ) list -> Sil.location -> Procname.t option -> Sil.typ -> +(Ident.t * Sil.instr * Sil.exp) option + +(** Module for creating cfg nodes and other utility functions related to them. *) +module Nodes : +sig + val is_binary_assign_op : Clang_ast_t.binary_operator_info -> bool + + val need_unary_op_node : Clang_ast_t.unary_operator_info -> bool + + val create_node : Cfg.Node.nodekind -> Ident.t list -> Sil.instr list -> + Sil.location -> CContext.t -> Cfg.Node.t + + val is_join_node : Cfg.Node.t -> bool + + val create_prune_node : bool -> (Sil.exp * Sil.typ) list -> Ident.t list -> Sil.instr list -> Sil.location -> Sil.if_kind -> + CContext.t -> Cfg.Node.t + + val is_prune_node : Cfg.Node.t -> bool + + val is_true_prune_node : Cfg.Node.t -> bool + + val prune_kind : bool -> Cfg.Node.nodekind + +end + +(** priority_node is used to enforce some kind of policy for creating nodes *) +(** in the cfg. Certain elements of the AST _must_ create nodes therefore *) +(** there is no need for them to use priority_node. Certain elements *) +(** instead need or need not to create a node depending of certain factors. *) +(** When an element of the latter kind wants to create a node it must claim *) +(** priority first (like taking a lock). priority can be claimes only when *) +(** it is free. If an element of AST succedes in claiming priority its id *) +(** (pointer) is recorded in priority. After an element has finished it *) +(** frees the priority. In general an AST element E checks if an ancestor *) +(** has claimed priority. If priority is already claimed E does not have to *) +(** create a node. If priority is free then it means E has to create the *) +(** node. Then E claims priority and release it afterward. *) +module PriorityNode : +sig + + type t = priority_node + + val is_priority_free : trans_state -> bool + + val try_claim_priority_node : trans_state -> Clang_ast_t.stmt_info -> trans_state + + val own_priority_node : t -> Clang_ast_t.stmt_info -> bool + + (* Used for function call and method call. It deals with creating or not *) + (* a cfg node depending of owning the priority_node and the nodes returned *) + (* by the parameters of the call *) + val compute_results_to_parent : trans_state -> Sil.location -> string -> Clang_ast_t.stmt_info -> trans_result -> trans_result + +end + +(** Module for translating goto instructions by keeping a map of labels. *) +module GotoLabel : +sig + val find_goto_label : CContext.t -> string -> Sil.location -> Cfg.Node.t + + val reset_all_labels : unit -> unit + +end + +(** Module that provides utility functions for translating different types of loops. *) +module Loops : +sig + type loop_kind = + | For of Clang_ast_t.stmt * Clang_ast_t.stmt * Clang_ast_t.stmt * Clang_ast_t.stmt + (* init, condition, increment and body *) + | While of Clang_ast_t.stmt * Clang_ast_t.stmt (* condition and body *) + | DoWhile of Clang_ast_t.stmt * Clang_ast_t.stmt (* condition and body *) + + val loop_kind_to_if_kind : loop_kind -> Sil.if_kind + + val get_cond : loop_kind -> Clang_ast_t.stmt + + val get_body : loop_kind -> Clang_ast_t.stmt + +end + +(** This module handles the translation of the variable self which is challenging because self *) +(** is used both as a variable in instance method calls and also as a type in class method calls. *) +module Self : +sig + + exception SelfClassException of string + + val add_self_parameter_for_super_instance : CContext.t -> Procname.t -> Sil.location -> Clang_ast_t.obj_c_message_expr_info -> + trans_result -> trans_result + + val is_var_self : Sil.pvar -> bool -> bool +end + +val compute_autorelease_pool_vars : CContext.t -> Clang_ast_t.stmt list -> (Sil.exp * Sil.typ) list + +val is_logical_negation_of_int : Sil.tenv -> Clang_ast_t.expr_info -> Clang_ast_t.unary_operator_info -> bool + +val is_dispatch_function : Clang_ast_t.stmt list -> int option diff --git a/infer/src/clang/cTypes.ml b/infer/src/clang/cTypes.ml new file mode 100644 index 000000000..948883173 --- /dev/null +++ b/infer/src/clang/cTypes.ml @@ -0,0 +1,201 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Utility module for retrieving types *) + +open Utils +open Clang_ast_t +open CFrontend_utils +open CFrontend_utils.General_utils +module L = Logging + +let get_function_return_type s = + let regexp = Str.regexp_string " (" in + let matches = try let _ = Str.search_forward regexp s 0 in true with Not_found -> false in + let regexp' = + if matches then regexp + else Str.regexp_string "(" in (* match e.g. "char *()" *) + let buf = Str.split regexp' s in + match buf with + | ret:: _ -> + let ret'= String.trim ret in + Printing.log_out ~fmt:"return type ='%s'@." ret'; + ret' + | _ -> assert false + +let get_type qt = + match qt.Clang_ast_t.qt_desugared with + | Some t -> t + | _ -> qt.Clang_ast_t.qt_raw + +let get_type_from_expr_info ei = + ei.Clang_ast_t.ei_qual_type + +let lookup_var_type context pvar = + let formals = Cfg.Procdesc.get_formals context.CContext.procdesc in + let locals = Cfg.Procdesc.get_locals context.CContext.procdesc in + try + let s, t = list_find (fun (s, t) -> s = (Sil.pvar_to_string pvar)) formals in + Printing.log_out ~fmt:"When looking for type of variable '%s' " (Sil.pvar_to_string pvar); + Printing.log_out ~fmt:"found '%s' in formals.@." (Sil.typ_to_string t); + t + with Not_found -> + try + let s, t = list_find (fun (s, t) -> Mangled.equal (Sil.pvar_get_name pvar) s) locals in + Printing.log_out ~fmt:"When looking for type of variable '%s' " (Sil.pvar_to_string pvar); + Printing.log_out ~fmt:"found '%s' in locals.@." (Sil.typ_to_string t); + t + with Not_found -> + try + let typ = CGlobal_vars.var_get_typ (CGlobal_vars.find (Sil.pvar_get_name pvar)) in + Printing.log_out ~fmt:"When looking for type of variable '%s'" (Sil.pvar_to_string pvar); + Printing.log_out ~fmt:" found '%s' in globals.@." (Sil.typ_to_string typ); + typ + with Not_found -> + Printing.log_err + ~fmt:"WARNING: Variable '%s' not found in local+formal when looking for its type. Returning void.\n%!" + (Sil.pvar_to_string pvar); + Sil.Tvoid + +(* Extract the type out of a statement. This is useful when the statement *) +(* denotes actually an expression *) +let extract_type_from_stmt s = + match s with + | BinaryConditionalOperator(_, _, expr_info) | ConditionalOperator(_, _, expr_info) + | AddrLabelExpr(_, _, expr_info, _) | ArraySubscriptExpr(_, _, expr_info) + | ArrayTypeTraitExpr(_, _, expr_info) | AsTypeExpr(_, _, expr_info) + | AtomicExpr(_, _, expr_info) | BinaryOperator(_, _, expr_info, _) + | CompoundAssignOperator(_, _, expr_info, _, _) + | BlockExpr(_, _, expr_info, _) | CXXBindTemporaryExpr (_, _ , expr_info, _) + | CXXBoolLiteralExpr (_, _, expr_info, _) | CXXConstructExpr (_, _, expr_info, _) + | CXXTemporaryObjectExpr (_, _, expr_info, _) | CXXDefaultArgExpr (_, _, expr_info) + | CXXDefaultInitExpr (_, _, expr_info) | CXXDeleteExpr (_, _, expr_info) + | CXXDependentScopeMemberExpr (_, _, expr_info) | CXXNewExpr (_, _, expr_info) + | CXXNoexceptExpr (_, _, expr_info) | CXXNullPtrLiteralExpr (_, _, expr_info) + | CXXPseudoDestructorExpr (_, _, expr_info) | CXXScalarValueInitExpr (_, _, expr_info) + | CXXStdInitializerListExpr (_, _, expr_info) | CXXThisExpr (_, _, expr_info) + | CXXThrowExpr (_, _, expr_info) | CXXTypeidExpr (_, _, expr_info) + | CXXUnresolvedConstructExpr (_, _, expr_info) | CXXUuidofExpr (_, _, expr_info) + | CallExpr (_, _, expr_info) | CUDAKernelCallExpr (_, _, expr_info) + | CXXMemberCallExpr (_, _, expr_info) | CXXOperatorCallExpr (_, _, expr_info) + | UserDefinedLiteral (_, _, expr_info) | CStyleCastExpr (_, _, expr_info, _, _) + | CXXFunctionalCastExpr (_, _, expr_info, _, _) | CXXConstCastExpr (_, _, expr_info, _, _, _) + | CXXDynamicCastExpr (_, _, expr_info, _, _, _) | CXXReinterpretCastExpr(_, _, expr_info, _, _, _) + | CXXStaticCastExpr (_, _, expr_info, _, _, _) | ObjCBridgedCastExpr (_, _, expr_info, _, _) + | ImplicitCastExpr (_, _, expr_info, _) | CharacterLiteral (_, _, expr_info, _ ) + | ChooseExpr (_, _, expr_info) | CompoundLiteralExpr (_, _, expr_info) + | ConvertVectorExpr (_, _, expr_info) | DeclRefExpr (_, _, expr_info, _) + | DependentScopeDeclRefExpr (_, _, expr_info) | DesignatedInitExpr (_, _, expr_info) + | ExprWithCleanups (_, _, expr_info, _) | ExpressionTraitExpr (_, _, expr_info) + | ExtVectorElementExpr (_, _, expr_info) | FloatingLiteral (_, _, expr_info, _) + | FunctionParmPackExpr (_, _, expr_info) | GNUNullExpr (_, _, expr_info) + | GenericSelectionExpr (_, _, expr_info) | ImaginaryLiteral (_, _, expr_info) + | ImplicitValueInitExpr (_, _, expr_info) + | InitListExpr (_, _, expr_info) | IntegerLiteral(_, _, expr_info, _) + | LambdaExpr (_, _, expr_info, _) | MSPropertyRefExpr (_, _, expr_info) + | MaterializeTemporaryExpr (_, _, expr_info, _) + | MemberExpr (_, _, expr_info, _) | ObjCArrayLiteral (_, _, expr_info) + | ObjCBoolLiteralExpr (_, _, expr_info , _) | ObjCBoxedExpr (_, _, expr_info, _) + | ObjCDictionaryLiteral (_, _, expr_info) | ObjCEncodeExpr (_, _, expr_info, _) + | ObjCIndirectCopyRestoreExpr (_, _, expr_info) | ObjCIsaExpr (_, _, expr_info) + | ObjCIvarRefExpr(_, _, expr_info, _) | ObjCMessageExpr(_, _, expr_info, _) + | ObjCPropertyRefExpr(_, _, expr_info, _) | ObjCProtocolExpr (_, _, expr_info, _) + | ObjCSelectorExpr (_, _, expr_info, _) | ObjCStringLiteral (_, _, expr_info) + | ObjCSubscriptRefExpr(_, _, expr_info, _) | OffsetOfExpr (_, _, expr_info) + | OpaqueValueExpr(_, _, expr_info, _) | UnresolvedLookupExpr (_, _, expr_info, _, _) + | UnresolvedMemberExpr (_, _, expr_info, _) | PackExpansionExpr (_, _, expr_info) + | ParenExpr (_, _, expr_info) | ParenListExpr (_, _, expr_info) + | PredefinedExpr (_, _, expr_info, _) | PseudoObjectExpr (_, _, expr_info) + | ShuffleVectorExpr (_, _, expr_info) | SizeOfPackExpr (_, _, expr_info) + | StmtExpr (_, _, expr_info) | StringLiteral (_, _, expr_info, _) + | SubstNonTypeTemplateParmExpr (_, _, expr_info) + | SubstNonTypeTemplateParmPackExpr (_, _, expr_info) + | TypeTraitExpr (_, _, expr_info) | UnaryExprOrTypeTraitExpr (_, _, expr_info, _) + | UnaryOperator(_, _, expr_info, _) + | VAArgExpr (_, _, expr_info) -> expr_info.Clang_ast_t.ei_qual_type + | _ -> (* For the other case we cannot get the type info *) + Printing.log_err ~fmt:"WARNING: Could not get type of statement '%s'\n%!" (Clang_ast_j.string_of_stmt s); + assert false + +let get_desugared_type t = + match t.Clang_ast_t.qt_desugared with + | Some t' -> t' + | _ -> assert false + +(* Remove the work 'struct' from a type name. Used to avoid repetition when typename are constructed*) +(* E.g. 'struct struct s' *) +let cut_struct_union s = + Printing.log_out ~fmt:"Cutting '%s'@." s; + let buf = Str.split (Str.regexp "[ \t]+") s in + match buf with + | "struct":: l (*-> Printing.string_from_list l *) + | "class":: l + | "union":: l -> string_from_list l + | _ -> s + +let get_name_from_struct s = + match s with + | Sil.Tstruct(_, _, _, Some n, _, _, _) -> n + | _ -> assert false + +let rec get_type_list nn ll = + match ll with + | [] -> [] + | (n, t):: ll' -> (* Printing.log_out ">>>>>Searching for type '%s'. Seen '%s'.@." nn n; *) + if n = nn then ( + Printing.log_out ~fmt:">>>>>>>>>>>>>>>>>>>>>>>NOW Found, Its type is: '%s'@." (Sil.typ_to_string t); + [t] + ) else get_type_list nn ll' + +let add_pointer_to_typ typ = + Sil.Tptr(typ, Sil.Pk_pointer) + +let remove_pointer_to_typ typ = + match typ with + | Sil.Tptr(typ, Sil.Pk_pointer) -> typ + | _ -> typ + +let classname_of_type typ = + match typ with + | Sil.Tvar (Sil.TN_csu (_, name) ) + | Sil.Tstruct(_, _, _, (Some name), _, _, _) + | Sil.Tvar (Sil.TN_typedef name) -> Mangled.to_string name + | Sil.Tfun _ -> CFrontend_config.objc_object + | _ -> (Printing.log_out + ~fmt:"Classname of type cannot be extracted in type %s" (Sil.typ_to_string typ)); assert false + +let get_raw_qual_type_decl_ref_exp_info decl_ref_expr_info = + match decl_ref_expr_info.Clang_ast_t.drti_decl_ref with + | Some d -> + (match d.Clang_ast_t.dr_qual_type with + | Some qt -> Some qt.Clang_ast_t.qt_raw + | None -> None) + | None -> None + +(* Iterates over the tenv to find the value of the enumeration constant *) +(* using its name Here we assume that the enumeration constant have *) +(* different names. Note: this assumption may not be true all the time. So *) +(* need to be careful and give name that cane ensure uniqueness. In case *) +(* of repeated names it gets the last. *) +let search_enum_type_by_name tenv name = + let found = ref None in + let mname = Mangled.from_string name in + let f tn typ = + match typ with + | Sil.Tenum enum_constants -> + list_iter (fun (c, v) -> if Mangled.equal c mname then found:= Some v else ()) enum_constants + | _ -> () in + Sil.tenv_iter f tenv; + !found + +let mk_classname n = + Sil.TN_csu (Sil.Class, Mangled.from_string n) + +let is_class typ = + match typ with + | Sil.Tptr( Sil.Tstruct(_, _, _, (Some name), _, _, _), _) + | Sil.Tptr( Sil.Tvar (Sil.TN_csu (_, name) ), _) -> + (Mangled.to_string name) = CFrontend_config.objc_class + | _ -> false diff --git a/infer/src/clang/cTypes.mli b/infer/src/clang/cTypes.mli new file mode 100644 index 000000000..8132f606c --- /dev/null +++ b/infer/src/clang/cTypes.mli @@ -0,0 +1,34 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Utility module for retrieving types *) + +val lookup_var_type : CContext.t -> Sil.pvar -> Sil.typ + +val add_pointer_to_typ : Sil.typ -> Sil.typ + +val get_raw_qual_type_decl_ref_exp_info : Clang_ast_t.decl_ref_expr_info -> string option + +val extract_type_from_stmt : Clang_ast_t.stmt -> Clang_ast_t.qual_type + +val get_type : Clang_ast_t.qual_type -> string + +val search_enum_type_by_name : Sil.tenv -> string -> Sil.const option + +val classname_of_type : Sil.typ -> string + +val get_function_return_type : string -> string + +val mk_classname : string -> Sil.typename + +val get_name_from_struct: Sil.typ -> Mangled.t + +(* Remove the work 'struct' from a type name. Used to avoid repetition when typename are constructed*) +(* E.g. 'struct struct s' *) +val cut_struct_union : string -> string + +val remove_pointer_to_typ : Sil.typ -> Sil.typ + +val is_class : Sil.typ -> bool diff --git a/infer/src/clang/cTypes_decl.ml b/infer/src/clang/cTypes_decl.ml new file mode 100644 index 000000000..4eeb539ad --- /dev/null +++ b/infer/src/clang/cTypes_decl.ml @@ -0,0 +1,366 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Processes types and record declarations by adding them to the tenv *) + +open Utils +open Clang_ast_t +open CFrontend_utils +open CFrontend_utils.General_utils + +module L = Logging +exception Typename_not_found + +(* Adds the predefined types objc_class which is a struct, and Class, *) +(* which is a pointer to objc_class. *) +let add_predefined_types tenv = + let objc_class_mangled = Mangled.from_string CFrontend_config.objc_class in + let objc_class_name = Sil.TN_csu (Sil.Class, objc_class_mangled) in + let objc_class_type_info = + Sil.Tstruct ([], [], Sil.Struct, + Some (Mangled.from_string CFrontend_config.objc_class), [], [], []) in + Sil.tenv_add tenv objc_class_name objc_class_type_info; + let mn = Mangled.from_string CFrontend_config.class_type in + let class_typename = Sil.TN_typedef(mn) in + let class_typ = Sil.Tptr ((Sil.Tvar + (Sil.TN_csu (Sil.Struct, objc_class_mangled))), Sil.Pk_pointer) in + Sil.tenv_add tenv class_typename class_typ; + let typename_objc_object = + Sil.TN_csu (Sil.Struct, Mangled.from_string CFrontend_config.objc_object) in + let id_typedef = Sil.Tptr (Sil.Tvar (typename_objc_object), Sil.Pk_pointer) in + let id_typename = Sil.TN_typedef (Mangled.from_string CFrontend_config.id_cl) in + Sil.tenv_add tenv id_typename id_typedef + +let rec search_for_named_type tenv typ = + let search typename = + match typename with + | Sil.TN_typedef name -> + (match Sil.tenv_lookup tenv typename with + | Some _ -> typename + | None -> + let pot_class_type = Sil.TN_csu (Sil.Class, name) in + match Sil.tenv_lookup tenv pot_class_type with + | Some _ -> pot_class_type + | None -> + let pot_protocol_type = Sil.TN_csu (Sil.Protocol, name) in + match Sil.tenv_lookup tenv pot_protocol_type with + | Some _ -> pot_protocol_type + | None -> + let pot_struct_type = Sil.TN_csu (Sil.Struct, name) in + match Sil.tenv_lookup tenv pot_struct_type with + | Some _ -> pot_struct_type + | None -> + let pot_union_type = Sil.TN_csu (Sil.Union, name) in + match Sil.tenv_lookup tenv pot_union_type with + | Some _ -> pot_union_type + | None -> raise Typename_not_found) + | _ -> typename in + match typ with + | Sil.Tvar typename -> Sil.Tvar (search typename) + | Sil.Tptr (typ, p) -> + Sil.Tptr (search_for_named_type tenv typ, p) + | _ -> typ + +(* Type representation is string in the clang ast. We use a parser for *) +(* parsing and then translating the type The parser is higher-order and *) +(* takes a tenv as needs to do look-ups *) +let string_type_to_sil_type tenv s = + Printing.log_out ~fmt:" ...Trying parsing TYPE from string: '%s'@." s; + if s = "" then ( + Printing.log_stats "\n Empty string parsed as type Void.\n"; + Sil.Tvoid) + else ( + (* NOTE sometimes we need to remove an extra occurrence of the word*) + (* "struct" or "union" used in RecordDecl raw type but not in VarDecl or other*) + (* raw types. This inconsistence gives problems when looking up tenv.*) + (* To overcome that we remove consistently this extra "union"/"struct" everytyme *) + (* we translate a type.*) + (* Example: 'union ' will be *) + (* 'union '*) + let s = (match Str.split (Str.regexp "[ \t]+") s with + | "struct"::"(anonymous":: "struct":: s' -> + (*Printing.log_out " ...Getting rid of the extra 'struct' word@."; *) + string_from_list ("struct"::"(anonymous":: s') + | "union"::"(anonymous":: "union":: s' -> + (*Printing.log_out " ...Getting rid of the extra 'union' word@."; *) + string_from_list ("union"::"(anonymous":: s') + | _ -> s) in + let lexbuf = Lexing.from_string s in + let t = + try + let t = CTypes_parser.parse (Ast_lexer.token) lexbuf in + Printing.log_out ~fmt: + " ...Parsed. Translated with sil TYPE '%s'@." (Sil.typ_to_string t); t + with Parsing.Parse_error -> ( + Printing.log_stats + ~fmt:"\nXXXXXXX PARSE ERROR for string '%s'. RETURNING Void.TODO@.@." s; + Sil.Tvoid) in + try + search_for_named_type tenv t + with Typename_not_found -> Printing.log_stats + ~fmt:"\nXXXXXX Parsed string '%s' as UNKNOWN type name. RETURNING a type name.TODO@.@." s; + t) + +let qual_type_to_sil_type_no_expansions tenv qt = + string_type_to_sil_type tenv (CTypes.get_type qt) + +let opt_type_to_sil_type tenv opt_type = + match opt_type with + | `Type(s) -> qual_type_to_sil_type_no_expansions tenv (Ast_expressions.create_qual_type s) + | `NoType -> Sil.Tvoid + + +let parse_func_type name func_type = + try + let lexbuf = Lexing.from_string func_type in + let (return_type, arg_types) = CTypes_parser.clang_func_type (Ast_lexer.token) lexbuf in + let arg_types = + match arg_types with + | [Sil.Tvoid] -> [] + | _ -> arg_types in + Printing.log_out ~fmt: + " ...Parsed. Translated with sil return type '%s' @." + ((Sil.typ_to_string return_type)^" <- "^(Utils.list_to_string (Sil.typ_to_string) arg_types)); + Some (return_type, arg_types) + with Parsing.Parse_error -> ( + Printing.log_stats ~fmt:"\nXXXXXXX PARSE ERROR for string '%s'." func_type; + None) + +(*In case of typedef like *) +(* typedef struct { f1; f2; ... } s; *) +(* the AST-dump splits the typedef definition from the struct definition. *) +(* The type in the typedef "s" will be "s" and this become detached from the struct definition.*) +(* To avoid circular entry in tenv, we disambiguate this case.*) +(* We check if in tenv there is a "strucs s" defined and we make the type def "s" *) +(* point directly to "struct s" *) +let rec disambiguate_typedef tenv namespace t mn = + match t with + | Sil.Tvar(Sil.TN_typedef mn') -> + if (Mangled.equal mn mn') then + (* This will give a circularity in the definition of typedef in the tenv. *) + (* Eg. TN_typdef(mn) --> TN_typedef(mn). We need to break it*) + let tn = Sil.TN_csu(Sil.Struct, mn) in + (match Sil.tenv_lookup tenv tn with + | Some _ -> + (* There is a struct in tenv, so we make the typedef mn pointing to the struct*) + Printing.log_out ~fmt:" ...Found type TN_typdef('%s') " (Mangled.to_string mn); + Printing.log_out ~fmt:"in typedef of '%s'@." (Mangled.to_string mn); + Printing.log_out ~fmt: + "Avoid circular definition in tenv by pointing the typedef to struc TN_csu('%s')@." + (Mangled.to_string mn); + Sil.Tvar(tn) + | None -> + if add_late_defined_record tenv namespace tn then + disambiguate_typedef tenv namespace t mn + else t) + else t + | _ -> t + +and do_typedef_declaration tenv namespace decl_info name opt_type typedef_decl_info = + if name = CFrontend_config.class_type || name = CFrontend_config.id_cl then () + else + let ns_suffix = Ast_utils.namespace_to_string namespace in + let name = ns_suffix^name in + let mn = Mangled.from_string name in + let typename = Sil.TN_typedef(mn) in + let t = opt_type_to_sil_type tenv opt_type in + (* check for ambiguities in typedef that may create circularities in tenv*) + let typ = disambiguate_typedef tenv namespace t mn in + Printing.log_out ~fmt:"ADDING: TypedefDecl for '%s'" name; + Printing.log_out ~fmt:" with type '%s'\n" (Sil.typ_to_string typ); + Printing.log_out ~fmt:" ...Adding entry to tenv with Typename TN_typedef = '%s'\n" + (Sil.typename_to_string typename); + Sil.tenv_add tenv typename typ + +and get_struct_fields tenv namespace decl_list = + match decl_list with + | [] -> [] + | FieldDecl(decl_info, name, qual_type, field_decl_info):: decl_list' -> + Printing.log_out ~fmt:" ...Defining field '%s'.\n" name; + let id = Ident.create_fieldname (Mangled.from_string name) 0 in + let typ = qual_type_to_sil_type tenv qual_type in + let annotation_items = [] in (* For the moment we don't use them*) + (id, typ, annotation_items):: get_struct_fields tenv namespace decl_list' + | CXXRecordDecl (decl_info, name, opt_type, decl_list, decl_context_info, record_decl_info) + :: decl_list' + (* C++/C Records treated in the same way*) + | RecordDecl (decl_info, name, opt_type, decl_list, decl_context_info, record_decl_info) + :: decl_list'-> + do_record_declaration tenv namespace decl_info name opt_type decl_list decl_context_info record_decl_info; + get_struct_fields tenv namespace decl_list' + | _ :: decl_list' -> get_struct_fields tenv namespace decl_list' + +and do_record_declaration tenv namespace decl_info name opt_type decl_list decl_context_info record_decl_info = + Printing.log_out ~fmt:"ADDING: RecordDecl for '%s'" name; + Printing.log_out ~fmt:" pointer= '%s'\n" decl_info.Clang_ast_t.di_pointer; + if not record_decl_info.Clang_ast_t.rdi_is_complete_definition then + Printing.log_err " ...Warning, definition incomplete. The full definition will probably be later \n"; + let typ = get_declaration_type tenv namespace decl_info name opt_type decl_list decl_context_info record_decl_info in + let typ = expand_structured_type tenv typ in + add_struct_to_tenv tenv typ + +(* For a record declaration it returns/constructs the type *) +and get_declaration_type tenv namespace decl_info n opt_type decl_list decl_context_info record_decl_info = + let ns_suffix = Ast_utils.namespace_to_string namespace in + let n = ns_suffix^n in + Printing.log_out ~fmt: "Record Declaration '%s' defined as struct\n" n; + let non_static_fields = get_struct_fields tenv namespace decl_list in + let non_static_fields = if CTrans_models.is_objc_memory_model_controlled n then + append_no_duplicates_fields [Sil.objc_ref_counter_field] non_static_fields + else non_static_fields in + let static_fields = [] in (* Warning for the moment we do not treat static field. *) + let typ = (match opt_type with + | `Type s -> qual_type_to_sil_type_no_expansions tenv (Ast_expressions.create_qual_type s) + | _ -> assert false) in + let csu = (match typ with + | Sil.Tvar (Sil.TN_csu (csu, _)) -> csu + | _ -> Sil.Struct) in + let name = (match opt_type with (* We need to take the name out of the type as the struct can be anonymous*) + | `Type n' -> Some (Mangled.from_string (CTypes.cut_struct_union n')) + | `NoType -> assert false) in + let superclass_list = [] in (* No super class for structs *) + let methods_list = [] in (* No methods list for structs *) + let item_annotation = Sil.item_annotation_empty in (* No annotations for struts *) + Sil.Tstruct + (non_static_fields, static_fields, csu, name, superclass_list, methods_list, item_annotation) + +(* Look for a record definition that is defined after it is dereferenced. *) +(* It returns true if a new record definition has been added to tenv.*) +and add_late_defined_record tenv namespace typename = + Printing.log_out ~fmt:"!!!! Calling late-defined record '%s'\n" (Sil.typename_to_string typename) ; + match typename with + | Sil.TN_csu(Sil.Struct, name) | Sil.TN_csu(Sil.Union, name) -> + let rec scan decls = + match decls with + | [] -> false + | CXXRecordDecl + (decl_info, record_name, opt_type, decl_list, decl_context_info, record_decl_info) + :: decls' + | RecordDecl + (decl_info, record_name, opt_type, decl_list, decl_context_info, record_decl_info) + :: decls' -> + (match opt_type with + | `Type t -> + (* the string t contains the name of the type preceded by the word struct. *) + let t_no_struct = CTypes.cut_struct_union t in + let pot_struct_type = Sil.TN_csu (Sil.Struct, (Mangled.from_string t_no_struct)) in + let pot_union_type = Sil.TN_csu (Sil.Union, (Mangled.from_string t_no_struct)) in + if (Sil.typename_equal typename pot_struct_type || + Sil.typename_equal typename pot_union_type) && + record_decl_info.Clang_ast_t.rdi_is_complete_definition then ( + Printing.log_out ~fmt:"!!!! Adding late-defined record '%s'\n" t; + do_record_declaration tenv namespace decl_info record_name opt_type decl_list + decl_context_info record_decl_info; + true) + else scan decls' + | _ -> scan decls') + | LinkageSpecDecl(_, decl_list', _):: decls' -> scan (decl_list'@decls') + | _:: decls' -> scan decls' in + scan !CFrontend_config.global_translation_unit_decls + | _ -> false + +(* Look for a typedef definition that is defined after it is used. *) +(* It returns true if a new typedef definition has been added to tenv.*) +and add_late_defined_typedef tenv namespace typename = + Printing.log_out ~fmt:"Calling late-defined typedef '%s'\n" (Sil.typename_to_string typename); + match typename with + | Sil.TN_typedef name -> + let rec scan decls = + match decls with + | [] -> false + | TypedefDecl (decl_info, name', opt_type, tdi) :: decls' -> + (match opt_type with + | `Type t -> + if (Mangled.to_string name) = name' then ( + Printing.log_out ~fmt:"!!!! Adding late-defined typedef '%s'\n" t; + do_typedef_declaration tenv namespace decl_info name' opt_type tdi; + true) + else scan decls' + | _ -> scan decls') + | LinkageSpecDecl(_, decl_list', _):: decls' -> scan (decl_list'@decls') + | _:: decls' -> scan decls' in + scan !CFrontend_config.global_translation_unit_decls + | _ -> false + +(* Expand a named type Tvar if it has a definition in tenv. This is used for Tenum, Tstruct, etc. *) +and expand_structured_type tenv typ = + match typ with + | Sil.Tvar tn -> + (match Sil.tenv_lookup tenv tn with + | Some t -> + Printing.log_out + ~fmt:" Type expanded with type '%s' found in tenv@." (Sil.typ_to_string t); + if Sil.typ_equal t typ then + typ + else expand_structured_type tenv t + | None -> if (add_late_defined_record tenv None tn || + add_late_defined_typedef tenv None tn) then + expand_structured_type tenv typ + else typ) + | Sil.Tptr(t, _) -> typ (*do not expand types under pointers *) + | _ -> typ + +and add_struct_to_tenv tenv typ = + let typ = expand_structured_type tenv typ in + let csu = match typ with + | Sil.Tstruct(_, _, csu, _, _, _, _) -> csu + | _ -> assert false in + let mangled = CTypes.get_name_from_struct typ in + let typename = Sil.TN_csu(csu, mangled) in + Printing.log_out ~fmt:" >>>Adding struct to tenv mangled='%s'\n" (Mangled.to_string mangled); + Printing.log_out ~fmt:" >>>Adding struct to tenv typ='%s'\n" (Sil.typ_to_string typ); + Printing.log_out ~fmt:" >>>with Key Typename TN_csu('%s')\n" (Sil.typename_to_string typename); + Printing.log_out ~fmt:" >>>Adding entry to tenv ('%s'," (Sil.typename_to_string typename); + Printing.log_out ~fmt:"'%s')\n" (Sil.typ_to_string typ); + Sil.tenv_add tenv typename typ; + Printing.log_out ~fmt:" >>>Verifying that Typename TN_csu('%s') is in tenv\n" + (Sil.typename_to_string typename); + (match Sil.tenv_lookup tenv typename with + | Some t -> Printing.log_out ~fmt:" >>>OK. Found typ='%s'\n" (Sil.typ_to_string t) + | None -> Printing.log_out " >>>NOT Found!!\n") + +and qual_type_to_sil_type_general tenv qt no_pointer = + let typ = string_type_to_sil_type tenv (CTypes.get_type qt) in + match typ with + | Sil.Tptr(np_typ, _) when no_pointer -> + expand_structured_type tenv np_typ + | _ -> expand_structured_type tenv typ + +(* Translate a qual_type from clang to sil type. It uses the raw field *) +(* (rather than desugared) *) +and qual_type_to_sil_type tenv qt = + qual_type_to_sil_type_general tenv qt false + +and qual_type_to_sil_type_np tenv qt = + qual_type_to_sil_type_general tenv qt true + +and type_name_to_sil_type tenv name = + qual_type_to_sil_type_general tenv (Ast_expressions.create_qual_type name) false + +let get_type_from_expr_info ei tenv = + let qt = ei.Clang_ast_t.ei_qual_type in + qual_type_to_sil_type tenv qt + +let class_from_pointer_type tenv qual_type = + match qual_type_to_sil_type tenv qual_type with + | Sil.Tptr( Sil.Tvar (Sil.TN_typedef name), _) -> Mangled.to_string name + | Sil.Tptr( Sil.Tvar (Sil.TN_csu (_, name)), _) -> Mangled.to_string name + | _ -> assert false + +let get_class_type_np tenv expr_info obj_c_message_expr_info = + let qt = + match obj_c_message_expr_info.Clang_ast_t.omei_receiver_kind with + | `Class qt -> qt + | _ -> expr_info.Clang_ast_t.ei_qual_type in + qual_type_to_sil_type tenv qt + +let extract_sil_type_from_stmt tenv s = + let qt = CTypes.extract_type_from_stmt s in + qual_type_to_sil_type tenv qt + +let get_type_curr_class tenv curr_class_opt = + let name = CContext.get_curr_class_name curr_class_opt in + let typ = Sil.Tvar (Sil.TN_csu (Sil.Class, (Mangled.from_string name))) in + expand_structured_type tenv typ diff --git a/infer/src/clang/cTypes_decl.mli b/infer/src/clang/cTypes_decl.mli new file mode 100644 index 000000000..1c62002f7 --- /dev/null +++ b/infer/src/clang/cTypes_decl.mli @@ -0,0 +1,44 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Processes types and record declarations by adding them to the tenv *) + +val get_declaration_type : Sil.tenv -> string option -> Clang_ast_t.decl_info -> string -> +Clang_ast_t.opt_type -> Clang_ast_t.decl list -> Clang_ast_t.decl_context_info -> +Clang_ast_t.record_decl_info -> Sil.typ + +val add_struct_to_tenv : Sil.tenv -> Sil.typ -> unit + +val do_typedef_declaration : Sil.tenv -> string option -> Clang_ast_t.decl_info -> string -> +Clang_ast_t.opt_type -> Clang_ast_t.typedef_decl_info -> unit + +val do_record_declaration : Sil.tenv -> string option -> Clang_ast_t.decl_info -> string -> +Clang_ast_t.opt_type -> Clang_ast_t.decl list -> Clang_ast_t.decl_context_info -> +Clang_ast_t.record_decl_info -> unit + +val parse_func_type : string -> string -> (Sil.typ * Sil.typ list) option + +(* Adds the predefined types objc_class which is a struct, *) +(* and Class, which is a pointer to objc_class. *) +val add_predefined_types : Sil.tenv -> unit + +val qual_type_to_sil_type : Sil.tenv -> Clang_ast_t.qual_type -> Sil.typ + +val qual_type_to_sil_type_np : Sil.tenv -> Clang_ast_t.qual_type -> Sil.typ + +val class_from_pointer_type : Sil.tenv -> Clang_ast_t.qual_type -> string + +val get_class_type_np : Sil.tenv -> Clang_ast_t.expr_info -> + Clang_ast_t.obj_c_message_expr_info -> Sil.typ + +val extract_sil_type_from_stmt : Sil.tenv -> Clang_ast_t.stmt -> Sil.typ + +val get_type_curr_class : Sil.tenv -> CContext.curr_class -> Sil.typ + +val expand_structured_type : Sil.tenv -> Sil.typ -> Sil.typ + +val get_type_from_expr_info : Clang_ast_t.expr_info -> Sil.tenv -> Sil.typ + +val type_name_to_sil_type : Sil.tenv -> string -> Sil.typ diff --git a/infer/src/clang/cTypes_parser.mly b/infer/src/clang/cTypes_parser.mly new file mode 100644 index 000000000..93d5b6c1e --- /dev/null +++ b/infer/src/clang/cTypes_parser.mly @@ -0,0 +1,236 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +/* Parser for types in the ast of clang */ +%{ + +%} + +%token INLINE STATIC CONST STARCONST EXTERN VOID CHAR SHORT INT LONG FLOAT DOUBLE UND_BOOL VOLATILE STARVOLATILE CARRET +%token CLASS STRUCT UNION UND_UND_UINT16_ UND_UND_UINT16_T UND_UND_UINT32_ UND_UND_INT32_T UND_UND_UINT32_T +%token UND_UND_INT64_T UND_UND_UINT64_T UINT8 UINT16 UINT32 UINT64 UNSIGNED SIGNED ENUM BUILTIN_FN_TYPE TYPENAME +%token UND_UND_STRONG UND_UND_UNSAFE_RETAIN UND_UND_WEAK UND_UND_AUTORELEASING NOEXCEPT +%token STAR_UND_UND_STRONG STAR_UND_UND_UNSAFE_UNRETAINED UND_UND_UNSAFE_UNRETAINED STAR_UND_UND_WEAK STAR_UND_UND_AUTORELEASING + +%token DOT SEMICOLON COLON COMMA SINGLE_QUOTE DOUBLE_QUOTE REV_QUOTE +%token PERCENT AMPERSAND EXCLAMATION EQUAL MINUS PLUS RESTRICT +%token LEFT_CHEVRON RIGHT_CHEVRON LEFT_PARENTHESIS RIGHT_PARENTHESIS LEFT_SQBRACKET RIGHT_SQBRACKET LEFT_BRACE RIGHT_BRACE +%token STAR PIPE SLASH BACKSLASH + +%token HEXA +%token NUMBER +%token IDENT +%token ANONYM_IDENT +%token NESTED_IDENT +%token NESTED_ANONYM_IDENT + +%token EOF + +%start parse +%start pointer_clang_type +%start clang_func_type + +%type pointer_clang_type +%type parse +%type <(Sil.typ * Sil.typ list)> clang_func_type + +%% + +keyword: + | ENUM { "enum" } + | UNSIGNED { "unsigned" } + | SIGNED { "signed" } + | CLASS { "class" } + | STRUCT { "struct" } + | UNION { "union" } + | VOID { "void" } + | CHAR { "char" } + | SHORT { "short" } + | INT { "int" } + | LONG { "long" } + | TYPENAME { "typename" } + | NOEXCEPT { "noexcept" } + | CONST { "const" } + | STARCONST { "*const" } + | FLOAT { "float" } + | DOUBLE { "double" } + | VOLATILE { "volatile" } + | RESTRICT { "restrict" } + | STARVOLATILE { "*volatile" } + + +ident: + | IDENT { $1 } +; + +anonym_ident: + | ANONYM_IDENT { $1 } +; + +nested_ident: + | NESTED_IDENT { $1 } +; + +nested_anonym_ident: + | NESTED_ANONYM_IDENT { $1 } +; + +ident_csu: + | ident {$1} + | anonym_ident { $1 } + | nested_ident { $1 } + | nested_anonym_ident { $1 } + +identk: + | ident { $1 } + | keyword { $1 } +; + +csu_sil: + | CLASS { Sil.Class } + | STRUCT { Sil.Struct } + | UNION { Sil.Union } +; + +arc_qualifier: + | UND_UND_STRONG { Sil.Pk_pointer } + | UND_UND_UNSAFE_UNRETAINED { Sil.Pk_objc_unsafe_unretained } + | UND_UND_WEAK { Sil.Pk_objc_weak } + | UND_UND_AUTORELEASING { Sil.Pk_objc_autoreleasing } +; + +star_arc_qualifier: + | STAR_UND_UND_STRONG { Sil.Pk_pointer } + | STAR_UND_UND_UNSAFE_UNRETAINED { Sil.Pk_objc_unsafe_unretained } + | STAR_UND_UND_WEAK { Sil.Pk_objc_weak } + | STAR_UND_UND_AUTORELEASING { Sil.Pk_objc_autoreleasing } +; + +number_list: + | { [] } + | NUMBER { [$1] } /* For dealing with an unspecified number of arguments */ + | NUMBER COMMA number_list { $1::$3 } +; + +pointer_clang_type_list: + | { [] } + | DOT DOT DOT { [] } /* For dealing with an unspecified number of arguments */ + | pointer_clang_type { [$1]} + | pointer_clang_type COMMA pointer_clang_type_list { $1::$3 } +; + +array_index: + | LEFT_SQBRACKET NUMBER RIGHT_SQBRACKET { Sil.Const (Sil.Cint((Sil.int_of_int64_kind (Int64.of_string $2) (Sil.IInt)))) } +; + +block_type: + | pointer_clang_type LEFT_PARENTHESIS CARRET RIGHT_PARENTHESIS + LEFT_PARENTHESIS pointer_clang_type_list RIGHT_PARENTHESIS + { Sil.Tfun false } + | pointer_clang_type LEFT_PARENTHESIS CARRET arc_qualifier RIGHT_PARENTHESIS + LEFT_PARENTHESIS pointer_clang_type_list RIGHT_PARENTHESIS + { Sil.Tfun false } + | pointer_clang_type LEFT_PARENTHESIS CARRET CONST arc_qualifier RIGHT_PARENTHESIS + LEFT_PARENTHESIS pointer_clang_type_list RIGHT_PARENTHESIS + { Sil.Tfun false } + | pointer_clang_type LEFT_PARENTHESIS CARRET CONST RIGHT_PARENTHESIS + LEFT_PARENTHESIS pointer_clang_type_list RIGHT_PARENTHESIS + { Sil.Tfun false } +; + +array_qualifier: + | CONST {} + | arc_qualifier {} /* Currently array do not deal with arc qualifiers */ +; + +template: + | LEFT_CHEVRON pointer_clang_type_list RIGHT_CHEVRON {} + | LEFT_CHEVRON number_list RIGHT_CHEVRON {} +; + +/* Nested arrays must be translated inside-out. */ +array_indices: + | array_index { [$1] } + | array_index array_indices { $1 :: $2 } + +pointer_clang_type: + | pointer_clang_type array_indices { List.fold_left (fun t x -> Sil.Tarray (t, x)) $1 (List.rev $2) } + | pointer_clang_type LEFT_SQBRACKET RIGHT_SQBRACKET { Sil.Tptr($1, Sil.Pk_pointer) } + | pointer_clang_type STAR { Sil.Tptr($1, Sil.Pk_pointer) } + | pointer_clang_type AMPERSAND { Sil.Tptr($1, Sil.Pk_reference) } + | pointer_clang_type STARCONST arc_qualifier { Sil.Tptr($1, $3) } + | arc_qualifier pointer_clang_type { Sil.Tptr($2,$1) } + | pointer_clang_type STARCONST { Sil.Tptr($1, Sil.Pk_pointer) } + | pointer_clang_type STARVOLATILE { Sil.Tptr($1, Sil.Pk_pointer) } + | pointer_clang_type STAR RESTRICT { Sil.Tptr($1, Sil.Pk_pointer) } + | pointer_clang_type star_arc_qualifier { Sil.Tptr($1, $2) } + | clang_type { $1 } + | clang_type array_qualifier array_indices { List.fold_left (fun t x -> Sil.Tarray (t, x)) $1 (List.rev $3) } + | clang_type template { $1 } + | pointer_clang_type LEFT_PARENTHESIS STAR RIGHT_PARENTHESIS + LEFT_PARENTHESIS pointer_clang_type_list RIGHT_PARENTHESIS + { Sil.Tptr(Sil.Tfun false, Sil.Pk_pointer) } + | pointer_clang_type LEFT_PARENTHESIS STAR RIGHT_PARENTHESIS + LEFT_PARENTHESIS pointer_clang_type_list RIGHT_PARENTHESIS NOEXCEPT + { Sil.Tptr(Sil.Tfun false, Sil.Pk_pointer) } + | block_type { Sil.Tptr($1, Sil.Pk_pointer) } + | pointer_clang_type LEFT_PARENTHESIS pointer_clang_type_list RIGHT_PARENTHESIS + { CFrontend_utils.Printing.log_err "WARNING: PARSING with TFun!\n"; + Sil.Tfun false } +; + +clang_type: + | UND_UND_UINT16_ { Sil.Tint Sil.IUInt } + | UND_UND_UINT16_T { Sil.Tint Sil.IUInt } + | UND_UND_UINT32_ { Sil.Tint Sil.IUInt } + | UND_UND_UINT32_T { Sil.Tint Sil.IUInt } + | UND_UND_UINT64_T { Sil.Tint Sil.IUInt } + | UND_UND_INT64_T { Sil.Tint Sil.IInt } + | UND_UND_INT32_T { Sil.Tint Sil.IInt } + | UINT8 { Sil.Tint Sil.IUInt } + | UINT16 { Sil.Tint Sil.IUInt } + | UINT32 { Sil.Tint Sil.IUInt } + | UINT64 { Sil.Tint Sil.IUInt } + | UNSIGNED INT { Sil.Tint Sil.IUInt } + | UNSIGNED LONG LONG { Sil.Tint Sil.IULongLong } + | UNSIGNED LONG { Sil.Tint Sil.IULong } + | UNSIGNED SHORT { Sil.Tint Sil.IUShort } + | FLOAT { Sil.Tfloat Sil.FFloat } + | DOUBLE { Sil.Tfloat Sil.FDouble } + | VOID { Sil.Tvoid } + | CHAR { Sil.Tint Sil.IChar } + | SIGNED CHAR { Sil.Tint Sil.ISChar } + | UNSIGNED CHAR { Sil.Tint Sil.IUChar } + | INT { Sil.Tint Sil.IInt } + | UND_BOOL { Sil.Tint Sil.IBool } + | SHORT { Sil.Tint Sil.IShort } + | LONG { Sil.Tint Sil.ILong } + | LONG LONG { Sil.Tint Sil.ILongLong } + | LONG DOUBLE { Sil.Tfloat Sil.FLongDouble } + | ENUM ident {Sil.Tvar (Sil.TN_enum (Mangled.from_string ("enum "^$2))) } + | BUILTIN_FN_TYPE { CFrontend_utils.Printing.log_err "WARNING: Parsing this with Tfun!\n"; + Sil.Tfun false } + | CONST pointer_clang_type { $2 } + | CONST TYPENAME pointer_clang_type { $3 } + | VOLATILE pointer_clang_type { $2 } + | ident ANONYM_IDENT { CFrontend_utils.Printing.log_out " ...Found just an identifier modified with a protocol. Ignoring protocol!. Parsing as Named Type!\n"; + Sil.Tvar (Sil.TN_typedef(Mangled.from_string $1))} + | ident { CFrontend_utils.Printing.log_out ~fmt:" ...Found just an identifier. Parsing as Named Type %s !\n" $1; + Sil.Tvar (Sil.TN_typedef(Mangled.from_string $1))} + | csu_sil ident_csu { let typename=Sil.TN_csu($1, Mangled.from_string $2) in + Sil.Tvar typename } +; + +clang_type_list: + | clang_type COMMA clang_type_list {$1 :: $3} + | clang_type {[$1]} + +clang_func_type: + | clang_type LEFT_PARENTHESIS clang_type_list RIGHT_PARENTHESIS + {($1, $3)} + + +parse: + | pointer_clang_type { $1 } diff --git a/infer/src/clang/cVar_decl.ml b/infer/src/clang/cVar_decl.ml new file mode 100644 index 000000000..329015c03 --- /dev/null +++ b/infer/src/clang/cVar_decl.ml @@ -0,0 +1,169 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Process variable declarations by saving them as local or global variables. *) +(** Computes the local variables of a function or method to be added to the procdesc *) + +open Utils +open CFrontend_utils +open Clang_ast_t + +module L = Logging + +(* For a variable declaration it return/construct the type *) +let get_var_type tenv name t = + let typ = CTypes_decl.qual_type_to_sil_type tenv t in + Printing.log_out ~fmt:" Getting/Defining type for variable '%s'" name; + Printing.log_out ~fmt:" as sil type '%s'\n" (Sil.typ_to_string typ); + typ + +(* NOTE: Currently we use this function to avoid certain C++ global variable definition defined *) +(* in traits and config files.*) +(* We recogneze them because these declaration have di_parent_pointer and di_previous_decl defined*) +let global_to_be_added di = + (di.Clang_ast_t.di_parent_pointer = None) && (di.Clang_ast_t.di_previous_decl =`None) + +let global_var_decl tenv namespace decl_info name t = + Printing.log_out ~fmt:"PASSING: VarDecl for '%s' to global procdesc" name; + Printing.log_out ~fmt:" pointer= '%s'\n" decl_info.Clang_ast_t.di_pointer; + if global_to_be_added decl_info then ( + let typ = get_var_type tenv name t in + Printing.log_out ~fmt:" >>> Adding entry to global procdesc: ('%s', " name; + Printing.log_out ~fmt:"'%s')\n" (Sil.typ_to_string typ); + CGlobal_vars.add name typ) + else Printing.log_out ~fmt:"SKIPPING VarDecl for '%s'\n" name + +let rec lookup_ahead_for_vardecl context pointer var_name kind decl_list = + match decl_list with + | [] -> Printing.log_out ~fmt:" Failing when looking ahead for variable '%s'\n" var_name; + assert false (* nothing has been found ahead, maybe something bad in the AST *) + | VarDecl(decl_info, var_name', t, _) :: rest when var_name = var_name' -> + if global_to_be_added decl_info then ( + let tenv = CContext.get_tenv context in + Printing.log_out ~fmt:"ADDING (later-defined): VarDecl '%s' to global procdesc\n" var_name'; + let typ = get_var_type tenv var_name' t in + Printing.log_out ~fmt:" >>> Adding (later-defined) entry to global procdesc: ('%s', " var_name'; + Printing.log_out ~fmt:"'%s')\n" (Sil.typ_to_string typ); + CGlobal_vars.add var_name' typ; + let mangled_var_name = Mangled.from_string var_name' in + let global_var = CGlobal_vars.find mangled_var_name in + CGlobal_vars.var_get_name global_var) + else (Printing.log_out ~fmt:"SKIPPING VarDecl for '%s'\n" var_name; + lookup_ahead_for_vardecl context pointer var_name kind rest) + | _ :: rest -> + lookup_ahead_for_vardecl context pointer var_name kind rest + +let lookup_var_static_globals context name = + let remove_separator s = + match Str.split (Str.regexp_string Config.anonymous_block_num_sep) s with + | s'':: _ -> s'' + | _ -> assert false in + let remove_block_prefix s = + match Str.split (Str.regexp_string Config.anonymous_block_prefix) s with + | [_; s''] -> s'' + | [s''] -> s'' + | _ -> assert false in + let remove_block_name pname = + let s = Procname.to_string pname in + let s'= remove_block_prefix s in + let s'' = if s'= s then s' + else remove_separator s' in + s'' in + let pname = Cfg.Procdesc.get_proc_name context.CContext.procdesc in + let str_pname = remove_block_name pname in + let static_name = Sil.mk_static_local_name str_pname name in + Printing.log_out ~fmt:" ...Looking for variable '%s' in static globals...\n" static_name; + let var_name = Mangled.from_string static_name in + let global_var = CGlobal_vars.find var_name in + let var = CGlobal_vars.var_get_name global_var in + Printing.log_out ~fmt:" ...Variable '%s' found in static globals!!\n" (Sil.pvar_to_string var); + var + +let lookup_var stmt_info context pointer var_name kind = + let pvar = CContext.LocalVars.lookup_var context stmt_info.Clang_ast_t.si_pointer var_name kind in + match pvar with + | Some var -> var + | None -> + try + lookup_var_static_globals context var_name + with Not_found -> + (Printing.log_out ~fmt:"Looking on later-defined decls for '%s'\n" var_name; + let decl_list = !CFrontend_config.global_translation_unit_decls in + lookup_ahead_for_vardecl context pointer var_name kind decl_list ) + +(* Traverses the body of the method top down and collects the *) +(* variable definitions in a map in the context. To be able to find the right variable name *) +(* in the reference instructions, all the variable names are also saved in a map from pointers *) +(* to variable names to be used in the translation of the method's body. *) +let rec get_variables_stmt context (stmt : Clang_ast_t.stmt) : unit = + match stmt with + | DeclStmt(_, lstmt, decl_list) -> + get_variables_decls context decl_list; + get_fun_locals context lstmt; + | DeclRefExpr(stmt_info, stmt_list, expr_info, decl_ref_expr_info) -> + (* Notice that DeclRefExpr is the reference to a declared var/function/enum... *) + (* so no declaration here *) + Printing.log_out ~fmt:"Collecting variables, passing from DeclRefExpr '%s'\n" + stmt_info.Clang_ast_t.si_pointer; + let var_name = CTrans_utils.get_name_decl_ref_exp_info decl_ref_expr_info stmt_info in + let kind = CTrans_utils.get_decl_kind decl_ref_expr_info in + (match kind with + | `EnumConstant | `ObjCIvar | `CXXMethod | `ObjCProperty -> () + | _ -> + let pvar = lookup_var stmt_info context stmt_info.Clang_ast_t.si_pointer var_name kind in + CContext.LocalVars.add_pointer_var stmt_info.Clang_ast_t.si_pointer pvar context) + | CompoundStmt(stmt_info, lstmt) -> + Printing.log_out ~fmt:"Collecting variables, passing from CompoundStmt '%s'\n" + stmt_info.Clang_ast_t.si_pointer; + CContext.LocalVars.enter_and_leave_scope context get_fun_locals lstmt + | ForStmt(stmt_info, lstmt) -> + Printing.log_out ~fmt:"Collecting variables, passing from ForStmt '%s'\n" + stmt_info.Clang_ast_t.si_pointer; + CContext.LocalVars.enter_and_leave_scope context get_fun_locals lstmt + | _ -> + let lstmt = Ast_utils.get_stmts_from_stmt stmt in + get_fun_locals context lstmt + +and get_fun_locals context (stmts : Clang_ast_t.stmt list) : unit = + match stmts with + | [] -> () + | stmt:: rest -> + (get_variables_stmt context stmt); + (get_fun_locals context rest) + +(* Collects the local of a function. *) +and get_variables_decls context (decl_list : Clang_ast_t.decl list) : unit = + let do_one_decl decl = + match decl with + | VarDecl (decl_info, name, qual_type, var_decl_info) -> + Printing.log_out ~fmt:"Collecting variables, passing from VarDecl '%s'\n" decl_info.Clang_ast_t.di_pointer; + let typ = get_var_type context.CContext.tenv name qual_type in + (match var_decl_info.Clang_ast_t.vdi_storage_class with + | Some "static" -> + let pname = Cfg.Procdesc.get_proc_name context.CContext.procdesc in + let static_name = (Procname.to_string pname)^"_"^name in + CGlobal_vars.add static_name typ; + let var = Sil.mk_pvar_global (Mangled.from_string static_name) in + CContext.LocalVars.add_pointer_var decl_info.Clang_ast_t.di_pointer var context + | _ -> + CContext.LocalVars.add_local_var context name typ decl_info.Clang_ast_t.di_pointer + (CFrontend_utils.General_utils.is_static_var var_decl_info)) + | CXXRecordDecl(di, n', ot, dl, dci, rdi) + | RecordDecl(di, n', ot, dl, dci, rdi) -> + let typ = CTypes_decl.get_declaration_type context.CContext.tenv context.CContext.namespace + di n' ot dl dci rdi in + CTypes_decl.add_struct_to_tenv context.CContext.tenv typ + | TypedefDecl (decl_info, name, opt_type, typedef_decl_info) -> + CTypes_decl.do_typedef_declaration context.CContext.tenv context.CContext.namespace + decl_info name opt_type typedef_decl_info + | StaticAssertDecl decl_info -> (* We do not treat Assertions. *) + Printing.log_out + ~fmt:"WARNING: When collecting variables, passing from StaticAssertDecl '%s'. Skipped.\n" + decl_info.Clang_ast_t.di_pointer + | _ -> Printing.log_out + ~fmt:"!!! When collecting locals of a function found '%s'. Cannot continue\n\n" + (Clang_ast_j.string_of_decl decl); + assert false in + list_iter do_one_decl decl_list diff --git a/infer/src/clang/cVar_decl.mli b/infer/src/clang/cVar_decl.mli new file mode 100644 index 000000000..ec17ab9cc --- /dev/null +++ b/infer/src/clang/cVar_decl.mli @@ -0,0 +1,13 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Process variable declarations by saving them as local or global variables. *) +(** Computes the local variables of a function or method to be added to the procdesc *) + +val get_fun_locals : CContext.t -> Clang_ast_t.stmt list -> unit + +val global_var_decl : Sil.tenv -> string option -> Clang_ast_t.decl_info -> string -> +Clang_ast_t.qual_type -> unit + diff --git a/infer/src/clang/objcCategory_decl.ml b/infer/src/clang/objcCategory_decl.ml new file mode 100644 index 000000000..2223129d8 --- /dev/null +++ b/infer/src/clang/objcCategory_decl.ml @@ -0,0 +1,71 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +open Utils +open CFrontend_utils + +module L = Logging + +(** In this module an ObjC category declaration or implementation is processed. The category *) +(** is saved in the tenv as a struct with the corresponding fields and methods , and the class it belongs to *) + +(* Name used for category with no name, i.e., "" *) +let noname_category class_name = + CFrontend_config.emtpy_name_category^class_name + +let cat_class_decl dr = + match dr.Clang_ast_t.dr_name with + | Some n -> n + | _ -> assert false + +let get_class_from_category_decl category_decl_info = + match category_decl_info.Clang_ast_t.odi_class_interface with + | Some dr -> cat_class_decl dr + | _ -> assert false + +let get_class_from_category_impl category_impl_info = + match category_impl_info.Clang_ast_t.ocidi_class_interface with + | Some dr -> cat_class_decl dr + | _ -> assert false + +let get_category_name_from_category_impl category_impl_info = + match category_impl_info.Clang_ast_t.ocidi_category_decl with + | Some dr -> cat_class_decl dr + | _ -> assert false + +(* Add potential extra fields defined only in the category *) +(* to the corresponding class. Update the tenv accordingly.*) +let process_category tenv name class_name decl_list = + let name = if name ="" then noname_category class_name else name in + Printing.log_out ~fmt:"Now name is '%s'\n" name; + let curr_class = CContext.ContextCategory (name, class_name) in + let fields = CField_decl.get_fields tenv curr_class decl_list in + let methods = ObjcProperty_decl.get_methods curr_class decl_list in + let mang_name = Mangled.from_string class_name in + let class_tn_name = Sil.TN_csu (Sil.Class, mang_name) in + match Sil.tenv_lookup tenv class_tn_name with + | Some Sil.Tstruct (intf_fields, _, _, _, superclass, intf_methods, annotation) -> + let new_fields = General_utils.append_no_duplicates_fields fields intf_fields in + let new_methods = General_utils.append_no_duplicates_methods methods intf_methods in + let class_type_info = + Sil.Tstruct ( + new_fields, [], Sil.Class, Some mang_name, superclass, new_methods, annotation + ) in + Printing.log_out ~fmt:" Updating info for class '%s' in tenv\n" class_name; + Sil.tenv_add tenv class_tn_name class_type_info; + curr_class + | _ -> assert false + +let category_decl tenv name category_decl_info decl_list = + Printing.log_out ~fmt:"ADDING: ObjCCategoryDecl for '%s'\n" name; + let class_name = get_class_from_category_decl category_decl_info in + process_category tenv name class_name decl_list + +let category_impl_decl tenv name decl_info category_impl_decl_info decl_list = + let category_name = get_category_name_from_category_impl category_impl_decl_info in + Printing.log_out ~fmt:"ADDING: ObjCCategoryImplDecl for '%s'\n" category_name; + let cat_class = get_class_from_category_impl category_impl_decl_info in + process_category tenv category_name cat_class decl_list + diff --git a/infer/src/clang/objcCategory_decl.mli b/infer/src/clang/objcCategory_decl.mli new file mode 100644 index 000000000..ce89855cb --- /dev/null +++ b/infer/src/clang/objcCategory_decl.mli @@ -0,0 +1,13 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** In this module an ObjC category declaration or implementation is processed. The category *) +(** is saved in the tenv as a struct with the corresponding fields and methods , and the class it belongs to *) + +val category_decl : Sil.tenv -> string -> Clang_ast_t.obj_c_category_decl_info -> Clang_ast_t.decl list +-> CContext.curr_class + +val category_impl_decl : Sil.tenv -> string -> Clang_ast_t.decl_info -> +Clang_ast_t.obj_c_category_impl_decl_info -> Clang_ast_t.decl list -> CContext.curr_class diff --git a/infer/src/clang/objcInterface_decl.ml b/infer/src/clang/objcInterface_decl.ml new file mode 100644 index 000000000..9cb814700 --- /dev/null +++ b/infer/src/clang/objcInterface_decl.ml @@ -0,0 +1,266 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** In this module an ObjC interface declaration or implementation is processed. The class *) +(** is saved in the tenv as a struct with the corresponding fields, potential superclass and *) +(** list of defined methods *) + +(* ObjectiveC doesn't have a notion of static or class fields. *) +(* So, in this module we translate a class into a sil srtuct with an empty list of static fields.*) + +open Utils +open CFrontend_utils +open CFrontend_utils.General_utils +open Clang_ast_t + +module L = Logging + +let objc_class_str = "ObjC-Class" + +let objc_class_annotation = + [({ Sil.class_name=objc_class_str; Sil.parameters=[]}, true)] + +let is_objc_class_annotation a = + match a with + | [({Sil.class_name=n; Sil.parameters=[]},true)] when n=objc_class_str -> true + | _ -> false + +let is_pointer_to_objc_class tenv typ = + match typ with + | Sil.Tptr (Sil.Tvar (Sil.TN_csu (Sil.Class, cname)), _) -> + (match Sil.tenv_lookup tenv (Sil.TN_csu (Sil.Class, cname)) with + | Some Sil.Tstruct(_, _, Sil.Class, _, _, _, a) when is_objc_class_annotation a -> true + | _ -> false) + | Sil.Tptr (Sil.Tstruct(_, _, Sil.Class, _, _, _, a), _) when + is_objc_class_annotation a -> true + | _ -> false + +let get_super_interface_decl otdi_super = + match otdi_super with + | Some dr -> dr.Clang_ast_t.dr_name + | _ -> None + +let get_protocols protocols = + let protocol_names = list_map ( + fun decl -> match decl.Clang_ast_t.dr_name with + | Some name -> name + | None -> assert false + ) protocols in + protocol_names + +(*The superclass is the first element in the list of super classes of structs in the tenv, *) +(* then come the protocols and categories. *) +let get_interface_superclasses super_opt protocols = + let super_class = + match super_opt with + | None -> [] + | Some super -> [(Sil.Class, Mangled.from_string super)] in + let protocol_names = list_map ( + fun name -> (Sil.Protocol, Mangled.from_string name) + ) protocols in + let super_classes = super_class@protocol_names in + super_classes + +let create_curr_class_and_superclasses_fields tenv decl_list class_name otdi_super otdi_protocols = + let super = get_super_interface_decl otdi_super in + let protocols = get_protocols otdi_protocols in + let curr_class = CContext.ContextCls (class_name, super, protocols) in + let superclasses = get_interface_superclasses super protocols in + let fields = CField_decl.get_fields tenv curr_class decl_list in + curr_class, superclasses, fields + +let update_curr_class curr_class superclasses = + let get_protocols protocols = list_fold_right ( + fun protocol converted_protocols -> + match protocol with + | (Sil.Protocol, name) -> (Mangled.to_string name):: converted_protocols + | _ -> converted_protocols + ) protocols [] in + match curr_class with + | CContext.ContextCls (class_name, _, _) -> + let super, protocols = + match superclasses with + | (Sil.Class, name):: rest -> Some (Mangled.to_string name), get_protocols rest + | _ -> None, get_protocols superclasses in + CContext.ContextCls (class_name, super, protocols) + | _ -> assert false + +(* Adds pairs (interface name, interface_type_info) to the global environment. *) +let add_class_to_tenv tenv class_name decl_list obj_c_interface_decl_info = + Printing.log_out ~fmt:"ADDING: ObjCInterfaceDecl for '%s'\n" class_name; + let interface_name = CTypes.mk_classname class_name in + let curr_class, superclasses, fields = + create_curr_class_and_superclasses_fields tenv decl_list class_name + obj_c_interface_decl_info.Clang_ast_t.otdi_super + obj_c_interface_decl_info.Clang_ast_t.otdi_protocols in + let methods = ObjcProperty_decl.get_methods curr_class decl_list in + let fields_sc = CField_decl.fields_superclass tenv obj_c_interface_decl_info in + list_iter (fun (fn, ft, _) -> + Printing.log_out ~fmt:"----->SuperClass field: '%s' " (Ident.fieldname_to_string fn); + Printing.log_out ~fmt:"type: '%s'\n" (Sil.typ_to_string ft)) fields_sc; + (*In case we found categories, or partial definition of this class earlier and they are already in the tenv *) + let fields, superclasses, methods = + match Sil.tenv_lookup tenv interface_name with + | Some Sil.Tstruct(saved_fields, _, _, _, saved_superclasses, saved_methods, _) -> + append_no_duplicates_fields fields saved_fields, + append_no_duplicates_csu superclasses saved_superclasses, + append_no_duplicates_methods methods saved_methods + | _ -> fields, superclasses, methods in + let fields = append_no_duplicates_fields fields fields_sc in + (* We add the special hidden counter_field for implementing reference counting *) + let fields = append_no_duplicates_fields [Sil.objc_ref_counter_field] fields in + Printing.log_out ~fmt:"Class %s field:\n" class_name; + list_iter (fun (fn, ft, _) -> + Printing.log_out ~fmt:"-----> field: '%s'\n" (Ident.fieldname_to_string fn)) fields; + let interface_type_info = + Sil.Tstruct(fields, [], Sil.Class, Some (Mangled.from_string class_name), + superclasses, methods, objc_class_annotation) in + Sil.tenv_add tenv interface_name interface_type_info; + Printing.log_out + ~fmt:" >>>Verifying that Typename '%s' is in tenv\n" (Sil.typename_to_string interface_name); + (match Sil.tenv_lookup tenv interface_name with + | Some t -> Printing.log_out ~fmt:" >>>OK. Found typ='%s'\n" (Sil.typ_to_string t) + | None -> Printing.log_out " >>>NOT Found!!\n"); + curr_class + +(* Add potential extra fields defined only in the implementation of the class *) +(* to the info given in the interface. Update the tenv accordingly.*) +let add_missing_fields tenv class_name decl_list idi = + let curr_class, superclasses, fields = + create_curr_class_and_superclasses_fields tenv decl_list class_name + idi.Clang_ast_t.oidi_super [] in + let mang_name = Mangled.from_string class_name in + let class_tn_name = Sil.TN_csu (Sil.Class, mang_name) in + Printing.log_out + ~fmt:" >>>Verifying that Typename TN_csu('%s') is in tenv\n" + (Sil.typename_to_string class_tn_name); + let curr_class = + (match Sil.tenv_lookup tenv class_tn_name with + | Some Sil.Tstruct(intf_fields, _, _, _, superclass, methods, annotation) -> + (let compute_extra_fields fields intf_fields = + let equal_fields (fn1, _, _) (fn2, _, _) = Ident.fieldname_equal fn1 fn2 in + let missing_field f = not (list_mem equal_fields f intf_fields) in + list_filter missing_field fields in + Printing.log_out + ~fmt:" Looking for extra fields defined only in the implementation of '%s'\n" + class_name; + let extra_fields = compute_extra_fields fields intf_fields in + list_iter (fun (fn, _, _) -> + Printing.log_out + ~fmt:" ---> Extra non-static field: '%s'\n" (Ident.fieldname_to_string fn)) + extra_fields; + let new_fields = append_no_duplicates_fields extra_fields intf_fields in + let class_type_info = + Sil.Tstruct ( + new_fields, [], Sil.Class, Some mang_name, superclass, methods, annotation + ) in + Printing.log_out ~fmt:" Updating info for class '%s' in tenv\n" class_name; + Sil.tenv_add tenv class_tn_name class_type_info; + update_curr_class curr_class superclass ) + | _ -> assert false) in + curr_class + +let add_missing_methods tenv class_name decl_list curr_class = + let methods = ObjcProperty_decl.get_methods curr_class decl_list in + let class_tn_name = Sil.TN_csu (Sil.Class, (Mangled.from_string class_name)) in + match Sil.tenv_lookup tenv class_tn_name with + | Some Sil.Tstruct(fields, [], Sil.Class, Some name, superclass, existing_methods, annotation) -> + let methods = General_utils.append_no_duplicates_methods existing_methods methods in + let typ = Sil.Tstruct(fields, [], Sil.Class, Some name, superclass, methods, annotation) in + Sil.tenv_add tenv class_tn_name typ + | _ -> () + +(* Interface_type_info has the name of instance variables and the name of methods. *) +let interface_declaration tenv class_name decl_list obj_c_interface_decl_info = + let curr_class = add_class_to_tenv tenv class_name decl_list obj_c_interface_decl_info in + curr_class + +(* Translate the methods defined in the implementation.*) +let interface_impl_declaration tenv class_name decl_list implementation_decl_info = + let curr_class = add_missing_fields tenv class_name decl_list implementation_decl_info in + add_missing_methods tenv class_name decl_list curr_class; + Printing.log_out ~fmt:"ADDING: ObjCImplementationDecl for class '%s'\n" class_name; + Printing.log_out " Processing method declarations...\n"; + curr_class + +(* search for definition of interface with non empty set of fields that may come after their use.*) +(* Typical example: *) +(* ...Partial definition of the interface I*) +(* :::: [later in the AST]*) +(* ...use of a field of I*) +(* ::: [later in the AST] *) +(* ...Full definition of the interface I *) +let lookup_late_defined_interface tenv cname = + let rec scan decls = + match decls with + | [] -> () + | ObjCInterfaceDecl(decl_info, name, decl_list, decl_context_info, obj_c_interface_decl_info) + :: decls' + when (Mangled.from_string name) = cname -> + scan decls' + | ObjCInterfaceDecl(decl_info, name, decl_list, decl_context_info, obj_c_interface_decl_info) + :: decls' + when (Mangled.from_string name) = cname -> + (* Assumption: here we assume that the first interface declaration with non empty set of fields is the *) + (* correct one. So we stop. *) + ignore (interface_declaration tenv name decl_list obj_c_interface_decl_info) + | _:: decls' -> scan decls' in + scan !CFrontend_config.global_translation_unit_decls + +(* Finds the field nfield in a Tstruc. If the Tstrct is a class and the field is not found *) +(* the search is extended in a recursive way to the hierarchy of superclasses. *) +let rec find_field tenv nfield str searched_late_defined = + (* let add_namespace_to_namefield cname = + match namespace with + | Some _ -> nfield + | None -> (Mangled.to_string cname)^"_"^nfield in *) + let print_error name_field fields = + Printing.log_err ~fmt:"\nFaild to find name field '%s'\n\n" (Ident.fieldname_to_string name_field) ; + Printing.log_err "In the following list of fields\n"; + list_iter (fun (fn, _, _) -> Printing.log_err ~fmt:"\nField name: '%s'\n\n" (Ident.fieldname_to_string fn)) fields; + Printing.print_failure_info "" in + let rec search_super s = + match s with + | [] -> None + | (Sil.Class, sname):: s' -> + L.err "@. ....Searching field in superclass (Class, '%s')@." (Mangled.to_string sname); + let str' = Sil.tenv_lookup tenv (Sil.TN_csu(Sil.Class, sname)) in + (match find_field tenv nfield str' searched_late_defined with + | Some field -> Some field + | None -> search_super s') + | (Sil.Protocol, sname):: s' -> + L.err "@. ... Searching field in protocol (Protocol, '%s')@." (Mangled.to_string sname); + search_super s' + | (Sil.Struct, sname):: s' -> + L.err "@. ... Searching field in struct (Struct, '%s')@." (Mangled.to_string sname); + None + | (Sil.Union, sname):: s' -> + L.err "@. ... Searching field in (Union, '%s')@." (Mangled.to_string sname); + None in + match str with + | Some Sil.Tstruct (sf, nsf, Sil.Struct, Some cname, _, _, _) + | Some Sil.Tstruct (sf, nsf, Sil.Union, Some cname, _, _, _) -> + (let name_field = Ident.create_fieldname (Mangled.from_string nfield) 0 in + try + Some (list_find (fun (fn, _, _) -> Sil.fld_equal fn name_field) (sf@nsf)) + with Not_found -> + print_error name_field (sf@nsf); None) + | Some Sil.Tstruct (sf, nsf, Sil.Class, Some cname, super, _, _) -> + (let name_field = CField_decl.mk_class_field_name (Mangled.to_string cname) nfield in + try + Some (list_find (fun (fn, _, _) -> Sil.fld_equal fn name_field) (sf@nsf)) + with Not_found -> + (* if we have already searched for late defined interfaces we check recursively *) + (* whether the field is defined in the hiearchy of superclasses.*) + (* If we don't find it we stop, giving error. *) + print_error name_field (sf@nsf); + if searched_late_defined then search_super super + else ( + Printing.log_err "@. Search late defined...@.@."; + (* if we don't find the field the first thing we do is scanning later definitions of interfaces. *) + lookup_late_defined_interface tenv cname; + let str' = Sil.tenv_lookup tenv (Sil.TN_csu(Sil.Class, cname)) in + find_field tenv nfield str' true)) + | _ -> None diff --git a/infer/src/clang/objcInterface_decl.mli b/infer/src/clang/objcInterface_decl.mli new file mode 100644 index 000000000..68b199a34 --- /dev/null +++ b/infer/src/clang/objcInterface_decl.mli @@ -0,0 +1,19 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** In this module an ObjC interface declaration is processed. The class *) +(** is saved in the tenv as a struct with the corresponding fields, potential superclass and *) +(** list of defined methods *) + +val interface_declaration : Sil.tenv -> string -> Clang_ast_t.decl list -> +Clang_ast_t.obj_c_interface_decl_info -> CContext.curr_class + +val find_field : Sil.tenv -> string -> Sil.typ option -> bool -> +(Ident.fieldname * Sil.typ * Sil.item_annotation) option + +val interface_impl_declaration : Sil.tenv -> string -> Clang_ast_t.decl list -> + Clang_ast_t.obj_c_implementation_decl_info -> CContext.curr_class + +val is_pointer_to_objc_class : Sil.tenv -> Sil.typ -> bool diff --git a/infer/src/clang/objcProperty_decl.ml b/infer/src/clang/objcProperty_decl.ml new file mode 100644 index 000000000..e59872e94 --- /dev/null +++ b/infer/src/clang/objcProperty_decl.ml @@ -0,0 +1,371 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Process properties by creating their getters and setters in the case that they need to be syntethized *) +(** or in the case of dynamic. *) +(* How it works: *) +(* - First, the property is defined in the interface. Then, we add the method declarations of the getter *) +(* and setter to the map property_table. *) +(* - Second, in the class implementation, if synthetize is available, create the getters and setters, *) +(* unless some of these methods has already been created before. *) + +open Utils +open CFrontend_utils +open CFrontend_config +open Clang_ast_t +module L = Logging + +open CContext + +type prop_getter_setter = string * (Clang_ast_t.decl * bool) option + +(** For each property, we save the getter and the setter method declarations (no implementation). *) +(** A property type is a tuple: *) +(** (qual_type, property attributes, decl_info, (getter_name, getter), (setter_name, setter), ivar name) *) +type property_type = Clang_ast_t.qual_type * Clang_ast_t.property_attribute list * + Clang_ast_t.decl_info * prop_getter_setter * prop_getter_setter * string option + +(** A table that record the property defined in the interface and its getter/setter. *) +(** This info used later on in the implementation if the getter/setter need to automatically *) +(** synthesized*) +module type PropertySig = +sig + + type t + + type property_key = (CContext.curr_class * string) + + val property_key_to_string : property_key -> string + + val reset_property_table: unit -> unit + + val find_property : CContext.curr_class -> string -> property_type + + val find_properties_class : CContext.curr_class -> (string * property_type) list + + val is_mem_property : property_key -> bool + + val replace_property : property_key -> property_type -> unit + + val add_property : property_key -> Clang_ast_t.qual_type -> + Clang_ast_t.property_attribute list -> Clang_ast_t.decl_info -> unit + + val print_property_table : unit -> unit + + val find_property_name_from_ivar : CContext.curr_class -> string -> string option +end +module Property: PropertySig = +struct + + type property_key = (CContext.curr_class * string) + + let property_key_to_string (curr_class, property_name) = + ((CContext.curr_class_to_string curr_class)^"-"^property_name) + + (** Hash table to implement error logs *) + module PropertyTableHash = Hashtbl.Make (struct + + type t = property_key + + let hash (curr_class, pname) = (CContext.curr_class_hash curr_class) + Hashtbl.hash pname + + let equal (curr_class1, property_name1) (curr_class2, property_name2) = + CContext.curr_class_equal curr_class1 curr_class2 && + (String.compare property_name1 property_name2 == 0) + + end) + + type t = property_type PropertyTableHash.t + + let property_table : t = PropertyTableHash.create 100 + + let reset_property_table () = + PropertyTableHash.reset property_table + + let rec find_property curr_class property_name = + try PropertyTableHash.find property_table (curr_class, property_name) + with Not_found -> + match curr_class with + | ContextCls (name, _, protocols) -> + let res_opt = list_fold_right + (fun protocol found_procname_opt -> + match found_procname_opt with + | Some found_procname -> Some found_procname + | None -> + Some (find_property (ContextProtocol protocol) property_name)) protocols None in + (match res_opt with + | Some res -> res + | None -> raise Not_found) + | _ -> raise Not_found + + let find_property_name_from_ivar curr_class ivar = + let res = ref None in + PropertyTableHash.iter (fun (cl, pname) (_, _, _, _, _, ivar') -> + match ivar' with + | Some s when (CContext.curr_class_equal curr_class cl) && s = ivar -> res:= Some pname + | _ -> ()) property_table; + !res + + let is_mem_property property = + PropertyTableHash.mem property_table property + + let replace_property property = + PropertyTableHash.replace property_table property + + let print_property_table () = + let print_item key (qt, attributes, decl_info, getter, setter, ivar) = + let getter_str = + match getter with + | getter_name, Some (ObjCMethodDecl(_, _, _), defined1) -> + getter_name + | _ -> "" in + let setter_str = match setter with + | setter_name, Some (ObjCMethodDecl(_, _, _), defined2) -> + setter_name + | _ -> "" in + Logging.out "Property item %s accessors %s and %s \n" + (property_key_to_string key) getter_str setter_str in + PropertyTableHash.iter print_item property_table + + let find_properties_class curr_class = + let find_properties (curr_class', property_name) property_type properties = + if (CContext.curr_class_equal curr_class' curr_class) then + (property_name, property_type):: properties + else properties in + PropertyTableHash.fold find_properties property_table [] + + let get_getter_name prop_name attributes = + match Ast_utils.getter_attribute_opt attributes with + | Some name -> name + | None -> prop_name + + let get_setter_name prop_name attributes = + match Ast_utils.setter_attribute_opt attributes with + | Some name -> name + | None -> "set"^(String.capitalize prop_name)^":" + + let add_property (curr_class, property_name) qt attributes decl_info = + let key = (curr_class, property_name) in + let getter_name = get_getter_name property_name attributes in + let setter_name = get_setter_name property_name attributes in + Printing.log_out ~fmt:" ...Using '%s' in property table\n" (property_key_to_string key); + PropertyTableHash.add property_table key + (qt, attributes, decl_info, (getter_name, None), (setter_name, None), None) +end + +let reset_property_table = Property.reset_property_table + +let print_property_table () = Property.print_property_table () + +let find_properties_class = Property.find_properties_class + +let get_ivarname_property pidi = + match pidi.Clang_ast_t.opidi_ivar_decl with + | Some dr -> (match dr.Clang_ast_t.dr_name with + | Some n -> n + | _ -> assert false) + | _ -> (* If ivar is not defined than we need to take the name of the property to define ivar*) + Ast_utils.property_name pidi + +let upgrade_property_accessor property_key property_type meth_decl new_defined is_getter = + let is_defined meth_decl = + match meth_decl with + | Some (method_decl, was_defined) -> new_defined || was_defined + | None -> new_defined in + match property_type with + | qt, attributes, decl_info, (gname, getter), (sname, setter), ivar -> + if is_getter then + let defined = is_defined getter in + Property.replace_property property_key + (qt, attributes, decl_info, (gname, Some (meth_decl, defined)), (sname, setter), ivar) + else let defined = is_defined setter in + Property.replace_property property_key + (qt, attributes, decl_info, (gname, getter), (sname, Some (meth_decl, defined)), ivar) + +let check_for_property curr_class method_name meth_decl body = + let defined = Option.is_some body in + let properties_class = find_properties_class curr_class in + let check_property_accessor curr_class method_name is_getter = + let method_is_getter (property_name, property_type) = + match property_type with (_, _, _, (getter_name, _), (setter_name, _), _) -> + let found = + if is_getter then (method_name = getter_name) + else (method_name = setter_name) in + if found then + (Printing.log_out ~fmt:" Found property '%s' defined in property table\n" + (Property.property_key_to_string (curr_class, property_name)); + upgrade_property_accessor + (curr_class, property_name) property_type meth_decl defined is_getter) in + list_iter method_is_getter properties_class in + check_property_accessor curr_class method_name true; + check_property_accessor curr_class method_name false + +let prepare_dynamic_property curr_class decl_info property_impl_decl_info = + let pname = Ast_utils.property_name property_impl_decl_info in + let qt = (try + let qt', atts, di, getter, setter, _ = Property.find_property curr_class pname in + let ivar = (match property_impl_decl_info.Clang_ast_t.opidi_ivar_decl with + | Some dr -> dr.Clang_ast_t.dr_name + | None -> None) in + (* update property info with proper ivar name *) + Property.replace_property (curr_class, pname) (qt', atts, di, getter, setter, ivar); + Printing.log_out ~fmt: "Updated property table by adding ivar name for property pname '%s'\n" pname; + Some qt' + with Not_found -> L.err "Property '%s' not found in the table. Ivar not updated and qual_type not found.@." pname; + None) in + match property_impl_decl_info.Clang_ast_t.opidi_implementation with + | `Dynamic -> + (* For Dynamic property we need to create the ObjCIvarDecl which specifies*) + (* the field of the property. In case of Dynamic this is not in the AST.*) + (* Once created the ObjCIvarDecl then we treat the property as synthesized *) + [Ast_expressions.make_objc_ivar_decl decl_info qt property_impl_decl_info] + | `Synthesize -> + (* No names of fields/method to collect from ObjCPropertyImplDecl when Synthesized *) + [] + +(*NOTE: Assumption: if there is a getter or a setter defined manually, *) +(* it has been translated already at this point. *) +let method_exists cfg class_name name attributes = + let procname = CMethod_trans.mk_procname_from_method class_name name in + match Cfg.Procdesc.find_from_name cfg procname with + | Some procdesc -> + Cfg.Procdesc.is_defined procdesc + | None -> false + +let is_property_read_only attributes = + list_mem (Ast_utils.property_attribute_eq) `Readonly attributes + +let should_generate_getter cfg class_name name attributes = + not (method_exists cfg class_name name attributes) + +let should_generate_setter cfg class_name name attributes = + let setter_exists = not (method_exists cfg class_name name attributes) in + let is_read_only = is_property_read_only attributes in + setter_exists && not is_read_only + +let get_memory_management_attribute attributes = + let memory_management_attributes = Ast_utils.get_memory_management_attributes () in + try Some (list_find ( + fun att -> list_mem (Ast_utils.property_attribute_eq) + att memory_management_attributes) attributes) + with Not_found -> None + +(*Makes the getters and setters according to the attributes: *) +(* - If readonly is available, only write getter *) +(* - If strong or retain are available then write the following code in the setter: *) +(* [param retain] *) +(* [self->_field release] *) +(* [self->_field = param] *) +(* - If copy is available then write the following code: *) +(* [self->_field = [param copy] *) +let make_getter_setter cfg curr_class decl_info property_impl_decl_info = + let class_name = CContext.get_curr_class_name curr_class in + let prop_name = Ast_utils.property_name property_impl_decl_info in + Printing.log_out ~fmt:"ADDING: ObjCPropertyImplDecl for property '%s' " prop_name; + Printing.log_out ~fmt:"pointer = '%s'\n" decl_info.Clang_ast_t.di_pointer; + let qt, attributes, decl_info, (getter_name, getter), (setter_name, setter), _ = (try + Property.find_property curr_class prop_name + with _ -> + Printing.log_out ~fmt:"Property %s not found@." prop_name; + assert false) in + let ivar_name = get_ivarname_property property_impl_decl_info in + let make_getter () = + ( + match getter with + | Some (ObjCMethodDecl(di, name, mdi), _) -> + let dummy_info = Ast_expressions.dummy_decl_info di in + if should_generate_getter cfg class_name name attributes then + let deref_self_field = Ast_expressions.make_deref_self_field + (CContext.get_qt_curr_class curr_class) dummy_info mdi.Clang_ast_t.omdi_result_type ivar_name in + let body = ReturnStmt(Ast_expressions.make_stmt_info dummy_info, [deref_self_field]) in + let mdi'= Ast_expressions.make_method_decl_info mdi body in + Property.replace_property + (curr_class, prop_name) + (qt, attributes, decl_info, + (getter_name, Some (ObjCMethodDecl(di, name, mdi), true)), (setter_name, setter), Some ivar_name); + [ObjCMethodDecl(dummy_info, name, mdi')] + else [] + | _ -> []) in + let make_setter () = + match setter with + | Some (ObjCMethodDecl(di, name, mdi), _) -> + if should_generate_setter cfg class_name name attributes then + let dummy_info = Ast_expressions.dummy_decl_info di in + let param_name, qt_param = (match mdi.Clang_ast_t.omdi_parameters with + | [ParmVarDecl(_, name, qt_param, _)] -> name, qt_param + | _ -> assert false) in + let is_hidden = (match property_impl_decl_info.Clang_ast_t.opidi_ivar_decl with + | Some dr -> dr.Clang_ast_t.dr_is_hidden + | _ -> false) in + let drti_decl_ref' = + Ast_expressions.make_general_decl_ref (`ParmVar) param_name is_hidden qt_param in + let decl_ref_expr_info' = Ast_expressions.make_decl_ref_expr_info drti_decl_ref' in + let expr_info = Ast_expressions.make_expr_info qt_param in + let stmt_info = Ast_expressions.make_stmt_info dummy_info in + let rhs_exp = Ast_expressions.make_cast_expr qt_param di decl_ref_expr_info' `ObjCProperty in + let lhs_exp = Ast_expressions.make_self_field + (CContext.get_qt_curr_class curr_class) di qt_param ivar_name in + let boi = { Clang_ast_t.boi_kind = `Assign } in + let setter = Ast_expressions.make_binary_stmt lhs_exp rhs_exp stmt_info expr_info boi in + let memory_management_attribute = (get_memory_management_attribute attributes) in + let code = + if Ast_utils.is_retain memory_management_attribute then + let param_decl = + Ast_expressions.make_decl_ref_exp_var (param_name, qt_param) `ParmVar stmt_info in + let retain_call = + Ast_expressions.make_message_expr qt_param retain param_decl stmt_info in + let release_call = + Ast_expressions.make_message_expr qt_param release lhs_exp stmt_info in + [retain_call; release_call; setter] + else if Ast_utils.is_copy memory_management_attribute then + let param_decl = + Ast_expressions.make_decl_ref_exp_var (param_name, qt_param) `ParmVar stmt_info in + let copy_call = + Ast_expressions.make_message_expr qt_param copy param_decl stmt_info in + let setter = + Ast_expressions.make_binary_stmt lhs_exp copy_call stmt_info expr_info boi in + [setter] + else [setter] in + let body = Ast_expressions.make_compound_stmt code stmt_info in + let mdi'= Ast_expressions.make_method_decl_info mdi body in + Property.replace_property + (curr_class, prop_name) + (qt, attributes, decl_info, + (getter_name, getter), + (setter_name, Some (ObjCMethodDecl(di, name, mdi), true)), Some ivar_name); + [ObjCMethodDecl(dummy_info, name, mdi')] + else [] + | _ -> [] in + match property_impl_decl_info.Clang_ast_t.opidi_implementation with + | `Dynamic + (* For the moment we treat Dynamic properties as Synthesize. This imply that setter/getter very simply, getting and setting *) + (* the value. Therefore we are assuming that the dynamic setter/getter won't have any other side effect.*) + | `Synthesize -> + (make_getter ()) @ (make_setter ()) + +(* Given a list of declarations in an interface returns a triple *) +(* (list of non static fields, list of static fields, list of methods)*) +(* TODO: this part is a bit redundant: we could add the methods to the tenv *) +(* at the same time that we add them to the cfg *) +let rec get_methods curr_class decl_list = + let class_name = CContext.get_curr_class_name curr_class in + match decl_list with + | [] -> [] + | (ObjCMethodDecl(decl_info, method_name, method_decl_info) as d):: decl_list' -> + Printing.log_out ~fmt:" ...Adding Method '%s' \n" (class_name^"_"^method_name); + let methods = get_methods curr_class decl_list' in + let _ = check_for_property curr_class method_name d method_decl_info.Clang_ast_t.omdi_body in + let meth_name = CMethod_trans.mk_procname_from_method class_name method_name in + meth_name:: methods + + | ObjCPropertyDecl(decl_info, pname, pdi):: decl_list' -> + (* Property declaration register the property on the property table to be *) + (* used later on in case getter and setters need to be synthesized by ObjCPropertyImplDecl *) + Printing.log_out ~fmt:" ...Adding Property Declaration '%s' " pname; + Printing.log_out ~fmt:" pointer= '%s' \n" decl_info.Clang_ast_t.di_pointer; + Property.add_property (curr_class, pname) pdi.opdi_qual_type pdi.opdi_property_attributes decl_info; + get_methods curr_class decl_list' (* TODO maybe add prop_name here *) + + | (d : Clang_ast_t.decl):: decl_list' -> get_methods curr_class decl_list' diff --git a/infer/src/clang/objcProperty_decl.mli b/infer/src/clang/objcProperty_decl.mli new file mode 100644 index 000000000..081e6a894 --- /dev/null +++ b/infer/src/clang/objcProperty_decl.mli @@ -0,0 +1,61 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** Process properties by creating their getters and setters in the case that they need to be syntethized *) +(** or in the case of dynamic. *) + +type prop_getter_setter = string * (Clang_ast_t.decl * bool) option + +(** For each property, we save the getter and the setter method declarations (no implementation). *) +(** A property type is a tuple: *) +(** (qual_type, property attributes, decl_info, (getter_name, getter), (setter_name, setter), ivar name ) *) +type property_type = Clang_ast_t.qual_type * Clang_ast_t.property_attribute list * + Clang_ast_t.decl_info * prop_getter_setter * prop_getter_setter * string option + +val prepare_dynamic_property : CContext.curr_class -> Clang_ast_t.decl_info -> +Clang_ast_t.obj_c_property_impl_decl_info -> Clang_ast_t.decl list + +val get_methods : CContext.curr_class -> Clang_ast_t.decl list -> Procname.t list + +val make_getter_setter : Cfg.cfg -> CContext.curr_class -> Clang_ast_t.decl_info -> Clang_ast_t.obj_c_property_impl_decl_info -> +Clang_ast_t.decl list + +val reset_property_table : unit -> unit + +val print_property_table : unit -> unit + +val is_property_read_only : Clang_ast_t.property_attribute list -> bool + +val find_properties_class : CContext.curr_class -> (string * property_type) list + + +module type PropertySig = +sig + + type t + + type property_key = (CContext.curr_class * string) + + val property_key_to_string : property_key -> string + + val reset_property_table: unit -> unit + + val find_property : CContext.curr_class -> string -> property_type + + val find_properties_class : CContext.curr_class -> (string * property_type) list + + val is_mem_property : property_key -> bool + + val replace_property : property_key -> property_type -> unit + + val add_property : property_key -> Clang_ast_t.qual_type -> + Clang_ast_t.property_attribute list -> Clang_ast_t.decl_info -> unit + + val print_property_table : unit -> unit + + val find_property_name_from_ivar : CContext.curr_class -> string -> string option +end + +module Property: PropertySig diff --git a/infer/src/clang/objcProtocol_decl.ml b/infer/src/clang/objcProtocol_decl.ml new file mode 100644 index 000000000..16e7c5e1f --- /dev/null +++ b/infer/src/clang/objcProtocol_decl.ml @@ -0,0 +1,23 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +open Utils +open CFrontend_utils + +module L = Logging + +let protocol_decl tenv name decl_list = + (* Adds pairs (protocol name, protocol_type_info) to the global environment. *) + (* Protocol_type_info contains the methods composing the protocol. *) + (* Here we are giving a similar treatment as interfaces (see above)*) + (* It may turn out that we need a more specific treatment for protocols*) + Printing.log_out ~fmt:"ADDING: ObjCProtocolDecl for '%s'\n" name; + let mang_name = Mangled.from_string name in + let curr_class = CContext.ContextProtocol name in + let protocol_name = Sil.TN_csu(Sil.Protocol, mang_name) in + let methods = ObjcProperty_decl.get_methods curr_class decl_list in + let protocol_type_info = Sil.Tstruct([], [], Sil.Protocol, Some mang_name, [], methods, []) in + Sil.tenv_add tenv protocol_name protocol_type_info; + curr_class diff --git a/infer/src/clang/objcProtocol_decl.mli b/infer/src/clang/objcProtocol_decl.mli new file mode 100644 index 000000000..27bfe4176 --- /dev/null +++ b/infer/src/clang/objcProtocol_decl.mli @@ -0,0 +1,9 @@ +(* +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +(** In this module an ObjC protocol declaration or implementation is processed. The protocol *) +(** is saved in the tenv as a struct with the corresponding methods *) + +val protocol_decl : Sil.tenv -> string -> Clang_ast_t.decl list -> CContext.curr_class diff --git a/infer/src/clang/plugin/CMakeLists.txt b/infer/src/clang/plugin/CMakeLists.txt new file mode 100644 index 000000000..93d32ac57 --- /dev/null +++ b/infer/src/clang/plugin/CMakeLists.txt @@ -0,0 +1,26 @@ +# Copyright 2004-present Facebook. All Rights Reserved. + +set(MODULE TRUE) + +set(LLVM_LINK_COMPONENTS support mc) + +add_clang_library(infer-plugin InferPlugin.cpp) + +add_dependencies(infer-plugin + ClangAttrClasses + ClangAttrList + ClangCommentNodes + ClangDeclNodes + ClangDiagnosticCommon + ClangStmtNodes + ) + +target_link_libraries(infer-plugin + clangFrontend + clangAST + ) + +set_target_properties(infer-plugin + PROPERTIES + LINKER_LANGUAGE CXX + PREFIX "") diff --git a/infer/src/clang/plugin/InferPlugin.cpp b/infer/src/clang/plugin/InferPlugin.cpp new file mode 100644 index 000000000..8cfbc3c78 --- /dev/null +++ b/infer/src/clang/plugin/InferPlugin.cpp @@ -0,0 +1,60 @@ +/* + * Copyright 2004-present Facebook. All Rights Reserved. + */ + +#include "clang/Frontend/FrontendPluginRegistry.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/AST.h" +#include "clang/Frontend/CompilerInstance.h" +#include "llvm/Support/raw_ostream.h" +using namespace clang; + +namespace { + + class PrintFunctionsConsumer : public ASTConsumer { + public: + virtual bool HandleTopLevelDecl(DeclGroupRef DG) { + for (DeclGroupRef::iterator i = DG.begin(), e = DG.end(); i != e; ++i) { + const Decl *D = *i; + if (const NamedDecl *ND = dyn_cast(D)) + llvm::errs() << "top-level-decl: \"" << ND->getNameAsString() << "\"\n"; + } + + return true; + } + }; + + class PrintFunctionNamesAction : public PluginASTAction { + protected: + ASTConsumer *CreateASTConsumer(CompilerInstance &CI, llvm::StringRef) { + return new PrintFunctionsConsumer(); + } + + bool ParseArgs(const CompilerInstance &CI, + const std::vector& args) { + for (unsigned i = 0, e = args.size(); i != e; ++i) { + llvm::errs() << "toplevel-plugin arg = " << args[i] << "\n"; + + // Example error handling. + if (args[i] == "-an-error") { + DiagnosticsEngine &D = CI.getDiagnostics(); + unsigned DiagID = D.getCustomDiagID( + DiagnosticsEngine::Error, "invalid argument '" + args[i] + "'"); + D.Report(DiagID); + return false; + } + } + if (args.size() && args[0] == "help") + PrintHelp(llvm::errs()); + + return true; + } + void PrintHelp(llvm::raw_ostream& ros) { + ros << "Help for toplevel-plugin plugin goes here\n"; + } + + }; + +} + +static FrontendPluginRegistry::Add X("infer-plugin", "print function names"); diff --git a/infer/src/clang/plugin/test/Makefile b/infer/src/clang/plugin/test/Makefile new file mode 100644 index 000000000..e80c15f2e --- /dev/null +++ b/infer/src/clang/plugin/test/Makefile @@ -0,0 +1,11 @@ +# Copyright 2004-present Facebook. All Rights Reserved. +BD=~/clang-llvm/build + +all: + /usr/local/bin/clang++ -DGNU_SOURCE -D_DEBUG -D_STDC_CONSTANT_MACROS \ + -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS \ + -I$(BD)/tools/clang/include -I$(BD)/lib/clang/3.4/include -I$(BD)/include \ + -Xclang -load -Xclang $(BD)/lib/infer-plugin.so \ + -Xclang -plugin -Xclang infer-plugin \ + test.cpp -fsyntax-only + diff --git a/infer/src/clang/plugin/test/test.cpp b/infer/src/clang/plugin/test/test.cpp new file mode 100644 index 000000000..bfc711587 --- /dev/null +++ b/infer/src/clang/plugin/test/test.cpp @@ -0,0 +1,17 @@ +/* + * Copyright 2004-present Facebook. All Rights Reserved. + */ + +int main() { return 0; } + +void f() { + for (int i = 0; i < 10; i++) { + ; + } +} + +void g() { + for (int i = 1; i < 10; i++) { + ; + } +} diff --git a/infer/src/harness/androidFramework.ml b/infer/src/harness/androidFramework.ml new file mode 100644 index 000000000..b9ce47c88 --- /dev/null +++ b/infer/src/harness/androidFramework.ml @@ -0,0 +1,371 @@ +(* +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +module L = Logging +module F = Format +module TypSet = Sil.TypSet + +open Utils + +(** Android lifecycle types and their lifecycle methods that are called by the framework *) + +(** work-in-progress list of known callback-registering method names *) +let callback_register_methods = + let method_list = ["addCallback"; "register"; "setOnClickListener"] in + list_fold_left (fun set str -> StringSet.add str set) StringSet.empty method_list + +let is_known_callback_register_method proc_str = StringSet.mem proc_str callback_register_methods + +let on_destroy = "onDestroy" +let on_destroy_view = "onDestroyView" + +(** return true if [procname] is a special lifecycle cleanup method *) +let is_destroy_method procname = + if Procname.is_java procname then + let method_name = Procname.java_get_method procname in + string_equal method_name on_destroy || string_equal method_name on_destroy_view + else false + +let android_lifecycles = + let android_content = "android.content" in + let android_app = "android.app" in + let fragment_lifecycle = + ["onInflate"; "onAttach"; "onCreate"; "onCreateView"; "onViewCreated"; "onActivityCreated"; + "onViewStateRestored"; "onStart"; "onResume"; "onPause"; "onSaveInstanceState"; "onStop"; + on_destroy_view; on_destroy; "onDetach"] in + [ (android_content, + "ContentProvider", + ["onCreate"]); + (android_app, + "Activity", + ["onCreate"; "onStart"; "onRestoreInstanceState"; "onPostCreate"; "onResume"; "onPostResume"; + "onCreateDescription"; "onSaveInstanceState"; "onPause"; "onStop"; on_destroy]); + (android_app, + "Service", + ["onCreate"; "onStart"; "onStartCommand"; "onBind"; "onUnbind"; on_destroy]); + (android_content, + "BroadcastReceiever", + ["onReceive"]); + (android_app, + "Fragment", + fragment_lifecycle); + (* this is the pre-Android 3.0 Fragment type (can also be used post-3.0) *) + ("android.support.v4.app", + "Fragment", + fragment_lifecycle); + ] + +let android_callbacks = + let cb_strs = [ + ("android.accounts", "OnAccountsUpdateListener"); + ("android.animation", "Animator$AnimatorListener"); + ("android.animation", "LayoutTransition$TransitionListener"); + ("android.animation", "TimeAnimator$TimeListener"); + ("android.animation", "ValueAnimator$AnimatorUpdateListener"); + ("android.app", "ActionBar$OnMenuVisibilityListener"); + ("android.app", "ActionBar$OnNavigationListener"); + ("android.app", "ActionBar$TabListener"); + ("android.app", "Application$ActivityLifecycleCallbacks"); + ("android.app", "DatePickerDialog$OnDateSetListener"); + ("android.app", "FragmentBreadCrumbs$OnBreadCrumbClickListener"); + ("android.app", "FragmentManager$OnBackStackChangedListener"); + ("android.app", "KeyguardManager$OnKeyguardExitResult"); + ("android.app", "LoaderManager$LoaderCallbacks"); + ("android.app", "PendingIntent$OnFinished"); + ("android.app", "SearchManager$OnCancelListener"); + ("android.app", "SearchManager$OnDismissListener"); + ("android.app", "TimePickerDialog$OnTimeSetListener"); + ("android.bluetooth", "BluetoothProfile$ServiceListener"); + ("android.content", "ClipboardManager$OnPrimaryClipChangedListener"); + ("android.content", "ComponentCallbacks"); + ("android.content", "ComponentCallbacks2"); + ("android.content", "DialogInterface$OnCancelListener"); + ("android.content", "DialogInterface$OnClickListener"); + ("android.content", "DialogInterface$OnDismissListener"); + ("android.content", "DialogInterface$OnKeyListener"); + ("android.content", "DialogInterface$OnMultiChoiceClickListener"); + ("android.content", "DialogInterface$OnShowListener"); + ("android.content", "IntentSender$OnFinished"); + ("android.content", "Loader$OnLoadCanceledListener"); + ("android.content", "Loader$OnLoadCompleteListener"); + ("android.content", "SharedPreferences$OnSharedPreferenceChangeListener"); + ("android.content", "SyncStatusObserver"); + ("android.database.sqlite", "SQLiteTransactionListener"); + ("android.drm", "DrmManagerClient$OnErrorListener"); + ("android.drm", "DrmManagerClient$OnEventListener"); + ("android.drm", "DrmManagerClient$OnInfoListener"); + ("android.gesture", "GestureOverlayView$OnGestureListener"); + ("android.gesture", "GestureOverlayView$OnGesturePerformedListener"); + ("android.gesture", "GestureOverlayView$OnGesturingListener"); + ("android.graphics", "SurfaceTexture$OnFrameAvailableListener"); + ("android.hardware", "Camera$AutoFocusCallback"); + ("android.hardware", "Camera$AutoFocusMoveCallback"); + ("android.hardware", "Camera$ErrorCallback"); + ("android.hardware", "Camera$FaceDetectionListener"); + ("android.hardware", "Camera$OnZoomChangeListener"); + ("android.hardware", "Camera$PictureCallback"); + ("android.hardware", "Camera$PreviewCallback"); + ("android.hardware", "Camera$ShutterCallback"); + ("android.hardware", "SensorEventListener"); + ("android.hardware.display", "DisplayManager$DisplayListener"); + ("android.hardware.input", "InputManager$InputDeviceListener"); + ("android.inputmethodservice", "KeyboardView$OnKeyboardActionListener"); + ("android.location", "GpsStatus$Listener"); + ("android.location", "GpsStatus$NmeaListener"); + ("android.location", "LocationListener"); + ("android.media", "AudioManager$OnAudioFocusChangeListener"); + ("android.media", "AudioRecord$OnRecordPositionUpdateListener"); + ("android.media", "JetPlayer$OnJetEventListener"); + ("android.media", "MediaPlayer$OnBufferingUpdateListener"); + ("android.media", "MediaPlayer$OnCompletionListener"); + ("android.media", "MediaPlayer$OnErrorListener"); + ("android.media", "MediaPlayer$OnInfoListener"); + ("android.media", "MediaPlayer$OnPreparedListener"); + ("android.media", "MediaPlayer$OnSeekCompleteListener"); + ("android.media", "MediaPlayer$OnTimedTextListener"); + ("android.media", "MediaPlayer$OnVideoSizeChangedListener"); + ("android.media", "MediaRecorder$OnErrorListener"); + ("android.media", "MediaRecorder$OnInfoListener"); + ("android.media", "MediaScannerConnection$MediaScannerConnectionClient"); + ("android.media", "MediaScannerConnection$OnScanCompletedListener"); + ("android.media", "SoundPool$OnLoadCompleteListener"); + ("android.media.audiofx", "AudioEffect$OnControlStatusChangeListener"); + ("android.media.audiofx", "AudioEffect$OnEnableStatusChangeListener"); + ("android.media.audiofx", "BassBoost$OnParameterChangeListener"); + ("android.media.audiofx", "EnvironmentalReverb$OnParameterChangeListener"); + ("android.media.audiofx", "Equalizer$OnParameterChangeListener"); + ("android.media.audiofx", "PresetReverb$OnParameterChangeListener"); + ("android.media.audiofx", "Virtualizer$OnParameterChangeListener"); + ("android.media.audiofx", "Visualizer$OnDataCaptureListener"); + ("android.media.effect", "EffectUpdateListener"); + ("android.net.nsd", "NsdManager$DiscoveryListener"); + ("android.net.nsd", "NsdManager$RegistrationListener"); + ("android.net.nsd", "NsdManager$ResolveListener"); + ("android.net.sip", "SipRegistrationListener"); + ("android.net.wifi.p2p", "WifiP2pManager$ActionListener"); + ("android.net.wifi.p2p", "WifiP2pManager$ChannelListener"); + ("android.net.wifi.p2p", "WifiP2pManager$ConnectionInfoListener"); + ("android.net.wifi.p2p", "WifiP2pManager$DnsSdServiceResponseListener"); + ("android.net.wifi.p2p", "WifiP2pManager$DnsSdTxtRecordListener"); + ("android.net.wifi.p2p", "WifiP2pManager$GroupInfoListener"); + ("android.net.wifi.p2p", "WifiP2pManager$PeerListListener"); + ("android.net.wifi.p2p", "WifiP2pManager$ServiceResponseListener"); + ("android.net.wifi.p2p", "WifiP2pManager$UpnpServiceResponseListener"); + ("android.os", "CancellationSignal$OnCancelListener"); + ("android.os", "IBinder$DeathRecipient"); + ("android.os", "MessageQueue$IdleHandler"); + ("android.os", "RecoverySystem$ProgressListener"); + ("android.preference", "Preference$OnPreferenceChangeListener"); + ("android.preference", "Preference$OnPreferenceClickListener"); + ("android.preference", "PreferenceFragment$OnPreferenceStartFragmentCallback"); + ("android.preference", "PreferenceManager$OnActivityDestroyListener"); + ("android.preference", "PreferenceManager$OnActivityResultListener"); + ("android.preference", "PreferenceManager$OnActivityStopListener"); + ("android.security", "KeyChainAliasCallback"); + ("android.speech", "RecognitionListener"); + ("android.speech.tts", "TextToSpeech$OnInitListener"); + ("android.speech.tts", "TextToSpeech$OnUtteranceCompletedListener"); + ("android.view", "ActionMode$Callback"); + ("android.view", "ActionProvider$VisibilityListener"); + ("android.view", "GestureDetector$OnDoubleTapListener"); + ("android.view", "GestureDetector$OnGestureListener"); + ("android.view", "InputQueue$Callback"); + ("android.view", "KeyEvent$Callback"); + ("android.view", "MenuItem$OnActionExpandListener"); + ("android.view", "MenuItem$OnMenuItemClickListener"); + ("android.view", "ScaleGestureDetector$OnScaleGestureListener"); + ("android.view", "SurfaceHolder$Callback"); + ("android.view", "SurfaceHolder$Callback2"); + ("android.view", "TextureView$SurfaceTextureListener"); + ("android.view", "View$OnAttachStateChangeListener"); + ("android.view", "View$OnClickListener"); + ("android.view", "View$OnCreateContextMenuListener"); + ("android.view", "View$OnDragListener"); + ("android.view", "View$OnFocusChangeListener"); + ("android.view", "View$OnGenericMotionListener"); + ("android.view", "View$OnHoverListener"); + ("android.view", "View$OnKeyListener"); + ("android.view", "View$OnLayoutChangeListener"); + ("android.view", "View$OnLongClickListener"); + ("android.view", "View$OnSystemUiVisibilityChangeListener"); + ("android.view", "View$OnTouchListener"); + ("android.view", "ViewGroup$OnHierarchyChangeListener"); + ("android.view", "ViewStub$OnInflateListener"); + ("android.view", "ViewTreeObserver$OnDrawListener"); + ("android.view", "ViewTreeObserver$OnGlobalFocusChangeListener"); + ("android.view", "ViewTreeObserver$OnGlobalLayoutListener"); + ("android.view", "ViewTreeObserver$OnPreDrawListener"); + ("android.view", "ViewTreeObserver$OnScrollChangedListener"); + ("android.view", "ViewTreeObserver$OnTouchModeChangeListener"); + ("android.view.accessibility", "AccessibilityManager$AccessibilityStateChangeListener"); + ("android.view.animation", "Animation$AnimationListener"); + ("android.view.inputmethod", "InputMethod$SessionCallback"); + ("android.view.inputmethod", "InputMethodSession$EventCallback"); + ("android.view.textservice", "SpellCheckerSession$SpellCheckerSessionListener"); + ("android.webkit", "DownloadListener"); + ("android.widget", "AbsListView$MultiChoiceModeListener"); + ("android.widget", "AbsListView$OnScrollListener"); + ("android.widget", "AbsListView$RecyclerListener"); + ("android.widget", "AdapterView$OnItemClickListener"); + ("android.widget", "AdapterView$OnItemLongClickListener"); + ("android.widget", "AdapterView$OnItemSelectedListener"); + ("android.widget", "AutoCompleteTextView$OnDismissListener"); + ("android.widget", "CalendarView$OnDateChangeListener"); + ("android.widget", "Chronometer$OnChronometerTickListener"); + ("android.widget", "CompoundButton$OnCheckedChangeListener"); + ("android.widget", "DatePicker$OnDateChangedListener"); + ("android.widget", "ExpandableListView$OnChildClickListener"); + ("android.widget", "ExpandableListView$OnGroupClickListener"); + ("android.widget", "ExpandableListView$OnGroupCollapseListener"); + ("android.widget", "ExpandableListView$OnGroupExpandListener"); + ("android.widget", "Filter$FilterListener"); + ("android.widget", "NumberPicker$OnScrollListener"); + ("android.widget", "NumberPicker$OnValueChangeListener"); + ("android.widget", "NumberPicker$OnDismissListener"); + ("android.widget", "PopupMenu$OnMenuItemClickListener"); + ("android.widget", "PopupWindow$OnDismissListener"); + ("android.widget", "RadioGroup$OnCheckedChangeListener"); + ("android.widget", "RatingBar$OnRatingBarChangeListener"); + ("android.widget", "SearchView$OnCloseListener"); + ("android.widget", "SearchView$OnQueryTextListener"); + ("android.widget", "SearchView$OnSuggestionListener"); + ("android.widget", "SeekBar$OnSeekBarChangeListener"); + ("android.widget", "ShareActionProvider$OnShareTargetSelectedListener"); + ("android.widget", "SlidingDrawer$OnDrawerCloseListener"); + ("android.widget", "SlidingDrawer$OnDrawerOpenListener"); + ("android.widget", "SlidingDrawer$OnDrawerScrollListener"); + ("android.widget", "TabHost$OnTabChangeListener"); + ("android.widget", "TextView$OnEditorActionListener"); + ("android.widget", "TimePicker$OnTimeChangedListener"); + ("android.widget", "ZoomButtonsController$OnZoomListener"); + ] in + list_fold_left (fun cbSet (pkg, clazz) -> + let qualified_name = Mangled.from_string (pkg ^ "." ^ clazz) in + Mangled.MangledSet.add qualified_name cbSet) Mangled.MangledSet.empty cb_strs + +(** return the complete set of superclasses of [typ *) +(* TODO (t4644852): factor out subtyping functions into some sort of JavaUtil module *) +let get_all_supertypes typ tenv = + let get_direct_supers = function + | Sil.Tstruct (_, _, Sil.Class, _, supers, _, _) -> supers + | _ -> [] in + let rec add_typ name typs = match Sil.get_typ name None tenv with + | Some typ -> get_supers_rec typ tenv (TypSet.add typ typs) + | None -> typs + and get_supers_rec typ tenv all_supers = + let direct_supers = get_direct_supers typ in + list_fold_left (fun typs (_, name) -> add_typ name typs) all_supers direct_supers in + get_supers_rec typ tenv TypSet.empty + +(** return true if [typ0] <: [typ1] *) +let is_subtype (typ0 : Sil.typ) (typ1 : Sil.typ) tenv = + TypSet.mem typ1 (get_all_supertypes typ0 tenv) + +(** return true if [class_name] is a known callback class name *) +let is_callback_class_name class_name = Mangled.MangledSet.mem class_name android_callbacks + +(** return true if [typ] is a known callback class *) +let is_callback_class typ tenv = + let supertyps = get_all_supertypes typ tenv in + TypSet.exists (fun typ -> match typ with + | Sil.Tstruct (_, _, Sil.Class, Some classname, _, _, _) -> + is_callback_class_name classname + | _ -> false) supertyps + +(** return true if [typ] is a subclass of [lifecycle_typ] *) +let typ_is_lifecycle_typ typ lifecycle_typ tenv = + let supers = get_all_supertypes typ tenv in + TypSet.mem lifecycle_typ supers + +(** return true if [class_name] is the name of a class that belong to the Android framework *) +let is_android_lib_class class_name = + let class_str = Mangled.to_string class_name in + string_is_prefix "android" class_str || string_is_prefix "com.android" class_str + +(** returns an option containing the var name and type of a callback registered by [procname], None +if no callback is registered *) +let get_callback_registered_by procname args tenv = + (* TODO (t4565077): this check should be replaced with a membership check in a hardcoded list of + * Android callback registration methods *) + (* for now, we assume a method is a callback registration method if it is a setter and has a + * callback class as a non - receiver argument *) + let is_callback_register_like = + let has_non_this_callback_arg args = list_length args > 1 in + let has_registery_name procname = + Procname.is_java procname && (PatternMatch.is_setter procname || + is_known_callback_register_method (Procname.java_get_method procname)) in + has_registery_name procname && has_non_this_callback_arg args in + let is_ptr_to_callback_class typ tenv = match typ with + | Sil.Tptr (typ, Sil.Pk_pointer) -> is_callback_class typ tenv + | _ -> false in + if is_callback_register_like then + (* we don't want to check if the receiver is a callback class; it's one of the method arguments + * that's being registered as a callback *) + let get_non_this_args args = list_tl args in + try + Some (list_find (fun (_, typ) -> is_ptr_to_callback_class typ tenv) (get_non_this_args args)) + with Not_found -> None + else None + +(** return a list of typ's corresponding to callback classes registered by [procdesc] *) +let get_callbacks_registered_by_proc procdesc tenv = + let collect_callback_typs callback_typs node instr = match instr with + | Sil.Call([], Sil.Const (Sil.Cfun callee), args, loc, _) -> + begin + match get_callback_registered_by callee args tenv with + | Some (_, callback_typ) -> callback_typ :: callback_typs + | None -> callback_typs + end + | _ -> callback_typs in + Cfg.Procdesc.fold_instrs collect_callback_typs [] procdesc + +(** returns true if [procname] is a method that registers a callback *) +let is_callback_register_method procname args tenv = + match get_callback_registered_by procname args tenv with + | Some _ -> true + | None -> false + +(** given an Android framework type mangled string [lifecycle_typ] (e.g., android.app.Activity) and +a list of method names [lifecycle_procs_strs], get the appropriate typ and procnames *) +let get_lifecycle_for_framework_typ_opt lifecycle_typ lifecycle_proc_strs tenv = + match Sil.get_typ lifecycle_typ None tenv with + | Some (Sil.Tstruct(_, _, Sil.Class, Some class_name, _, decl_procs, _) as lifecycle_typ) -> + (* TODO (t4645631): collect the procedures for which is_java is returning false *) + let lookup_proc lifecycle_proc = + list_find (fun decl_proc -> + Procname.is_java decl_proc && lifecycle_proc = Procname.java_get_method decl_proc + ) decl_procs in + (* convert each of the framework lifecycle proc strings to a lifecycle method procname *) + let lifecycle_procs = + list_fold_left (fun lifecycle_procs lifecycle_proc_str -> + try (lookup_proc lifecycle_proc_str) :: lifecycle_procs + with Not_found -> lifecycle_procs) + [] lifecycle_proc_strs in + Some (lifecycle_typ, lifecycle_procs) + | _ -> None + +(** return the complete list of (package, lifecycle_classname, lifecycle_methods) trios *) +let get_lifecycles = android_lifecycles + + +(** Checks if the exception is an uncheched exception *) +let is_runtime_exception tenv exn = + let runtime_exception_opt = + let name = Mangled.from_package_class "java.lang" "RuntimeException" in + let typename = Sil.TN_csu (Sil.Class, name) in + Sil.tenv_lookup tenv typename in + match Sil.tenv_lookup tenv (Sil.TN_csu (Sil.Class, exn)) with + | None -> assert false + | Some exn_type -> + begin + match runtime_exception_opt with + | None -> false + | Some runtime_exception_type -> + is_subtype exn_type runtime_exception_type tenv + end + + +let non_stub_android_jar () = + let root_dir = Filename.dirname (Filename.dirname Sys.executable_name) in + list_fold_left Filename.concat root_dir ["lib"; "java"; "android"; "android-19.jar"] diff --git a/infer/src/harness/androidFramework.mli b/infer/src/harness/androidFramework.mli new file mode 100644 index 000000000..565ca0477 --- /dev/null +++ b/infer/src/harness/androidFramework.mli @@ -0,0 +1,39 @@ +(* +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +(** Android lifecycle types and their lifecycle methods that are called by the framework *) + +open Utils + +(** return the complete list of (package, lifecycle_classname, lifecycle_methods) trios *) +val get_lifecycles : (string * string * string list) list + +(** return true if [typ] is a subclass of [lifecycle_typ] *) +val typ_is_lifecycle_typ : Sil.typ -> Sil.typ -> Sil.tenv -> bool + +(** return true if [typ] is a known callback class, false otherwise *) +val is_callback_class : Sil.typ -> Sil.tenv -> bool + +(** return true if [procname] is a special lifecycle cleanup method *) +val is_destroy_method : Procname.t -> bool + +(** returns an option containing the var name and type of a callback registered by [procname], None +if no callback is registered *) +val get_callback_registered_by : Procname.t -> (Sil.exp * Sil.typ) list -> Sil.tenv -> (Sil.exp * Sil.typ) option + +(** return a list of typ's corresponding to callback classes registered by [procdesc] *) +val get_callbacks_registered_by_proc : Cfg.Procdesc.t -> Sil.tenv -> Sil.typ list + +(** given an Android framework type mangled string [lifecycle_typ] (e.g., android.app.Activity) and +a list of method names [lifecycle_procs_strs], get the appropriate typ and procnames *) +val get_lifecycle_for_framework_typ_opt : Mangled.t -> string list -> Sil.tenv -> (Sil.typ * Procname.t list) option + +(** return true if [class_name] is the name of a class that belong to the Android framework *) +val is_android_lib_class : Mangled.t -> bool + +(** Path to the android.jar file containing real code, not just the method stubs as in the SDK *) +val non_stub_android_jar : unit -> string + +(** [is_runtime_exception tenv exn] checks if exn is an unchecked exception *) +val is_runtime_exception : Sil.tenv -> Mangled.t -> bool diff --git a/infer/src/harness/harness.ml b/infer/src/harness/harness.ml new file mode 100644 index 000000000..c4474e1b4 --- /dev/null +++ b/infer/src/harness/harness.ml @@ -0,0 +1,177 @@ +(* +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +module L = Logging +module F = Format + +open Utils + +(** Automatically create a harness method to exercise code under test *) + +(** given a list [lst] = fst @ (e :: rest), a test predicate [test], and a list [to_insert], returns +the list fst @ (e :: to_insert) @ rest, where e is the first element such that test(e) evaluates +to true. if test(e) does not evaluate to true for any element of the list, returns [lst]. *) +let insert_after lst test to_insert = + let rec insert_rec to_process processed = match to_process with + | instr :: to_process -> + let processed' = instr :: processed in + if test instr then + list_append (list_rev processed') (list_append to_insert to_process) + else + insert_rec to_process processed' + | [] -> lst in + insert_rec lst [] + +(** find callees that register callbacks and add instrumentation to extract the callback. +return the set of new global static fields created to extract callbacks and their types *) +let extract_callbacks procdesc cfg_file cfg tenv harness_name harness_lvar callback_fields = + (* try to turn a nasty callback name like MyActivity$1 into a nice callback name like + * Button.OnClickListener[line 7]*) + let create_descriptive_callback_name callback_typ loc = + let typ_str = match PatternMatch.type_get_class_name callback_typ with + | Some mangled -> Mangled.get_mangled mangled + | None -> Sil.typ_to_string callback_typ in + let pretty_typ_str = + if Procname.is_anonymous_inner_class_name typ_str then + match PatternMatch.type_get_direct_supertypes callback_typ with + | [] -> + (* this should never happen since an inner class always has a supertype *) + assert false + | l -> + (* choose to describe this anonymous inner class with one of the interfaces that it + * implements. translation always places interfaces at the end of the supertypes list *) + Mangled.get_mangled (list_hd (list_rev l)) + else typ_str in + Mangled.from_string (pretty_typ_str ^ "[line " ^ Sil.loc_to_string loc ^ "]") in + let create_instrumentation_fields created_flds node instr = match instr with + | Sil.Call([], Sil.Const (Sil.Cfun callee), args, loc, _) -> + begin + match AndroidFramework.get_callback_registered_by callee args tenv with + | Some (cb_obj, (Sil.Tptr (cb_typ, Sil.Pk_pointer) as ptr_to_cb_typ)) -> + let callback_fld_name = create_descriptive_callback_name ptr_to_cb_typ loc in + let created_fld = Ident.create_fieldname callback_fld_name 0 in + (* create a function that takes the type of the harness class as an argument and modifies + * the instruction set with callback extraction code. we do this because we need to know + * the typ of the harness class before we can write to any of its fields, but we cannot + * actually create this typ until we know how many fields we are going to create in order + * to extract callbacks *) + let mk_field_write harness_class_typ = + (* create an instruction that writes the registered callback object to a global static + * field in the harness class *) + let fld_write_lhs = Sil.Lfield (harness_lvar, created_fld, harness_class_typ) in + let extract_cb_instr = Sil.Set (fld_write_lhs, cb_typ, cb_obj, loc) in + let instrumented_instrs = + let node_instrs = Cfg.Node.get_instrs node in + insert_after node_instrs (fun e -> e == instr) [extract_cb_instr] in + Cfg.Node.replace_instrs node instrumented_instrs; + (cfg_file, cfg) in + (created_fld, ptr_to_cb_typ, mk_field_write) :: created_flds + | _ -> created_flds + end + | _ -> created_flds in + Cfg.Procdesc.fold_instrs create_instrumentation_fields callback_fields procdesc + +(** find all of the callbacks registered by methods in [lifecycle_trace *) +let find_registered_callbacks lifecycle_trace harness_name proc_file_map tenv = + (* what would be ideal to do here is to go through every method (transitively) called by a + * lifecycle method and look for registered callbacks, however, this would need to be a complex + * iterative process, as detecting callbacks can lead to more methods being called, which in + * turn can lead to more callbacks being registered. so what we do here is iterate through each + * file that a lifecycle proc is defined in and collect all callbacks possibly registered in + * methods in that file. this can err on the side of including too many callbacks (for example, if + * a callback is registered in a superclass method that is overridden, this scheme would + * wrongly include it). on the other hand, this will miss callbacks registered in + * callees of lifecycle methods that aren't in our list of "lifecycle methods files" *) + (* TODO (t4793988): do something more principled here *) + let harness_lvar = Sil.Lvar (Sil.mk_pvar_global harness_name) in + let lifecycle_cfg_files = + list_fold_left (fun lifecycle_files (lifecycle_proc, _) -> + try + let cfg_fname = + let source_dir = Inhabit.source_dir_from_name lifecycle_proc proc_file_map in + DB.source_dir_get_internal_file source_dir ".cfg" in + DB.FilenameSet.add cfg_fname lifecycle_files + with Not_found -> lifecycle_files + ) DB.FilenameSet.empty lifecycle_trace in + DB.FilenameSet.fold (fun cfg_file registered_callbacks -> + match Cfg.load_cfg_from_file cfg_file with + | Some cfg -> + list_fold_left (fun registered_callbacks procdesc -> + extract_callbacks procdesc cfg_file cfg tenv harness_name harness_lvar registered_callbacks + ) registered_callbacks (Cfg.get_all_procs cfg) + | None -> registered_callbacks + ) lifecycle_cfg_files [] + +(** if [typ] is a lifecycle type, generate a list of (method call, receiver) pairs constituting a +lifecycle trace *) +let try_create_lifecycle_trace typ lifecycle_typ lifecycle_procs proc_file_map tenv = match typ with + | Sil.Tstruct(_, _, Sil.Class, Some class_name, _, methods, _) + when AndroidFramework.typ_is_lifecycle_typ typ lifecycle_typ tenv && + not (AndroidFramework.is_android_lib_class class_name) -> + let ptr_to_typ = Some (Sil.Tptr (typ, Sil.Pk_pointer)) in + list_fold_left (fun trace lifecycle_proc -> + (* given a lifecycle subclass T, resolve the call T.lifecycle_proc() to the procname + * that will actually be called at runtime *) + let resolved_proc = SymExec.resolve_method tenv class_name lifecycle_proc in + (resolved_proc, ptr_to_typ) :: trace + ) [] lifecycle_procs + | _ -> [] + +(** get all the callbacks registered in [lifecycle_trace], transform the SIL to "extract" them into +global static fields belong to the harness so that they are easily callable, and return a list +of the (field, typ) pairs that we have created for this purpose *) +let extract_callbacks lifecycle_trace harness_procname proc_file_map tenv = + let harness_name = Mangled.from_string (Procname.to_string harness_procname) in + let registered_cbs = + find_registered_callbacks lifecycle_trace harness_name proc_file_map tenv in + let fields = list_map (fun (fld, typ, _) -> (fld, typ, [])) registered_cbs in + (* create a new typ for the harness containing all of the cb extraction vars as static fields *) + let harness_typ = + Sil.Tstruct (fields, [], Sil.Class, Some harness_name, [], [harness_procname], []) in + (* update the tenv with our created harness typ. we don't have to save the tenv to disk here + * because this is done immediately after harness generation runs in jMain.ml *) + let harness_class = Sil.TN_csu (Sil.Class, harness_name) in + Sil.tenv_add tenv harness_class harness_typ; + let cfgs_to_save = + list_fold_left (fun cfgs_to_save (_, _, instrument_sil_f) -> + (* instrument the cfg's with callback extraction code *) + let (cfg_file, cfg) = instrument_sil_f harness_typ in + DB.FilenameMap.add cfg_file cfg cfgs_to_save + ) DB.FilenameMap.empty registered_cbs in + (* re-save the cfgs that we've modified by extracting callbacks *) + DB.FilenameMap.iter + (fun cfg_file cfg -> Cfg.store_cfg_to_file cfg_file false cfg) cfgs_to_save; + (* these are all the static fields holding callbacks that should be invoked by the harness *) + let harness_global = Sil.Lvar (Sil.mk_pvar_global harness_name) in + list_map (fun (fld, typ, _) -> (Sil.Lfield (harness_global, fld, harness_typ), typ)) fields + +(** generate a harness for each lifecycle type in an Android application *) +let create_android_harness proc_file_map tenv = + list_iter (fun (pkg, clazz, lifecycle_methods) -> + let typ_name = Mangled.from_package_class pkg clazz in + match AndroidFramework.get_lifecycle_for_framework_typ_opt typ_name lifecycle_methods tenv with + | Some (framework_typ, framework_procs) -> + (* iterate through the type environment and generate a lifecycle harness for each subclass of + * [lifecycle_typ] *) + Sil.tenv_iter (fun _ typ -> + match try_create_lifecycle_trace typ framework_typ framework_procs proc_file_map tenv with + | [] -> () + | lifecycle_trace -> + (* we have identified an application lifecycle type and created a trace for it. now, + * identify the callbacks registered by methods belonging to this type and get the + * inhabitation module to create a harness for us *) + let harness_procname = + let harness_cls_name = PatternMatch.get_type_name typ in + Procname.mangled_java (None, harness_cls_name) None "InferGeneratedHarness" [] in + let callback_fields = + extract_callbacks lifecycle_trace harness_procname proc_file_map tenv in + Inhabit.inhabit_trace lifecycle_trace callback_fields harness_procname proc_file_map tenv + ) tenv + | None -> () + ) AndroidFramework.get_lifecycles + +let parse_trace trace = Stacktrace.parse_stack_trace trace + +(** Generate a harness method for exe_env and add it to the execution environment *) +let create_harness proc_file_map tenv = create_android_harness proc_file_map tenv diff --git a/infer/src/harness/harness.mli b/infer/src/harness/harness.mli new file mode 100644 index 000000000..b9d2d04ea --- /dev/null +++ b/infer/src/harness/harness.mli @@ -0,0 +1,8 @@ +(* +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +(** Automatically create a harness method to exercise code under test *) + +(** Generate a harness method for exe_env and add it to the execution environment *) +val create_harness : DB.source_file Procname.Map.t -> Sil.tenv -> unit diff --git a/infer/src/harness/inhabit.ml b/infer/src/harness/inhabit.ml new file mode 100644 index 000000000..b06b6c940 --- /dev/null +++ b/infer/src/harness/inhabit.ml @@ -0,0 +1,396 @@ +(* +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +(** Generate a procedure that calls a given sequence of methods. Useful for harness/test +* generation. *) + +module L = Logging +module F = Format +module P = Printf +module IdSet = Ident.IdentSet +module TypSet = Sil.TypSet +module TypMap = Sil.TypMap +open Utils + +type lifecycle_trace = (Procname.t * Sil.typ option) list +type callback_trace = (Sil.exp * Sil.typ) list + +(** list of instrs and temporary variables created during inhabitation and a cache of types that +* have already been inhabited *) +type env = { instrs : Sil.instr list; + tmp_vars : Ident.t list; + cache : Sil.exp TypMap.t; + (* set of types currently being inhabited. consult to prevent infinite recursion *) + cur_inhabiting : TypSet.t; + pc : Sil.location; + harness_name : Procname.t } + +(** add an instruction to the env, update tmp_vars, and bump the pc *) +let env_add_instr instr tmp_vars env = + let incr_pc pc = { pc with Sil.line = pc.Sil.line + 1 } in + { env with instrs = instr :: env.instrs; tmp_vars = tmp_vars @ env.tmp_vars; pc = incr_pc env.pc } + +(** call flags for an allocation or call to a constructor *) +let cf_alloc = Sil.cf_default + +(** returns true if the procedure description is a Java static method *) +(* TODO (t4559939): this information isn't in Procdesc, so we can't implement this method *) +let is_static procdesc = false + +let fun_exp_from_name proc_name = Sil.Const (Sil.Cfun (proc_name)) + +let source_dir_from_name proc_name proc_file_map = + let source_file = Procname.Map.find proc_name proc_file_map in + DB.source_dir_from_source_file source_file + +let cfg_from_name proc_name proc_file_map = + let source_dir = source_dir_from_name proc_name proc_file_map in + let cfg_fname = DB.source_dir_get_internal_file source_dir ".cfg" in + match Cfg.load_cfg_from_file cfg_fname with + | Some cfg -> cfg + | None -> assert false + +let cg_from_name proc_name proc_file_map = + let source_dir = source_dir_from_name proc_name proc_file_map in + let cg_fname = DB.source_dir_get_internal_file source_dir ".cg" in + match Cg.load_from_file cg_fname with + | Some cfg -> cfg + | None -> assert false + +let procdesc_from_name proc_name proc_file_map = + match Cfg.Procdesc.find_from_name (cfg_from_name proc_name proc_file_map) proc_name with + | Some proc_desc -> proc_desc + | None -> assert false + +let formals_from_name proc_name proc_file_map = + let procdesc = procdesc_from_name proc_name proc_file_map in + Cfg.Procdesc.get_formals procdesc + +let local_name_cntr = ref 0 + +let create_fresh_local_name () = + incr local_name_cntr; + "dummy_local" ^ string_of_int !local_name_cntr + +(** more forgiving variation of list_tl that won't raise an exception on the empty list *) +let tl_or_empty l = if l = [] then l else list_tl l + +let get_non_receiver_formals formals = tl_or_empty formals + +(** create Sil corresponding to x = new typ() or x = new typ[]. For ordinary allocation, sizeof_typ +* and ret_typ should be the same, but arrays are slightly odd in that sizeof_typ will have a size +* component but the size component of ret_typ is always -1. *) +let inhabit_alloc sizeof_typ ret_typ alloc_kind env = + let retval = Ident.create_fresh Ident.knormal in + let inhabited_exp = Sil.Var retval in + let call_instr = + let fun_new = fun_exp_from_name alloc_kind in + let sizeof_exp = Sil.Sizeof (sizeof_typ, Sil.Subtype.exact) in + let args = [(sizeof_exp, Sil.Tptr (ret_typ, Sil.Pk_pointer))] in + Sil.Call ([retval], fun_new, args, env.pc, cf_alloc) in + (inhabited_exp, env_add_instr call_instr [retval] env) + +(** find or create a Sil expression with type typ *) +let rec inhabit_typ typ proc_file_map env = + try (TypMap.find typ env.cache, env) + with Not_found -> + let inhabit_internal typ env = match typ with + | Sil.Tptr (Sil.Tarray (inner_typ, Sil.Const (Sil.Cint size)), Sil.Pk_pointer) -> + let arr_size = Sil.Const (Sil.Cint (Sil.Int.one)) in + let arr_typ = Sil.Tarray (inner_typ, arr_size) in + inhabit_alloc arr_typ typ SymExec.ModelBuiltins.__new_array env + | Sil.Tptr (typ, Sil.Pk_pointer) as ptr_to_typ -> + (* TODO (t4575417): this case does not work correctly for enums, but they are currently + * broken in Infer anyway (see t4592290) *) + let (allocated_obj_exp, env) = inhabit_alloc typ typ SymExec.ModelBuiltins.__new env in + (* select methods that are constructors and won't force us into infinite recursion because + * we are already inhabiting one of their argument types *) + let get_all_suitable_constructors typ = match typ with + | Sil.Tstruct (_, _, Sil.Class, _, superclasses, methods, _) -> + let is_suitable_constructor p = + let try_get_non_receiver_formals p = + try get_non_receiver_formals (formals_from_name p proc_file_map) + with Not_found -> [] in + Procname.is_constructor p && list_for_all (fun (_, typ) -> + not (TypSet.mem typ env.cur_inhabiting)) (try_get_non_receiver_formals p) in + list_filter (fun p -> is_suitable_constructor p) methods + | _ -> [] in + let (env, typ_class_name) = match get_all_suitable_constructors typ with + | constructor :: _ -> + (* arbitrarily choose a constructor for typ and invoke it. eventually, we may want to + * nondeterministically call all possible constructors instead *) + let env = + inhabit_constructor constructor (allocated_obj_exp, ptr_to_typ) proc_file_map env in + (* try to get the unqualified name as a class (e.g., Object for java.lang.Object so we + * we can use it as a descriptive local variable name in the harness *) + let typ_class_name = + if Procname.is_java constructor then Procname.java_get_simple_class constructor + else create_fresh_local_name () in + (env, Mangled.from_string typ_class_name) + | [] -> (env, Mangled.from_string (create_fresh_local_name ())) in + (* add the instructions *& local = [allocated_obj_exp]; id = *& local, where local and id are + * both fresh. the only point of this is to add a descriptive local name that makes error + * reports from the harness look nicer -- it's not necessary to make symbolic execution work *) + let fresh_local_exp = Sil.Lvar (Sil.mk_pvar typ_class_name env.harness_name) in + let write_to_local_instr = + Sil.Set (fresh_local_exp, ptr_to_typ, allocated_obj_exp, env.pc) in + let env' = env_add_instr write_to_local_instr [] env in + let fresh_id = Ident.create_fresh Ident.knormal in + let read_from_local_instr = Sil.Letderef (fresh_id, fresh_local_exp, ptr_to_typ, env'.pc) in + (Sil.Var fresh_id, env_add_instr read_from_local_instr [fresh_id] env') + | Sil.Tint (_) -> (Sil.Const (Sil.Cint (Sil.Int.zero)), env) + | Sil.Tfloat (_) -> (Sil.Const (Sil.Cfloat 0.0), env) + | typ -> + L.err "Couldn't inhabit typ: %a@." (Sil.pp_typ pe_text) typ; + assert false in + let (inhabited_exp, env') = + inhabit_internal typ { env with cur_inhabiting = TypSet.add typ env.cur_inhabiting } in + (inhabited_exp, { env' with cache = TypMap.add typ inhabited_exp env.cache; + cur_inhabiting = env.cur_inhabiting }) + +(** inhabit each of the types in the formals list *) +and inhabit_args formals proc_file_map env = + let inhabit_arg (formal_name, formal_typ) (args, env) = + let (exp, env) = inhabit_typ formal_typ proc_file_map env in + ((exp, formal_typ) :: args, env) in + list_fold_right inhabit_arg formals ([], env) + +(** create Sil that calls the constructor in constr_name on allocated_obj and inhabits the +* remaining arguments *) +and inhabit_constructor constr_name (allocated_obj, obj_type) proc_file_map env = + try + (* this lookup can fail when we try to get the procdesc of a procedure from a different + * module. this could be solved with a whole - program class hierarchy analysis *) + let (args, env) = + let non_receiver_formals = tl_or_empty (formals_from_name constr_name proc_file_map) in + inhabit_args non_receiver_formals proc_file_map env in + let constr_instr = + let fun_exp = fun_exp_from_name constr_name in + Sil.Call ([], fun_exp, (allocated_obj, obj_type) :: args, env.pc, Sil.cf_default) in + env_add_instr constr_instr [] env + with Not_found -> env + +let inhabit_call_with_args procname procdesc args env = + let retval = + let is_void = Cfg.Procdesc.get_ret_type procdesc = Sil.Tvoid in + if is_void then [] else [Ident.create_fresh Ident.knormal] in + let call_instr = + let fun_exp = fun_exp_from_name procname in + let flags = { Sil.cf_virtual = not (is_static procdesc); Sil.cf_noreturn = false; Sil.cf_is_objc_block = false; } in + Sil.Call (retval, fun_exp, args, env.pc, flags) in + env_add_instr call_instr retval env + +(** create Sil that inhabits args to and calls proc_name *) +let inhabit_call (procname, receiver) proc_file_map env = + try + let procdesc = procdesc_from_name procname proc_file_map in + (* swap the type of the 'this' formal with the receiver type, if there is one *) + let formals = match (Cfg.Procdesc.get_formals procdesc, receiver) with + | ((name, typ) :: formals, Some receiver) -> (name, receiver) :: formals + | (formals, None) -> formals + | ([], Some receiver) -> + L.err + "Expected at least one formal to bind receiver to in method %a@." Procname.pp procname; + assert false in + let (args, env) = inhabit_args formals proc_file_map env in + inhabit_call_with_args procname procdesc args env + with Not_found -> env + +let inhabit_fld_trace flds proc_file_map env = + let invoke_cb (fld_exp, fld_typ) env = + let lhs = Ident.create_fresh Ident.knormal in + let fld_read_instr = + Sil.Letderef (lhs, fld_exp, fld_typ, env.pc) in + let env = env_add_instr fld_read_instr [lhs] env in + match fld_typ with + | Sil.Tptr (Sil.Tstruct (_, _, Sil.Class, _, _, procs, _), _) -> + let inhabit_cb_call procname env = + try + let procdesc = procdesc_from_name procname proc_file_map in + (* replace receiver arg with the value read from the field, inhabit other args *) + let (args, env) = + let formals = Cfg.Procdesc.get_formals procdesc in + inhabit_args (tl_or_empty formals) proc_file_map env in + inhabit_call_with_args procname procdesc ((Sil.Var lhs, fld_typ) :: args) env + with Not_found -> + (* TODO (t4645631): investigate why this failure occurs *) + env in + list_fold_left (fun env procname -> + if not (Procname.is_constructor procname) && + not (Procname.java_is_access_method procname) then inhabit_cb_call procname env + else env) env procs + | _ -> assert false in + list_fold_left (fun env fld -> invoke_cb fld env) env flds + +(** create a dummy file for the harness and associate them in the exe_env *) +let create_dummy_harness_file harness_name harness_cfg tenv = + let dummy_file_name = + let dummy_file_dir = + let sources_dir = DB.sources_dir () in + if Sys.file_exists sources_dir then sources_dir + else Filename.get_temp_dir_name () in + let file_str = + Procname.java_get_class harness_name ^ "_" ^Procname.java_get_method harness_name ^ ".java" in + Filename.concat dummy_file_dir file_str in + DB.source_file_from_string dummy_file_name + +(** write the SIL for the harness to a file *) +(* TODO (t3040429): fill this file up with Java-like code that matches the SIL *) +let write_harness_to_file harness_instrs harness_file = + let harness_file = + let harness_file_name = DB.source_file_to_string harness_file in + ref (create_outfile harness_file_name) in + let pp_harness fmt = list_iter (fun instr -> + Format.fprintf fmt "%a\n" (Sil.pp_instr pe_text) instr) harness_instrs in + do_outf harness_file (fun outf -> + pp_harness outf.fmt; + close_outf outf) + +(** add the harness proc to the cg and make sure its callees can be looked up by sym execution *) +let add_harness_to_cg harness_name harness_cfg harness_node loc cg tenv = + Cg.add_node cg harness_name; + let create_dummy_procdesc procname = + (* convert a java type string to a type *) + let rec lookup_typ typ_str = match typ_str with + | "" | "void" -> Sil.Tvoid + | "int" -> Sil.Tint Sil.IInt + | "byte" -> Sil.Tint Sil.IShort + | "short" -> Sil.Tint Sil.IShort + | "boolean" -> Sil.Tint Sil.IBool + | "char" -> Sil.Tint Sil.IChar + | "long" -> Sil.Tint Sil.ILong + | "float" -> Sil.Tfloat Sil.FFloat + | "double" -> Sil.Tfloat Sil.FDouble + | typ_str when String.contains typ_str '[' -> + let stripped_typ = String.sub typ_str 0 ((String.length typ_str) - 2) in + let array_typ_size = Sil.exp_get_undefined false in + Sil.Tptr (Sil.Tarray (lookup_typ stripped_typ, array_typ_size), Sil.Pk_pointer) + | _ -> + (* non-primitive/non-array type--resolve it in the tenv *) + match Sil.get_typ (Mangled.from_string typ_str) None tenv with + | Some typ -> typ + | None -> failwith ("Failed to look up typ " ^ typ_str) in + let return_typ = lookup_typ (Procname.java_get_return_type procname) in + let params = + let param_strs = Procname.java_get_parameters procname in + list_fold_right (fun typ_str params -> ("", lookup_typ typ_str) :: params) param_strs [] in + let open Cfg.Procdesc in + let proc_attributes = + { + Sil.access = Sil.Default; + Sil.exceptions = []; + Sil.is_abstract = false; + Sil.is_bridge_method = false; + Sil.is_objc_instance_method = false; + Sil.is_synthetic_method = false; + Sil.language = Sil.Java; + Sil.method_annotation = Sil.method_annotation_empty; + } in + create { + cfg = harness_cfg; + name = procname; + is_defined = false; + ret_type = return_typ; + formals = params; + locals = []; + captured = []; + loc = loc; + proc_attributes = proc_attributes; + } in + list_iter (fun p -> + (* add harness -> callee edge to the call graph *) + Cg.add_edge cg harness_name p; + (* create dummy procdescs for callees not in the module. hopefully t4583729 will remove the + * need to do this in the future *) + if not (SymExec.function_is_builtin p) then + (* simulate symbolic execution's lookup of a procedure *) + match Cfg.Procdesc.find_from_name harness_cfg p with + | Some _ -> () + | None -> ignore (create_dummy_procdesc p) + ) (Cfg.Node.get_callees harness_node) + +(** create and fill the appropriate nodes and add them to the harness cfg. also add the harness +* proc to the cg *) +let setup_harness_cfg harness_name harness_cfg env proc_file_map tenv = + (* TMP: pick an arbitrary cg and cfg to piggyback the harness code onto *) + (* TODO (t4707171): create our own fresh cfg / cg instead *) + let (source_dir, cg) = + let (proc_name, _) = Procname.Map.choose proc_file_map in + let cg = cg_from_name proc_name proc_file_map in + (source_dir_from_name proc_name proc_file_map, cg) in + let cfg_file = DB.source_dir_get_internal_file source_dir ".cfg" in + let harness_cfg = match Cfg.load_cfg_from_file cfg_file with + | Some cfg -> cfg + | None -> assert false in + let cg_file = DB.source_dir_get_internal_file source_dir ".cg" in + let procdesc = + let open Cfg.Procdesc in + let proc_attributes = + { + Sil.access = Sil.Default; + Sil.exceptions = []; + Sil.is_abstract = false; + Sil.is_bridge_method = false; + Sil.is_objc_instance_method = false; + Sil.is_synthetic_method = false; + Sil.language = Sil.Java; + Sil.method_annotation = Sil.method_annotation_empty; + } in + create { + cfg = harness_cfg; + name = harness_name; + is_defined = true; + ret_type = Sil.Tvoid; + formals = []; + locals = []; + captured = []; + loc = env.pc; + proc_attributes = proc_attributes; + } in + let harness_node = + (* important to reverse the list or there will be scoping issues! *) + let instrs = (list_rev env.instrs) in + let nodekind = Cfg.Node.Stmt_node "method_body" in + Cfg.Node.create harness_cfg env.pc nodekind instrs procdesc env.tmp_vars in + let (start_node, exit_node) = + let create_node kind = Cfg.Node.create harness_cfg env.pc kind [] procdesc [] in + let start_kind = Cfg.Node.Start_node procdesc in + let exit_kind = Cfg.Node.Exit_node procdesc in + (create_node start_kind, create_node exit_kind) in + Cfg.Procdesc.set_start_node procdesc start_node; + Cfg.Procdesc.set_exit_node procdesc exit_node; + Cfg.Node.add_locals_ret_declaration start_node []; + Cfg.Node.set_succs_exn start_node [harness_node] [exit_node]; + Cfg.Node.set_succs_exn harness_node [exit_node] [exit_node]; + Cfg.add_removetemps_instructions harness_cfg; + Cfg.add_abstraction_instructions harness_cfg; + add_harness_to_cg harness_name harness_cfg harness_node env.pc cg tenv; + (* save out the cg and cfg so that they will be accessible in the next phase of the analysis *) + Cg.store_to_file cg_file cg; + Cfg.store_cfg_to_file cfg_file false harness_cfg + +(** create a procedure named harness_name that calls each of the methods in trace in the specified +* order with the specified receiver and add it to the execution environment *) +let inhabit_trace trace cb_flds harness_name proc_file_map tenv = if list_length trace > 0 then + let harness_cfg = Cfg.Node.create_cfg () in + let harness_file = create_dummy_harness_file harness_name harness_cfg tenv in + let empty_env = + let pc = { Sil.line = 1; col = 1; file = harness_file; nLOC = 0; } in + { instrs = []; + tmp_vars = []; + cache = TypMap.empty; + pc = pc; + cur_inhabiting = TypSet.empty; + harness_name = harness_name; } in + (* synthesize the harness body *) + let env'' = + (* invoke lifecycle methods *) + let env' = + list_fold_left (fun env to_call -> inhabit_call to_call proc_file_map env) empty_env trace in + (* invoke callbacks *) + inhabit_fld_trace cb_flds proc_file_map env' in + try + setup_harness_cfg harness_name harness_cfg env'' proc_file_map tenv; + write_harness_to_file (list_rev env''.instrs) harness_file + with Not_found -> () diff --git a/infer/src/harness/inhabit.mli b/infer/src/harness/inhabit.mli new file mode 100644 index 000000000..f34282d5e --- /dev/null +++ b/infer/src/harness/inhabit.mli @@ -0,0 +1,25 @@ +(* +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +open Utils + +(** Generate a procedure that calls a given sequence of methods. Useful for harness/test generation. *) + +type lifecycle_trace = (Procname.t * Sil.typ option) list + +type callback_trace = (Sil.exp * Sil.typ) list + +(** create a procedure named harness_name that calls each of the methods in trace in the specified +order with the specified receiver and add it to the execution environment *) +val inhabit_trace : lifecycle_trace -> callback_trace -> Procname.t -> + +DB.source_file Procname.Map.t -> Sil.tenv -> unit + +val source_dir_from_name : Procname.t -> DB.source_file Procname.Map.t -> DB.source_dir + +val cfg_from_name : Procname.t -> DB.source_file Procname.Map.t -> Cfg.cfg + +val cg_from_name : Procname.t -> DB.source_file Procname.Map.t -> Cg.t + +val procdesc_from_name : Procname.t -> DB.source_file Procname.Map.t -> Cfg.Procdesc.t diff --git a/infer/src/harness/stacktrace.ml b/infer/src/harness/stacktrace.ml new file mode 100644 index 000000000..b474fca1c --- /dev/null +++ b/infer/src/harness/stacktrace.ml @@ -0,0 +1,89 @@ +(* +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +(** Module for parsing stack traces and using them to guide Infer analysis *) + +module L = Logging +module F = Format +open Utils + +type str_frame = { + class_str : string; + method_str : string; + file_str : string; + line_num : int; +} + +(** A stack trace element whose procname / source file we can identify *) +type resolved_frame = { + possible_calls : Procname.t list; + file_name : DB.source_file; + line_num : int; +} + +type stack_frame = + | Resolved of resolved_frame + | Unresolved of str_frame + +(** list representation of a stack trace. head of the list is the top of the stack (line/proc where +exception occurs *) +type stack_trace = stack_frame list + +(** given [str_frame], try to resolve its components in [exe_env] *) +let try_resolve_frame str_frame exe_env tenv = + try + let class_name = Mangled.from_string str_frame.class_str in + (* find the class name in the tenv and get the procedure(s) whose names match the procedure name + * in the stack trace. Note that the stack trace does not have any type or argument information; + * the name is all that we have to go on *) + match Sil.tenv_lookup tenv (Sil.TN_csu (Sil.Class, class_name)) with + | Some Sil.Tstruct (_, _, Sil.Class, _, _, decl_procs, _) -> + let possible_calls = + list_filter + (fun proc -> Procname.java_get_method proc = str_frame.method_str) + decl_procs in + if list_length possible_calls > 0 then + (* using list_hd here assumes that all of the possible calls are declared in the + * same file, which will be true in Java but not necessarily in other languages *) + let file_name = Exe_env.get_source exe_env (list_hd possible_calls) in + Resolved + { possible_calls = possible_calls; file_name = file_name; line_num = str_frame.line_num; } + else Unresolved str_frame + | _ -> Unresolved str_frame + with Not_found -> Unresolved str_frame + +(** given a stack trace line like "at com.foo.Class.method(Class.java:42)" extract the class name, +method name, file name, and line number *) +let parse_frame frame_str exe_env tenv = + (* separate the qualified method name and the parenthesized text/line number*) + ignore(Str.string_match (Str.regexp "at \\(.*\\)(\\(.*\\))") frame_str 0); + let qualified_procname = Str.matched_group 1 frame_str in + let file_and_line = Str.matched_group 2 frame_str in + (* separate the class name from the method name *) + ignore(Str.string_match (Str.regexp "\\(.*\\)\\.\\(.*\\)") qualified_procname 0); + let class_str = Str.matched_group 1 qualified_procname in + let method_str = Str.matched_group 2 qualified_procname in + (* separate the filename and line number *) + ignore(Str.string_match (Str.regexp "\\(.*\\):\\([0-9]+\\)") file_and_line 0); + let file_str = Str.matched_group 1 file_and_line in + let line_num = int_of_string (Str.matched_group 2 file_and_line) in + try_resolve_frame + { class_str = class_str; method_str = method_str; file_str = file_str; line_num = line_num } + exe_env tenv + +(** create an Infer-readable representation of a stack trace given its raw text *) +let parse_stack_trace trace_str exe_env = + let tenv = Exe_env.get_tenv exe_env (list_hd (Cg.get_defined_nodes (Exe_env.get_cg exe_env))) in + let trace_list = Str.split (Str.regexp "\n") trace_str in + list_map (fun frame_str -> parse_frame frame_str exe_env tenv) trace_list + +let pp_str_frame fmt = function + | Resolved f -> + F.fprintf fmt "Procs { %a }" (pp_semicolon_seq pe_text Procname.pp) f.possible_calls + | Unresolved f -> + F.fprintf fmt "UNRESOLVED: %s %s %s %d" f.class_str f.method_str f.file_str f.line_num + +let rec pp_str_stack_trace fmt = function + | [] -> () + | frame :: rest -> F.fprintf fmt "%a;@\n%a" pp_str_frame frame pp_str_stack_trace rest diff --git a/infer/src/harness/stacktrace.mli b/infer/src/harness/stacktrace.mli new file mode 100644 index 000000000..ad4c36205 --- /dev/null +++ b/infer/src/harness/stacktrace.mli @@ -0,0 +1,12 @@ +(* +* Copyright (c) 2013 - Facebook. All rights reserved. +*) + +(** Module for parsing stack traces and using them to guide Infer analysis *) + +open Utils + +type stack_trace + +(** create an Infer-readable representation of a stack trace given its raw text *) +val parse_stack_trace : string -> Exe_env.t -> stack_trace diff --git a/infer/src/java/.project b/infer/src/java/.project new file mode 100644 index 000000000..5e9119fba --- /dev/null +++ b/infer/src/java/.project @@ -0,0 +1,17 @@ + + + InferJava + + + + + + Ocaml.ocamlMakefileBuilder + + + + + + ocaml.ocamlnatureMakefile + + diff --git a/infer/src/java/INSTALL b/infer/src/java/INSTALL new file mode 100644 index 000000000..060da3bc5 --- /dev/null +++ b/infer/src/java/INSTALL @@ -0,0 +1,46 @@ + +This file contains the instructions to compile a version of Infer to verify +Java programs. + +In order to avoid problems with some dependencies that may occur at compile time, +it is best to use the OCaml Batteries Included distribution. + +Step 1) Install Javalib and Sawja with the following the instruction: + +a) install libzip-dev: + + On ubuntu, install: libzip-ocaml-dev + +b) first install javalib by going to: + + dependencies/javalib-2.2.2 + + and run + ./configure.sh + + and follow the instructions. + +c) install Sawja: + + go to + + dependencies/sawja-1.4 + + and then: + + ./configure.sh + make && sudo make install + +Step 2) InferJava can now be compiled by going to the directory: + + infer/java + + and typing: + + make + +This will automatically compile the InferJava binary and move it to + + infer/bin + +Note that folder with the binary should be added to PATH diff --git a/infer/src/java/QUESTIONS b/infer/src/java/QUESTIONS new file mode 100644 index 000000000..52b712329 --- /dev/null +++ b/infer/src/java/QUESTIONS @@ -0,0 +1,5 @@ + +- It seems that we are never calling print_icfg_dotty with the second +argument: "extra edges" + +- What does the location corresponds to for a procdesc created for an external method invocation ? diff --git a/infer/src/java/TODO b/infer/src/java/TODO new file mode 100644 index 000000000..b68fcd751 --- /dev/null +++ b/infer/src/java/TODO @@ -0,0 +1,16 @@ + +- Sawja being GPL, remember to produce two binaries, one with Infer and one for the Sawja libs, for the releases versions + +- In the construction of cfgs: add line numbers for the start and the exit nodes + +- Sil.typename should rather be called namedType to clarify that it is not the name of a type, but a type build with a name + +- If sil.mli, the different kinds of if are not documented. Some are clear but others are obscure. The choice of an if_kind should also be explained + +- translate correctly the JBir.ArrayLength expression + +- in sil.mli, the explanation of what the function expand_type does is not clear enough + +- take into account the final keyword + +- rename the file InferInject.ml into inferinject.ml as it not consistent with the other module names. diff --git a/infer/src/java/doc.odocl b/infer/src/java/doc.odocl new file mode 100644 index 000000000..7cfd85369 --- /dev/null +++ b/infer/src/java/doc.odocl @@ -0,0 +1,5 @@ +sil +cfg_infer +cg +dotty +symExec diff --git a/infer/src/java/jAnnotation.ml b/infer/src/java/jAnnotation.ml new file mode 100644 index 000000000..fcefb636c --- /dev/null +++ b/infer/src/java/jAnnotation.ml @@ -0,0 +1,40 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +open Javalib_pack +open Utils + +(** Translate an annotation. *) +let translate a : Sil.annotation = + let class_name = JBasics.cn_name a.JBasics.kind in + let translate_value_pair (name, value) = + match value with + | JBasics.EVArray [JBasics.EVCstString s] -> + s + | JBasics.EVCstString s -> + s + | _ -> "?" in + let element_value_pairs = a.JBasics.element_value_pairs in + { Sil.class_name = class_name; + Sil.parameters = list_map translate_value_pair element_value_pairs } + + +(** Translate an item annotation. *) +let translate_item avlist : Sil.item_annotation = + let trans_vis = function + | Javalib.RTVisible -> true + | Javalib.RTInvisible -> false in + let trans (a, v) = translate a, trans_vis v in + list_map trans avlist + + +(** Translate a method annotation. *) +let translate_method ann : Sil.method_annotation = + let global_ann = ann.Javalib.ma_global in + let param_ann = ann.Javalib.ma_parameters in + let ret_item = translate_item global_ann in + let param_items = list_map translate_item param_ann in + ret_item, param_items diff --git a/infer/src/java/jAnnotation.mli b/infer/src/java/jAnnotation.mli new file mode 100644 index 000000000..7326469f2 --- /dev/null +++ b/infer/src/java/jAnnotation.mli @@ -0,0 +1,14 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +open Javalib_pack + + +(** Translate an item annotation. *) +val translate_item : (JBasics.annotation * Javalib.visibility) list -> Sil.item_annotation + +(** Translate a method annotation. *) +val translate_method : Javalib.method_annotations -> Sil.method_annotation diff --git a/infer/src/java/jClasspath.ml b/infer/src/java/jClasspath.ml new file mode 100644 index 000000000..53c6281b2 --- /dev/null +++ b/infer/src/java/jClasspath.ml @@ -0,0 +1,207 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +open Javalib_pack +open Sawja_pack +open Utils + +module L = Logging + +let models_specs_filenames = ref StringSet.empty + +let arg_classpath = ref "" + +let arg_jarfile = ref "" + +let set_jarfile file = + arg_jarfile := file + +let javac_verbose_out = ref "" + +let set_verbose_out path = + javac_verbose_out := path + +let models_jar = ref "" + + +let collect_specs_filenames jar_filename = + let file_in = Zip.open_in jar_filename in + let collect set e = + let filename = e.Zip.filename in + if not (Filename.check_suffix filename ".specs") then set + else + let proc_filename = (Filename.chop_extension (Filename.basename filename)) in + StringSet.add proc_filename set in + models_specs_filenames := list_fold_left collect !models_specs_filenames (Zip.entries file_in); + Zip.close_in file_in + + +let add_models jar_filename = + models_jar := jar_filename; + if Sys.file_exists !models_jar then + collect_specs_filenames jar_filename + else + failwith "Java model file not found" + + +let is_model procname = + StringSet.mem (Procname.to_filename procname) !models_specs_filenames + + +let split_classpath cp = Str.split (Str.regexp JFile.sep) cp + + +let java_source_file_from_path path = + if Filename.is_relative path then + failwith "Expect absolute path for java source files" + else + match !Config.project_root with + | None -> DB.abs_source_file_from_path path + | Some project_root -> DB.rel_source_file_from_abs_path project_root path + + +(** Add the android.jar containing bytecode at the beginning of the class path *) +let add_android_jar paths = + AndroidFramework.non_stub_android_jar () :: paths + + +let append_path classpath path = + if Sys.file_exists path then + let full_path = filename_to_absolute path in + if String.length classpath = 0 then + full_path + else + classpath^JFile.sep^full_path + else + classpath + + +let load_sources_and_classes () = + let file_in = open_in !javac_verbose_out in + let cwd = Sys.getcwd () in + let convert_filename fname = + if Filename.is_relative fname then + Filename.concat cwd fname + else + fname in + let rec loop paths roots sources classes = + try + let lexbuf = Lexing.from_string (input_line file_in) in + match JVerboseParser.line JVerboseLexer.token lexbuf with + | JVerbose.Source fname -> + let source_file = java_source_file_from_path (convert_filename fname) in + loop paths roots (StringMap.add (Filename.basename fname) source_file sources) classes + | JVerbose.Class fname -> + let cn, root_info = Javalib.extract_class_name_from_file fname in + let root_dir = if root_info = "" then Filename.current_dir_name else root_info in + let updated_roots = + if list_exists (fun p -> p = root_dir) roots then roots + else root_dir:: roots in + loop paths updated_roots sources (JBasics.ClassSet.add cn classes) + | JVerbose.Classpath parsed_paths -> + loop parsed_paths roots sources classes + with + | JBasics.Class_structure_error _ + | Parsing.Parse_error + | Failure "lexing: empty token" -> loop paths roots sources classes + | End_of_file -> + close_in file_in; + let classpath = list_fold_left append_path "" (roots @ (add_android_jar paths)) in + (classpath, sources, classes) in + loop [] [] StringMap.empty JBasics.ClassSet.empty + + +type classmap = JCode.jcode Javalib.interface_or_class JBasics.ClassMap.t + + +type program = { + classpath: Javalib.class_path; + models: classmap; + mutable classmap: classmap +} + + +let get_classmap program = + program.classmap + + +let get_classpath program = + program.classpath + + +let get_models program = + program.models + + +let add_class cn jclass program = + program.classmap <- JBasics.ClassMap.add cn jclass program.classmap + + +let lookup_node cn (program: program) = + try + Some (JBasics.ClassMap.find cn (get_classmap program)) + with Not_found -> + try + let jclass = Javalib.get_class (get_classpath program) cn in + add_class cn jclass program; + Some jclass + with + | JBasics.No_class_found _ + | JBasics.Class_structure_error _ + | Invalid_argument _ -> None + + +let classname_of_class_filename class_filename = + let parts = Str.split (Str.regexp "/") class_filename in + let classname_str = + if list_length parts > 1 then + list_fold_left (fun s p -> s^"."^p) (list_hd parts) (list_tl parts) + else + list_hd parts in + JBasics.make_cn classname_str + + +let extract_classnames classnames jar_filename = + let file_in = Zip.open_in jar_filename in + let collect classes entry = + let class_filename = entry.Zip.filename in + try + let () = ignore (Str.search_forward (Str.regexp "class") class_filename 0) in + (classname_of_class_filename (Filename.chop_extension class_filename):: classes) + with Not_found -> classes in + let classnames_after = list_fold_left collect classnames (Zip.entries file_in) in + Zip.close_in file_in; + classnames_after + + +let collect_classes classmap jar_filename = + let classpath = Javalib.class_path jar_filename in + let collect classmap cn = + JBasics.ClassMap.add cn (Javalib.get_class classpath cn) classmap in + list_fold_left collect classmap (extract_classnames [] jar_filename) + + +let classmap_of_classpath classpath = + let jar_filenames = + list_filter (fun p -> not (Sys.is_directory p)) (split_classpath classpath) in + list_fold_left collect_classes JBasics.ClassMap.empty jar_filenames + + +let load_program classpath classes arg_source_files = + JUtils.log "loading program ... %!"; + let models = + if !models_jar = "" then JBasics.ClassMap.empty + else collect_classes JBasics.ClassMap.empty !models_jar in + let program = { + classpath = Javalib.class_path classpath; + models = models; + classmap = JBasics.ClassMap.empty + } in + JBasics.ClassSet.iter + (fun cn -> ignore (lookup_node cn program)) + classes; + JUtils.log "done@."; + program diff --git a/infer/src/java/jClasspath.mli b/infer/src/java/jClasspath.mli new file mode 100644 index 000000000..2d3f5bc2f --- /dev/null +++ b/infer/src/java/jClasspath.mli @@ -0,0 +1,39 @@ +open Javalib_pack +open Sawja_pack + +(** Jar file containing the models *) +val models_jar : string ref + +(** Adds the set of procnames for the models of Java libraries so that methods with similar names are skipped during the capture *) +val add_models : string -> unit + +(** Check if there is a model for the given procname *) +val is_model : Procname.t -> bool + +val set_verbose_out: string -> unit + +(** create a source file from an absolute path. Source files are relative if the project root is specified and absolute otherwise *) +val java_source_file_from_path : string -> DB.source_file + +val split_classpath : string -> string list + +(** load the list of source files and the list of classes from the javac verbose file *) +val load_sources_and_classes : unit -> +string * DB.source_file Utils.StringMap.t * JBasics.ClassSet.t + +type classmap = JCode.jcode Javalib.interface_or_class JBasics.ClassMap.t + +type program + +val get_classmap : program -> classmap + +val get_models : program -> classmap + +(** load a java program *) +val load_program : string -> JBasics.ClassSet.t -> DB.source_file Utils.StringMap.t -> program + +(** retrive a Java node from the classname *) +val lookup_node : JBasics.class_name -> program -> JCode.jcode Javalib.interface_or_class option + +(** [collect_classes cmap filename] adds to [cmap] the classes found in the jar file [filename] *) +val collect_classes : classmap -> string -> classmap diff --git a/infer/src/java/jConfig.ml b/infer/src/java/jConfig.ml new file mode 100644 index 000000000..b0753f020 --- /dev/null +++ b/infer/src/java/jConfig.ml @@ -0,0 +1,140 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +open Javalib_pack + +(** {2 Class names and types} *) +let infer_builtins_cl = JBasics.make_cn "com.facebook.infer.models.InferBuiltins" + +let infer_array_cl = "com.facebook.infer.models.InferArray" + +let infer_undefined_cl = "com.facebook.infer.models.InferUndefined" + +let infer_object_cl = "com.facebook.infer.models.InferObject" + +let object_cl = "java.lang.Object" + +let java_lang_object_classname = Mangled.from_string object_cl + +let obj_type = (JBasics.TObject (JBasics.TClass (JBasics.make_cn object_cl))) + +let bool_type = JBasics.TBasic `Bool + +let string_cl = "java.lang.String" + +let class_cl = "java.lang.Class" + +let npe_cl = "java.lang.NullPointerException" + +let cce_cl = "java.lang.ClassCastException" + +let out_of_bound_cl = "java.lang.ArrayIndexOutOfBoundsException" + +let reentrant_lock_cl = "java.util.concurrent.locks.ReentrantLock" + +let lock_cl = "java.util.concurrent.locks.Lock" + +let reentrant_rwlock_cl = "java.util.concurrent.locks.ReentrantReadWriteLock" + +let reentrant_rlock_cl = reentrant_rwlock_cl^"$ReadLock" + +let reentrant_wlock_cl = reentrant_rwlock_cl^"$WriteLock" + +let thread_class = "java.lang.Thread" + +let runnable_if = "java.lang.Runnable" + +let lock_method = "lock" + +let unlock_method = "unlock" + +let try_lock_method = "tryLock" + +let start_method = "start" + +let run_method = "run" + +(** {2 Names of special variables, constants and method names} *) + +let this = "this" + +let constructor_name = "" + +let clone_name = "clone" + +let field_st = "field" + +let field_cst = "" + +(** {2 Names of primitive types} *) + +let void = "void" + +let boolean_st = "boolean" + +let byte_st = "byte" + +let char_st = "char" + +let double_st = "double" + +let float_st = "float" + +let int_st = "int" + +let long_st = "long" + +let short_st = "short" + +(** {2 Encoding of primitive types when they are the element type of arrays } *) + +let boolean_code = "Z" + +let byte_code = "B" + +let char_code = "C" + +let double_code = "D" + +let float_code = "F" + +let int_code = "I" + +let long_code = "J" + +let short_code = "S" + +let class_code cl = "L"^cl + +let errors_db_file = "errors.db" +let main_errors_file = "Java_frontend.errors" + +(** {2 Flags } *) + +(* the Sawja representation of the Java Bytecode will be printed *) +let html_mode = ref false + +(* debug information will be printed *) +let debug_mode = ref false + +(* The classes in the given jar file will be translated. No sources needed *) +let dependency_mode = ref false + +(* The dynamic dispatch will be handled partially statically *) +let static_dispatch = ref false + +(* counts the amount of initializer methods, in init-mode *) +let init_count = ref 0 + +let normalize_string s = + let rexp = Str.regexp_string "$" in + Str.global_replace rexp "_" s + +(* Translate the safety checks doen by the JVM *) +let translate_checks = ref false + +(* Generate harness for Android code *) +let create_harness = true diff --git a/infer/src/java/jContext.ml b/infer/src/java/jContext.ml new file mode 100644 index 000000000..36bbfc268 --- /dev/null +++ b/infer/src/java/jContext.ml @@ -0,0 +1,134 @@ +open Javalib_pack +open Sawja_pack + +module NodeTbl = Cfg.NodeHash + +type jump_kind = + | Next + | Jump of int + | Exit + +type meth_kind = + | Normal + | Init + +(** data *) +type icfg = { + tenv : Sil.tenv; + cg : Cg.t; + cfg : Cfg.cfg; +} + +type t = + { icfg : icfg; + procdesc : Cfg.Procdesc.t; + impl : JBir.t; + mutable var_map : (Sil.pvar * Sil.typ * Sil.typ) JBir.VarMap.t; + if_jumps : int NodeTbl.t; + goto_jumps : (int, jump_kind) Hashtbl.t; + cn : JBasics.class_name; + meth_kind : meth_kind; + node : JCode.jcode Javalib.interface_or_class; + program : JClasspath.program; + never_null_matcher: Inferconfig.NeverReturnNull.matcher; + } + +let create_context never_null_matcher icfg procdesc impl cn meth_kind node program = + { icfg = icfg; + procdesc = procdesc; + impl = impl; + var_map = JBir.VarMap.empty; + if_jumps = NodeTbl.create 10; + goto_jumps = Hashtbl.create 10; + cn = cn; + meth_kind = meth_kind; + node = node; + program = program; + never_null_matcher = never_null_matcher; + } + +let get_icfg context = context.icfg +let get_cfg context = context.icfg.cfg +let get_cg context = context.icfg.cg +let get_tenv context = context.icfg.tenv +let get_procdesc context = context.procdesc +let get_cn context = context.cn +let get_node context = context.node +let get_program context = context.program +let get_impl context = context.impl +let get_var_map context = context.var_map +let set_var_map context var_map = context.var_map <- var_map +let get_meth_kind context = context.meth_kind +let get_never_null_matcher context = context.never_null_matcher + +let get_or_set_pvar_type context var typ = + let var_map = get_var_map context in + try + let (pvar, otyp, _) = (JBir.VarMap.find var var_map) in + let tenv = get_tenv context in + if Prover.check_subtype tenv typ otyp || (Prover.check_subtype tenv otyp typ) then + set_var_map context (JBir.VarMap.add var (pvar, otyp, typ) var_map) + else set_var_map context (JBir.VarMap.add var (pvar, typ, typ) var_map); + (pvar, typ) + with Not_found -> + let procname = (Cfg.Procdesc.get_proc_name (get_procdesc context)) in + let varname = Mangled.from_string (JBir.var_name_g var) in + let pvar = Sil.mk_pvar varname procname in + set_var_map context (JBir.VarMap.add var (pvar, typ, typ) var_map); + (pvar, typ) + +let lookup_pvar_type context var typ = (get_or_set_pvar_type context var typ) + +let set_pvar context var typ = fst (get_or_set_pvar_type context var typ) + +let reset_pvar_type context = + let var_map = get_var_map context in + let aux var item = + match item with (pvar, otyp, typ) -> + set_var_map context (JBir.VarMap.add var (pvar, otyp, otyp) var_map) in + JBir.VarMap.iter aux var_map + +let get_var_type context var = + try + let (_, otyp', otyp) = JBir.VarMap.find var (get_var_map context) in + Some otyp + with Not_found -> None + +let get_if_jumps context = context.if_jumps +let get_goto_jumps context = context.goto_jumps + +let add_if_jump context node pc = + NodeTbl.add (get_if_jumps context) node pc + +let get_if_jump context node = + try + Some (NodeTbl.find (get_if_jumps context) node) + with Not_found -> None + +let add_goto_jump context pc jump = + Hashtbl.add (get_goto_jumps context) pc jump + +let get_goto_jump context pc = + try + Hashtbl.find (get_goto_jumps context) pc + with Not_found -> Next + +let is_goto_jump context pc = + try + match Hashtbl.find (get_goto_jumps context) pc with + | Jump _ -> true + | _ -> false + with Not_found -> false + +let exn_node_table = Procname.Hash.create 100 + +let reset_exn_node_table () = + Procname.Hash.clear exn_node_table + +let add_exn_node procname (exn_node : Cfg.Node.t) = + Procname.Hash.add exn_node_table procname exn_node + +let get_exn_node procdesc = + try + Some (Procname.Hash.find exn_node_table (Cfg.Procdesc.get_proc_name procdesc)) + with Not_found -> None diff --git a/infer/src/java/jContext.mli b/infer/src/java/jContext.mli new file mode 100644 index 000000000..3d3686ace --- /dev/null +++ b/infer/src/java/jContext.mli @@ -0,0 +1,114 @@ +open Javalib_pack +open Sawja_pack + +(** data structure for representing whether an instruction is a goto, a return or a standard instruction. *) +type jump_kind = + | Next + | Jump of int + | Exit + + +(** data structure for identifying whether a method is the initialiser of a class - that initialises the static fields- or a standard method *) +type meth_kind = + | Normal + | Init + +(** Hastable for storing nodes that correspond to if-instructions. These are +used when adding the edges in the contrl flow graph. *) +module NodeTbl : Hashtbl.S with type key = Cfg.Node.t + + +(** data structure for saving the three structures tht contain the intermediate +representation of a file: the type environment, the control graph and the control +flow graph *) +type icfg = { + tenv : Sil.tenv; + cg : Cg.t; + cfg : Cfg.cfg; +} + +(** data structure for storing the context elements. *) +type t + + +(** cretes a context for a given method. *) +val create_context : +Inferconfig.NeverReturnNull.matcher -> +icfg -> +Cfg.Procdesc.t -> +JBir.t -> +JBasics.class_name -> +meth_kind -> +JCode.jcode Javalib.interface_or_class -> +JClasspath.program -> +t + +(** returns the intermediate representation of the Java file from the context. *) +val get_icfg : t -> icfg + +(** returns the code of the method from the context *) +val get_impl : t -> JBir.t + +(** returns the class where the method is defined from the context *) +val get_cn : t -> JBasics.class_name + +(** returns the type environment that corresponds to the current file. *) +val get_tenv : t -> Sil.tenv + +(** returns the control graph that corresponds to the current file. *) +val get_cg : t -> Cg.t + +(** returns the control flow graph that corresponds to the current file. *) +val get_cfg : t -> Cfg.cfg + +(** returns the procedure description in the intermediate language for the +current method. *) +val get_procdesc : t -> Cfg.Procdesc.t + +(** returns the method kind of the current method: standard or initialiser of +static fields. *) +val get_meth_kind : t -> meth_kind + +(** adds to the context the line that an if-node will jump to *) +val add_if_jump : t -> Cfg.Node.t -> int -> unit + +(** returns whether the given node corresponds to an if-instruction *) +val get_if_jump : t -> Cfg.Node.t -> int option + +(** adds to the context the line that the node in the given line will jump to. *) +val add_goto_jump : t -> int -> jump_kind -> unit + +(** if the given line corresponds to a goto instruction, then returns the +line where it jumps to, otherwise returns the next line. *) +val get_goto_jump : t -> int -> jump_kind + +(** returns whether the given line corresponds to a goto instruction. *) +val is_goto_jump : t -> int -> bool + +(** returns the Java program *) +val get_program : t -> JClasspath.program + +(** returns the current node *) +val get_node : t -> JCode.jcode Javalib.interface_or_class + +(** returns a match function for procedures that are never returning null +according to .inferfonfig *) +val get_never_null_matcher : t -> Inferconfig.NeverReturnNull.matcher + +(** [set_pvar context var type] adds a variable with a type to the context *) +val set_pvar : t -> JBir.var -> Sil.typ -> Sil.pvar + +(** [get_var_type context var] returns the type of the variable, if the variable is in the context *) +val get_var_type : t -> JBir.var -> Sil.typ option + +(** resets the dynamic type of the variables in the context. *) +val reset_pvar_type : t -> unit + +(** resets the hashtable mapping methods to their exception nodes *) +val reset_exn_node_table : unit -> unit + +(** adds the exception node for a given method *) +val add_exn_node : Procname.Hash.key -> Cfg.Node.t -> unit + +(** returns the exception node of a given method *) +val get_exn_node : Cfg.Procdesc.t -> Cfg.Node.t option diff --git a/infer/src/java/jFrontend.ml b/infer/src/java/jFrontend.ml new file mode 100644 index 000000000..127920058 --- /dev/null +++ b/infer/src/java/jFrontend.ml @@ -0,0 +1,232 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +open Javalib_pack +open Sawja_pack + +open Utils + + +module L = Logging + + +let add_edges context start_node exn_node exit_nodes method_body_nodes impl super_call = + let pc_nb = Array.length method_body_nodes in + let last_pc = pc_nb - 1 in + let is_last pc = (pc = last_pc) in + let rec get_body_nodes pc = + let current_nodes = method_body_nodes.(pc) in + match current_nodes with + | JTrans.Skip when (is_last pc) && not (JContext.is_goto_jump context pc) -> + exit_nodes + | JTrans.Skip -> direct_successors pc + | JTrans.Instr node -> [node] + | JTrans.Prune (node_true, node_false) -> [node_true; node_false] + | JTrans.Loop (join_node, _, _) -> [join_node] + and direct_successors pc = + if is_last pc && not (JContext.is_goto_jump context pc) then + exit_nodes + else + match JContext.get_goto_jump context pc with + | JContext.Next -> get_body_nodes (pc + 1) + | JContext.Jump goto_pc -> + if pc = goto_pc then [] (* loop in goto *) + else get_body_nodes goto_pc + | JContext.Exit -> exit_nodes in + let get_succ_nodes node pc = + match JContext.get_if_jump context node with + | None -> direct_successors pc + | Some jump_pc -> get_body_nodes jump_pc in + let get_exn_nodes = + if super_call then (fun x -> exit_nodes) + else JTransExn.create_exception_handlers context [exn_node] get_body_nodes impl in + let connect node pc = + Cfg.Node.set_succs_exn node (get_succ_nodes node pc) (get_exn_nodes pc) in + let connect_nodes pc translated_instruction = + match translated_instruction with + | JTrans.Skip -> () + | JTrans.Instr node -> connect node pc + | JTrans.Prune (node_true, node_false) -> + connect node_true pc; + connect node_false pc + | JTrans.Loop (join_node, node_true, node_false) -> + Cfg.Node.set_succs_exn join_node [node_true; node_false] []; + connect node_true pc; + connect node_false pc in + let first_nodes = direct_successors (-1) in (* deals with the case of an empty array *) + Cfg.Node.set_succs_exn start_node first_nodes exit_nodes; (* the exceptions edges here are going directly to the exit node *) + if not super_call then + Cfg.Node.set_succs_exn exn_node exit_nodes exit_nodes; (* the exceptions node is just before the exit node *) + Array.iteri connect_nodes method_body_nodes + +(** Add a concrete method. *) +let add_cmethod never_null_matcher program icfg node cm is_static = + let cfg = icfg.JContext.cfg in + let tenv = icfg.JContext.tenv in + let cn, ms = JBasics.cms_split cm.Javalib.cm_class_method_signature in + let is_clinit = JBasics.ms_equal ms JBasics.clinit_signature in + if !JTrans.no_static_final = false + && is_clinit + && not (JTransStaticField.has_static_final_fields node) then + JUtils.log "\t\tskipping class initializer: %s@." (JBasics.ms_name ms) + else + match JTrans.get_method_procdesc program cfg tenv cn ms is_static with + | JTrans.Defined procdesc when JClasspath.is_model (Cfg.Procdesc.get_proc_name procdesc) -> + (* do not capture the method if there is a model for it *) + JUtils.log "Skipping method with a model: %s@." (Procname.to_string (Cfg.Procdesc.get_proc_name procdesc)); + | JTrans.Defined procdesc -> + let start_node = Cfg.Procdesc.get_start_node procdesc in + let exit_node = Cfg.Procdesc.get_exit_node procdesc in + let exn_node = + match JContext.get_exn_node procdesc with + | Some node -> node + | None -> assert false in + let impl = JTrans.get_implementation cm in + let instrs, meth_kind = + if is_clinit && not !JTrans.no_static_final then + let instrs = JTransStaticField.static_field_init node cn (JBir.code impl) in + (instrs, JContext.Init) + else (JBir.code impl), JContext.Normal in + let context = + JContext.create_context + never_null_matcher icfg procdesc impl cn meth_kind node program in + let method_body_nodes = Array.mapi (JTrans.instruction context) instrs in + let procname = Cfg.Procdesc.get_proc_name procdesc in + add_edges context start_node exn_node [exit_node] method_body_nodes impl false; + Cg.add_node icfg.JContext.cg procname; + if Procname.is_constructor procname then Cfg.set_procname_priority cfg procname + | JTrans.Called _ -> () + + +(** Add an abstract method. *) +let add_amethod program icfg node am is_static = + let cfg = icfg.JContext.cfg in + let tenv = icfg.JContext.tenv in + let cn, ms = JBasics.cms_split am.Javalib.am_class_method_signature in + match JTrans.get_method_procdesc program cfg tenv cn ms is_static with + | JTrans.Defined procdesc when (JClasspath.is_model (Cfg.Procdesc.get_proc_name procdesc)) -> + (* do not capture the method if there is a model for it *) + JUtils.log "Skipping method with a model: %s@." (Procname.to_string (Cfg.Procdesc.get_proc_name procdesc)); + | JTrans.Defined procdesc -> + Cg.add_node icfg.JContext.cg (Cfg.Procdesc.get_proc_name procdesc) + | JTrans.Called _ -> () + + +let path_of_cached_classname cn = + let root_path = Filename.concat !Config.results_dir "classnames" in + let package_path = list_fold_left Filename.concat root_path (JBasics.cn_package cn) in + Filename.concat package_path ((JBasics.cn_simple_name cn)^".java") + + +let cache_classname cn = + let path = path_of_cached_classname cn in + let splitted_root_dir = + let rec split l p = + match p with + | p when p = Filename.current_dir_name -> l + | p when p = Filename.dir_sep -> l + | p -> split ((Filename.basename p):: l) (Filename.dirname p) in + split [] (Filename.dirname path) in + let rec mkdir l p = + let () = + if not (Sys.file_exists p) then + Unix.mkdir p 493 in + match l with + | [] -> () + | d:: tl -> mkdir tl (Filename.concat p d) in + mkdir splitted_root_dir Filename.dir_sep; + let file_out = open_out(path) in + output_string file_out (string_of_float (Unix.time ())); + close_out file_out + +let is_classname_cached cn = + Sys.file_exists (path_of_cached_classname cn) + +(* Given a source file and a class, translates the code of this class. +In init - mode, finds out whether this class contains initializers at all, +in this case translates it. In standard mode, all methods are translated *) +let create_icfg never_null_matcher linereader program icfg source_file cn node = + JUtils.log "\tclassname: %s@." (JBasics.cn_name cn); + cache_classname cn; + let cfg = icfg.JContext.cfg in + let tenv = icfg.JContext.tenv in + begin + Javalib.m_iter (JTrans.create_local_procdesc program linereader cfg tenv node) node; + Javalib.m_iter (fun m -> + let method_kind = JTrans.get_method_kind m in + match m with + | Javalib.ConcreteMethod cm -> + add_cmethod never_null_matcher program icfg node cm method_kind + | Javalib.AbstractMethod am -> + add_amethod program icfg node am method_kind + ) node + end + +(* +This type definition is for a future improvement of the capture where in one pass, the frontend will +translate things differently whether a source file is found for a given class +*) +type capture_status = + | With_source of string + | Library of string + | Unknown + +(* returns true for the set of classes that are selected to be translated *) +let should_capture classes source_basename node = + let classname = Javalib.get_name node in + let temporary_skip = + (* TODO (#6341744): remove this *) + list_exists + (fun part -> part = "graphschema") + (JBasics.cn_package classname) in + if JBasics.ClassSet.mem classname classes && not temporary_skip then + begin + match Javalib.get_sourcefile node with + | None -> false + | Some source -> source = source_basename + end + else false + + +(* Computes the control - flow graph and call - graph of a given source file. +In the standard - mode, it translated all the classes that correspond to this +source file. *) +let compute_source_icfg + never_null_matcher linereader classes program tenv source_basename source_file = + let icfg = + { JContext.cg = Cg.create (); + JContext.cfg = Cfg.Node.create_cfg (); + JContext.tenv = tenv } in + let select test procedure cn node = + (* let () = JUtils.log "translating: %s@." (JBasics.cn_name (JProgram.get_name node)) in *) + if test node then + try + procedure cn node + with + | Bir.Subroutine -> () + | e -> raise e in + let () = + JBasics.ClassMap.iter + (select + (should_capture classes source_basename) + (create_icfg never_null_matcher linereader program icfg source_file)) + (JClasspath.get_classmap program) in + (icfg.JContext.cg, icfg.JContext.cfg) + +let compute_class_icfg never_null_matcher linereader program tenv node fake_source_file = + let icfg = + { JContext.cg = Cg.create (); + JContext.cfg = Cfg.Node.create_cfg (); + JContext.tenv = tenv } in + begin + try + create_icfg + never_null_matcher linereader program icfg fake_source_file (Javalib.get_name node) node + with + | Bir.Subroutine -> () + | e -> raise e + end; + (icfg.JContext.cg, icfg.JContext.cfg) diff --git a/infer/src/java/jFrontend.mli b/infer/src/java/jFrontend.mli new file mode 100644 index 000000000..33dcf0a8e --- /dev/null +++ b/infer/src/java/jFrontend.mli @@ -0,0 +1,38 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +open Javalib_pack +open Sawja_pack + +(** [path_of_cached_classname cn] returns the path of a cached classname *) +val path_of_cached_classname : JBasics.class_name -> string + +(** [cache_classname cn] stores the classname to the disk *) +val cache_classname : JBasics.class_name -> unit + +(** [is_classname_cached cn] *) +val is_classname_cached : JBasics.class_name -> bool + +(** [compute_icfg linereader classes program tenv source_basename source_file] create the call graph and control flow graph for the file [source_file] by translating all the classes in [program] originating from [source_file] *) +val compute_source_icfg : +Inferconfig.NeverReturnNull.matcher -> +Printer.LineReader.t -> +JBasics.ClassSet.t -> +JClasspath.program -> +Sil.tenv -> +string -> +DB.source_file -> +Cg.t * Cfg.cfg + +(** Compute the CFG for a class *) +val compute_class_icfg : +Inferconfig.NeverReturnNull.matcher -> +Printer.LineReader.t -> +JClasspath.program -> +Sil.tenv -> +JCode.jcode Javalib.interface_or_class -> +DB.source_file -> +Cg.t * Cfg.cfg diff --git a/infer/src/java/jMain.ml b/infer/src/java/jMain.ml new file mode 100644 index 000000000..d65015bc9 --- /dev/null +++ b/infer/src/java/jMain.ml @@ -0,0 +1,169 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +open Javalib_pack + +module L = Logging +open Utils + +let arg_desc = + let base_arg = + let options_to_keep = ["-results_dir"; "-project_root"] in + let filter arg_desc = + list_filter (fun desc -> let (option_name, _, _, _) = desc in list_mem string_equal option_name options_to_keep) arg_desc in + let desc = + (filter base_arg_desc) @ + [ + "-models", Arg.String (fun filename -> JClasspath.add_models filename), Some "paths", "set the path to the jar containing the models"; + "-debug", Arg.Unit (fun () -> JConfig.debug_mode := true), None, "write extra translation information"; + "-dependencies", Arg.Unit (fun _ -> JConfig.dependency_mode := true), None, "translate all the dependencies during the capture"; + "-no-static_final", Arg.Unit (fun () -> JTrans.no_static_final := true), None, "no special treatment for static final fields"; + "-tracing", Arg.Unit (fun () -> JConfig.translate_checks := true), None, + "Translate JVM checks"; + "-verbose_out", Arg.String (fun path -> JClasspath.set_verbose_out path), None, + "Set the path to the javac verbose output" + ] in + Arg2.create_options_desc false "Parsing Options" desc in + base_arg + +let usage = + "Usage: InferJava -d compilation_dir -sources filename\n" + +let print_usage_exit () = + Arg2.usage arg_desc usage; + exit(1) + +let () = + let analysing_models = Config.from_env_variable "ANALYZE_MODELS" in + Arg2.parse arg_desc (fun arg -> ()) usage; + if analysing_models && !JClasspath.models_jar <> "" then + failwith "Not expecting model file when analyzing the models"; + if not analysing_models && !JClasspath.models_jar = "" then + failwith "Java model file is required" + + +let init_global_state source_file = + Sil.curr_language := Sil.Java; + DB.current_source := source_file; + DB.Results_dir.init (); + Ident.reset_name_generator (); + SymOp.reset_total (); + JContext.reset_exn_node_table (); + let nLOC = FileLOC.file_get_loc (DB.source_file_to_string source_file) in + Config.nLOC := nLOC + + +let store_icfg tenv cg cfg source_file = + let source_dir = DB.source_dir_from_source_file !DB.current_source in + begin + let cfg_file = DB.source_dir_get_internal_file source_dir ".cfg" in + let cg_file = DB.source_dir_get_internal_file source_dir ".cg" in + Cfg.add_removetemps_instructions cfg; + Preanal.doit cfg tenv; + Cfg.add_abstraction_instructions cfg; + Cg.store_to_file cg_file cg; + Cfg.store_cfg_to_file cfg_file true cfg; + if !JConfig.debug_mode then + begin + Config.write_dotty := true; + Config.print_types := true; + Dotty.print_icfg_dotty cfg []; + Cg.save_call_graph_dotty None Specs.get_specs cg + end + end + + +(* Given a source file, its code is translated, and the call-graph, control-flow-graph and type *) +(* environment are obtained and saved. *) +let do_source_file + never_null_matcher linereader classes program tenv source_basename source_file proc_file_map = + JUtils.log "\nfilename: %s (%s)@." + (DB.source_file_to_string source_file) source_basename; + init_global_state source_file; + let call_graph, cfg = + JFrontend.compute_source_icfg + never_null_matcher linereader classes program tenv source_basename source_file in + store_icfg tenv call_graph cfg source_file; + if JConfig.create_harness then + list_fold_left + (fun proc_file_map pdesc -> + Procname.Map.add (Cfg.Procdesc.get_proc_name pdesc) source_file proc_file_map) + proc_file_map (Cfg.get_all_procs cfg) + else proc_file_map + + +let capture_libs never_null_matcher linereader program tenv = + let capture_class tenv cn node = + match node with + | Javalib.JInterface _ -> () + | Javalib.JClass _ when JFrontend.is_classname_cached cn -> () + | Javalib.JClass _ -> + begin + let fake_source_file = JClasspath.java_source_file_from_path (JFrontend.path_of_cached_classname cn) in + init_global_state fake_source_file; + let call_graph, cfg = + JFrontend.compute_class_icfg + never_null_matcher linereader program tenv node fake_source_file in + store_icfg tenv call_graph cfg fake_source_file; + JFrontend.cache_classname cn; + end in + JBasics.ClassMap.iter (capture_class tenv) (JClasspath.get_classmap program) + + +(* load a stored global tenv if the file is found, and create a new one otherwise *) +let load_tenv program = + let tenv_filename = DB.global_tenv_fname () in + let tenv = + if DB.file_exists tenv_filename then + begin + match Sil.load_tenv_from_file tenv_filename with + | None -> Sil.create_tenv () + | Some tenv -> tenv + end + else + Sil.create_tenv () in + JTransType.update_tenv tenv program; + tenv + + +(* Store to a file the type environment containing all the types required to perform the analysis *) +let save_tenv classpath tenv = + JTransType.saturate_tenv_with_classpath classpath tenv; + let tenv_filename = DB.global_tenv_fname () in + (* TODO: this prevents per compilation step incremental analysis at this stage *) + if DB.file_exists tenv_filename then DB.file_remove tenv_filename; + JUtils.log "writing new tenv %s@." (DB.filename_to_string tenv_filename); + Sil.store_tenv_to_file tenv_filename tenv + + +(* The program is loaded and translated *) +let do_all_files classpath sources classes = + JUtils.log "Translating %d source files (%d classes)@." + (StringMap.cardinal sources) + (JBasics.ClassSet.cardinal classes); + let program = JClasspath.load_program classpath classes sources in + let tenv = load_tenv program in + let linereader = Printer.LineReader.create () in + let never_null_matcher = Inferconfig.NeverReturnNull.load_matcher Sil.Java in + let proc_file_map = + StringMap.fold + (do_source_file never_null_matcher linereader classes program tenv) + sources + Procname.Map.empty in + if !JConfig.dependency_mode then + capture_libs never_null_matcher linereader program tenv; + if JConfig.create_harness then Harness.create_harness proc_file_map tenv; + save_tenv classpath tenv; + JUtils.log "done @." + + +(* loads the source files and translates them *) +let () = + let classpath, sources, classes = JClasspath.load_sources_and_classes () in + if StringMap.is_empty sources then + print_endline "TODO: print error message" + else + do_all_files classpath sources classes diff --git a/infer/src/java/jTrans.ml b/infer/src/java/jTrans.ml new file mode 100644 index 000000000..5bb7f64b1 --- /dev/null +++ b/infer/src/java/jTrans.ml @@ -0,0 +1,1162 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +open Javalib_pack +open Sawja_pack + + +open Utils +module L = Logging + +type method_kind = + | Static + | Non_Static + +type invoke_kind = + | I_Virtual + | I_Interface + | I_Special + | I_Static + +exception Frontend_error of string + +let constr_loc_map : Sil.location JBasics.ClassMap.t ref = ref JBasics.ClassMap.empty + +let init_loc_map : Sil.location JBasics.ClassMap.t ref = ref JBasics.ClassMap.empty + +let get_method_kind m = if Javalib.is_static_method m then Static else Non_Static + +(** Fix the line associated to a method definition. +Since Sawja often reports a method off by a few lines, we search +backwards for a line where the method name is. *) +let fix_method_definition_line linereader proc_name loc = + let method_name = + let raw = Procname.java_get_method proc_name in + if raw = "" + then + let inner_class_name cname = snd (string_split_character cname '$') in + inner_class_name (Procname.java_get_simple_class proc_name) + else raw in + let regex = Str.regexp (Str.quote method_name) in + let method_is_defined_here linenum = + match Printer.LineReader.from_file_linenum_original linereader loc.Sil.file linenum with + | None -> raise Not_found + | Some line -> + (try ignore (Str.search_forward regex line 0); true + with Not_found -> false) in + let line = ref loc.Sil.line in + try + while not (method_is_defined_here !line) do + line := !line -1; + if !line < 0 then raise Not_found + done; + { loc with Sil.line = !line } + with Not_found -> loc + +let get_location impl pc meth_kind cn = + if meth_kind = JContext.Init then + try + JBasics.ClassMap.find cn !init_loc_map + with Not_found -> Sil.dummy_location + else + let line_number = + let ln = + try JBir.get_source_line_number pc impl + with Invalid_argument e -> None in + match ln with + | None -> 0 + | Some n -> n in + { Sil.line = line_number; Sil.col = -1; Sil.file = !DB.current_source; Sil.nLOC = !Config.nLOC } + +let get_undefined_method_call ovt = + let get_undefined_method ovt = + match ovt with + | None -> JConfig.void^"_undefined" + | Some vt -> + match vt with + | JBasics.TBasic bt -> (JTransType.string_of_basic_type bt)^"_undefined" + | JBasics.TObject ot -> + begin + match ot with + | JBasics.TArray vt -> assert false + | JBasics.TClass cn -> + if JBasics.cn_name cn = JConfig.string_cl then + "string_undefined" + else + if JBasics.cn_name cn = JConfig.object_cl then + "object_undefined" + else assert false + end in + let undef_cn = JBasics.make_cn JConfig.infer_undefined_cl in + let undef_name = get_undefined_method ovt in + let undef_ms = JBasics.make_ms undef_name [] ovt in + (undef_cn, undef_ms) + + +let retrieve_fieldname fieldname = + try + let subs = Str.split (Str.regexp (Str.quote ".")) (Ident.fieldname_to_string fieldname) in + if list_length subs = 0 then + assert false + else + list_hd (list_rev subs) + with hd -> assert false + + +let get_field_name program static tenv cn fs context = + match JTransType.get_class_type_no_pointer program tenv cn with + | Sil.Tstruct (fields, sfields, Sil.Class, _, _, _, _) -> + let fieldname, _, _ = + try + list_find + (fun (fieldname, _, _) -> retrieve_fieldname fieldname = JBasics.fs_name fs) + (if static then sfields else fields) + with Not_found -> + (* TODO: understand why fields cannot be found here *) + JUtils.log "cannot find %s.%s@." (JBasics.cn_name cn) (JBasics.fs_name fs); + raise (Frontend_error "Cannot find fieldname") in + fieldname + | _ -> assert false + + +let formals_from_signature program tenv cn ms is_static = + let counter = ref 0 in + let method_name = JBasics.ms_name ms in + let get_arg_name () = + let arg = method_name^"_arg_"^(string_of_int !counter) in + incr counter; + arg in + let collect l vt = + let arg_name = get_arg_name () in + let arg_type = JTransType.value_type program tenv vt in + (arg_name, arg_type):: l in + let init_arg_list = match is_static with + | Static -> [] + | Non_Static -> [(JConfig.this, JTransType.get_class_type program tenv cn)] in + list_rev (list_fold_left collect init_arg_list (JBasics.ms_args ms)) + +let formals program tenv cn impl = + let collect l (vt, var) = + let name = JBir.var_name_g var in + let typ = JTransType.param_type program tenv cn var vt in + (name, typ):: l in + list_rev (list_fold_left collect [] (JBir.params impl)) + +(** Creates the local and formal variables from a procedure based on the +impl argument. If the meth_kind is Init, we add a parameter field to +the initialiser method. *) +let locals_formals program tenv cn impl meth_kind = + let form_list = + if meth_kind = JContext.Init then + let string_type = (JTransType.get_class_type program tenv (JBasics.make_cn JConfig.string_cl)) in + [(JConfig.field_st, string_type) ] + else formals program tenv cn impl in + let is_formal v = + let v = Mangled.to_string v in + list_exists (fun (v', _) -> Utils.string_equal v v') form_list in + let collect l var = + let vname = Mangled.from_string (JBir.var_name_g var) in + let names = (fst (list_split l)) in + if not (is_formal vname) && (not (list_mem Mangled.equal vname names)) then + (vname, Sil.Tvoid):: l + else + l in + let vars = JBir.vars impl in + let loc_list = list_rev (Array.fold_left collect [] vars) in + (loc_list, form_list) + +let get_constant (c : JBir.const) = + match c with + | `Int i -> Sil.Cint (Sil.Int.of_int32 i) + | `ANull -> Sil.Cint Sil.Int.null + | `Class ot -> Sil.Cclass (Ident.string_to_name (JTransType.object_type_to_string ot)) + | `Double f -> Sil.Cfloat f + | `Float f -> Sil.Cfloat f + | `Long i64 -> Sil.Cint (Sil.Int.of_int64 i64) + | `String jstr -> Sil.Cstr (JBasics.jstr_pp jstr) + +let static_field_name cn fs = + let classname = JBasics.cn_name cn in + let fieldname = JBasics.fs_name fs in + Mangled.from_string (classname^"."^fieldname) + +let get_binop binop = + match binop with + | JBir.Add _ -> Sil.PlusA + | JBir.Sub _ -> Sil.MinusA + | JBir.Mult _ -> Sil.Mult + | JBir.Div _ -> Sil.Div + | JBir.Rem _ -> Sil.Mod + | JBir.IAnd -> Sil.BAnd + | JBir.IShl -> Sil.Shiftlt + | JBir.IShr -> Sil.Shiftrt + | JBir.IOr -> Sil.BOr + | JBir.IXor -> Sil.BXor + | JBir.IUshr -> + raise (Frontend_error "Unsigned right shift operator") + | JBir.LShl -> Sil.Shiftlt + | JBir.LShr -> Sil.Shiftrt + | JBir.LAnd -> Sil.BAnd + | JBir.LOr -> Sil.BOr + | JBir.LXor -> Sil.BXor + | JBir.LUshr -> + raise (Frontend_error "Unsigned right shift operator") + | JBir.CMP comp -> + raise (Frontend_error "Unsigned right shift operator") + | JBir.ArrayLoad vt -> + raise (Frontend_error "Array load operator") + +let get_test_operator op = + match op with + | `Eq -> Sil.Eq + | `Ge -> Sil.Ge + | `Gt -> Sil.Gt + | `Le -> Sil.Le + | `Lt -> Sil.Lt + | `Ne -> Sil.Ne + +type defined_status = + | Defined of Cfg.Procdesc.t + | Called of Cfg.Procdesc.t + +type translation_status = + | Created of defined_status + | Unknown + +let lookup_procdesc cfg procname = + match Cfg.Procdesc.find_from_name cfg procname with + | Some procdesc -> + if Cfg.Procdesc.is_defined procdesc then + Created (Defined procdesc) + else + Created (Called procdesc) + | None -> Unknown + +let is_java_native cm = + (cm.Javalib.cm_implementation = Javalib.Native) + +let is_clone ms = + JBasics.ms_name ms = JConfig.clone_name + +let get_implementation cm = + match cm.Javalib.cm_implementation with + | Javalib.Native -> + let cms = cm.Javalib.cm_class_method_signature in + let cn, ms = JBasics.cms_split cms in + JUtils.log "native method %s found in %s@." (JBasics.ms_name ms) (JBasics.cn_name cn); + assert false + | Javalib.Java t -> + JBir.transform ~bcv: false ~ch_link: false ~formula: false ~formula_cmd:[] cm (Lazy.force t) + +let update_constr_loc cn ms loc_start = + if (JBasics.ms_name ms) = JConfig.constructor_name then + try ignore(JBasics.ClassMap.find cn !constr_loc_map) + with Not_found -> constr_loc_map := (JBasics.ClassMap.add cn loc_start !constr_loc_map) + +let update_init_loc cn ms loc_start = + if JBasics.ms_equal ms JBasics.clinit_signature then + try ignore(JBasics.ClassMap.find cn !init_loc_map) + with Not_found -> init_loc_map := (JBasics.ClassMap.add cn loc_start !init_loc_map) + +let no_static_final = ref false + +(** Creates a procedure description. *) +let create_local_procdesc program linereader cfg tenv node m = + let cn, ms = JBasics.cms_split (Javalib.get_class_method_signature m) in + let meth_kind = + if JBasics.ms_equal ms JBasics.clinit_signature then JContext.Init + else JContext.Normal in + if not ( + !no_static_final = false && + meth_kind = JContext.Init && + not (JTransStaticField.has_static_final_fields node)) + then + let procname = (JTransType.get_method_procname cn ms) in + let create_new_procdesc () = + let trans_access = function + | `Default -> Sil.Default + | `Public -> Sil.Public + | `Private -> Sil.Private + | `Protected -> Sil.Protected in + try + match m with + | Javalib.AbstractMethod am -> (* create a procdesc with empty body *) + let formals = formals_from_signature program tenv cn ms (get_method_kind m) in + let method_annotation = JAnnotation.translate_method am.Javalib.am_annotations in + let procdesc = + let open Cfg.Procdesc in + let proc_attributes = + { + Sil.access = trans_access am.Javalib.am_access; + Sil.exceptions = list_map JBasics.cn_name am.Javalib.am_exceptions; + Sil.is_abstract = true; + Sil.is_bridge_method = am.Javalib.am_bridge; + Sil.is_objc_instance_method = false; + Sil.is_synthetic_method = am.Javalib.am_synthetic; + Sil.language = Sil.Java; + Sil.method_annotation = method_annotation; + } in + create { + cfg = cfg; + name = procname; + is_defined = true; + ret_type = JTransType.return_type program tenv ms meth_kind; + formals = formals; + locals = []; + captured = []; + loc = Sil.dummy_location; + proc_attributes = proc_attributes + } in + let start_kind = Cfg.Node.Start_node procdesc in + let start_node = Cfg.Node.create cfg Sil.dummy_location start_kind [] procdesc [] in + let exit_kind = (Cfg.Node.Exit_node procdesc) in + let exit_node = Cfg.Node.create cfg Sil.dummy_location exit_kind [] procdesc [] in + Cfg.Node.set_succs_exn start_node [exit_node] [exit_node]; + Cfg.Procdesc.set_start_node procdesc start_node; + Cfg.Procdesc.set_exit_node procdesc exit_node + | Javalib.ConcreteMethod cm when is_java_native cm -> + let formals = formals_from_signature program tenv cn ms (get_method_kind m) in + let method_annotation = JAnnotation.translate_method cm.Javalib.cm_annotations in + let _procdesc = + let open Cfg.Procdesc in + let proc_attributes = + { + Sil.access = trans_access cm.Javalib.cm_access; + Sil.exceptions = list_map JBasics.cn_name cm.Javalib.cm_exceptions; + Sil.is_abstract = false; + Sil.is_bridge_method = cm.Javalib.cm_bridge; + Sil.is_objc_instance_method = false; + Sil.is_synthetic_method = cm.Javalib.cm_synthetic; + Sil.language = Sil.Java; + Sil.method_annotation = method_annotation; + } in + create { + cfg = cfg; + name = procname; + is_defined = false; + ret_type = JTransType.return_type program tenv ms meth_kind; + formals = formals; + locals = []; + captured = []; + loc = Sil.dummy_location; + proc_attributes = proc_attributes; + } in + () + | Javalib.ConcreteMethod cm -> + let impl = get_implementation cm in + let locals, formals = locals_formals program tenv cn impl meth_kind in + let loc_start = + let loc = (get_location impl 0 JContext.Normal cn) in + fix_method_definition_line linereader procname loc in + let loc_exit = (get_location impl (Array.length (JBir.code impl) - 1) JContext.Normal cn) in + let method_annotation = JAnnotation.translate_method cm.Javalib.cm_annotations in + update_constr_loc cn ms loc_start; + update_init_loc cn ms loc_exit; + let procdesc = + let open Cfg.Procdesc in + let proc_attributes = + { + Sil.access = trans_access cm.Javalib.cm_access; + Sil.exceptions = list_map JBasics.cn_name cm.Javalib.cm_exceptions; + Sil.is_abstract = false; + Sil.is_bridge_method = cm.Javalib.cm_bridge; + Sil.is_objc_instance_method = false; + Sil.is_synthetic_method = cm.Javalib.cm_synthetic; + Sil.language = Sil.Java; + Sil.method_annotation = method_annotation; + } in + create { + cfg = cfg; + name = procname; + is_defined = true; + ret_type = JTransType.return_type program tenv ms meth_kind; + formals = formals; + locals = locals; + captured = []; + loc = loc_start; + proc_attributes = proc_attributes; + } in + let start_kind = Cfg.Node.Start_node procdesc in + let start_node = Cfg.Node.create cfg loc_start start_kind [] procdesc [] in + let exit_kind = (Cfg.Node.Exit_node procdesc) in + let exit_node = Cfg.Node.create cfg loc_exit exit_kind [] procdesc [] in + let exn_kind = Cfg.Node.exn_sink_kind in + let exn_node = Cfg.Node.create cfg loc_exit exn_kind [] procdesc [] in + JContext.add_exn_node procname exn_node; + Cfg.Procdesc.set_start_node procdesc start_node; + Cfg.Procdesc.set_exit_node procdesc exit_node; + Cfg.Node.add_locals_ret_declaration start_node locals; + with JBir.Subroutine -> + L.err "create_local_procdesc raised JBir.Subroutine on %a@." Procname.pp procname in + match lookup_procdesc cfg procname with + | Unknown -> create_new_procdesc () + | Created defined_status -> + begin + match defined_status with + | Defined procdesc -> assert false + | Called procdesc -> + Cfg.Procdesc.remove cfg (Cfg.Procdesc.get_proc_name procdesc) false; + create_new_procdesc () + end + +let create_external_procdesc program cfg tenv cn ms method_annotation is_static = + let return_type = + match JBasics.ms_rtype ms with + | None -> Sil.Tvoid + | Some vt -> JTransType.value_type program tenv vt in + let formals = formals_from_signature program tenv cn ms is_static in + let procname = JTransType.get_method_procname cn ms in + ignore ( + let open Cfg.Procdesc in + let proc_attributes = + { + Sil.access = Sil.Default; + Sil.exceptions = []; + Sil.is_abstract = false; + Sil.is_bridge_method = false; + Sil.is_objc_instance_method = false; + Sil.is_synthetic_method = false; + Sil.language = Sil.Java; + Sil.method_annotation = method_annotation; + } in + create { + cfg = cfg; + name = procname; + is_defined = false; + ret_type = return_type; + formals = formals; + locals = []; + captured = []; + loc = Sil.dummy_location; + proc_attributes = proc_attributes; + }) + +(** returns the procedure description of the given method and creates it if it hasn't been created before *) +let rec get_method_procdesc program cfg tenv cn ms is_static = + let procname = JTransType.get_method_procname cn ms in + match lookup_procdesc cfg procname with + | Unknown -> + create_external_procdesc program cfg tenv cn ms Sil.method_annotation_empty is_static; + get_method_procdesc program cfg tenv cn ms is_static + | Created status -> status + +let use_static_final_fields context = + (not !no_static_final) && (JContext.get_meth_kind context) <> JContext.Init + +let builtin_new = + Sil.Const (Sil.Cfun SymExec.ModelBuiltins.__new) + +let builtin_get_array_size = + Sil.Const (Sil.Cfun SymExec.ModelBuiltins.__get_array_size) + +let create_sil_deref exp typ loc = + let fresh_id = Ident.create_fresh Ident.knormal in + let deref = Sil.Letderef (fresh_id, exp, typ, loc) in + fresh_id, deref + +(** translate an expression used as an r-value *) +let rec expression context pc expr = + (* JUtils.log "\t\t\t\texpr: %s@." (JBir.print_expr expr); *) + let cn = (JContext.get_cn context) in + let program = JContext.get_program context in + let loc = get_location (JContext.get_impl context) pc (JContext.get_meth_kind context) cn in + let tenv = JContext.get_tenv context in + let type_of_expr = JTransType.expr_type context expr in + let trans_var pvar var_type = + let id = Ident.create_fresh Ident.knormal in + let sil_instr = Sil.Letderef (id, Sil.Lvar pvar, type_of_expr, loc) in + ([id], [sil_instr], Sil.Var id) in + match expr with + | JBir.Var (vt, var) -> + let pvar = (JContext.set_pvar context var type_of_expr) in + trans_var pvar type_of_expr + | JBir.Const c -> + begin + match c with (* We use the constant internally to mean a variable. *) + | `String s when (JBasics.jstr_pp s) = JConfig.field_cst -> + let varname = Mangled.from_string JConfig.field_st in + let string_type = (JTransType.get_class_type program tenv (JBasics.make_cn JConfig.string_cl)) in + let procname = (Cfg.Procdesc.get_proc_name (JContext.get_procdesc context)) in + let pvar = Sil.mk_pvar varname procname in + trans_var pvar string_type + | _ -> ([], [], Sil.Const (get_constant c)) + end + | JBir.Unop (unop, ex) -> + let type_of_ex = JTransType.expr_type context ex in + let (ids, instrs, sil_ex) = expression context pc ex in + begin + match unop with + | JBir.Neg _ -> (ids, instrs, Sil.UnOp (Sil.Neg, sil_ex, Some type_of_expr)) + | JBir.ArrayLength -> + let array_typ_no_ptr = + match type_of_ex with + | Sil.Tptr (typ, _) -> typ + | _ -> type_of_ex in + let fresh_id, deref = create_sil_deref sil_ex array_typ_no_ptr loc in + let args = [(sil_ex, type_of_ex)] in + let ret_id = Ident.create_fresh Ident.knormal in + let call_instr = Sil.Call([ret_id], builtin_get_array_size, args, loc, Sil.cf_default) in + (ids @ [fresh_id; ret_id], instrs @ [deref; call_instr], Sil.Var ret_id) + | JBir.Conv conv -> + let cast_ex = Sil.Cast (JTransType.cast_type conv, sil_ex) in + (ids, instrs, cast_ex) + | JBir.InstanceOf ot | JBir.Cast ot -> + let subtypes = + (match unop with + | JBir.InstanceOf _ -> Sil.Subtype.subtypes_instof + | JBir.Cast _ -> Sil.Subtype.subtypes_cast + | _ -> assert false) in + let sizeof_expr = + JTransType.sizeof_of_object_type program tenv ot subtypes in + let builtin = + (match unop with + | JBir.InstanceOf ot -> Sil.Const (Sil.Cfun SymExec.ModelBuiltins.__instanceof) + | JBir.Cast ot -> Sil.Const (Sil.Cfun SymExec.ModelBuiltins.__cast) + | _ -> assert false) in + let args = [(sil_ex, type_of_ex); (sizeof_expr, Sil.Tvoid)] in + let ret_id = Ident.create_fresh Ident.knormal in + let call = Sil.Call([ret_id], builtin, args, loc, Sil.cf_default) in + let res_ex = Sil.Var ret_id in + (ids @ [ret_id], instrs @ [call], res_ex) + end + | JBir.Binop (binop, ex1, ex2) -> + let (idl1, instrs1, sil_ex1) = expression context pc ex1 + and (idl2, instrs2, sil_ex2) = expression context pc ex2 in + begin + match binop with + | JBir.ArrayLoad vt -> + (* add an instruction that dereferences the array *) + let array_typ = Sil.Tarray(type_of_expr, Sil.Var (Ident.create_fresh Ident.kprimed)) in + let fresh_id, deref_array_instr = create_sil_deref sil_ex1 array_typ loc in + let id = Ident.create_fresh Ident.knormal in + let letderef_instr = + Sil.Letderef (id, Sil.Lindex (sil_ex1, sil_ex2), type_of_expr, loc) in + let ids = idl1 @ idl2 @ [fresh_id; id] in + let instrs = (instrs1 @ (deref_array_instr :: instrs2)) @ [letderef_instr] in + ids, instrs, Sil.Var id + | other_binop -> + let sil_binop = get_binop other_binop in + let sil_expr = Sil.BinOp (sil_binop, sil_ex1, sil_ex2) in + (idl1 @ idl2, (instrs1 @ instrs2), sil_expr) + end + | JBir.Field (ex, cn, fs) -> + let (idl, instrs, sil_expr) = expression context pc ex in + let field_name = get_field_name program false tenv cn fs context in + let sil_type = + try + JTransType.get_class_type_no_pointer program tenv cn + with Frontend_error msg -> assert false in + let sil_expr = Sil.Lfield (sil_expr, field_name, sil_type) in + let tmp_id = Ident.create_fresh Ident.knormal in + let lderef_instr = Sil.Letderef (tmp_id, sil_expr, sil_type, loc) in + (idl @ [tmp_id], instrs @ [lderef_instr], Sil.Var tmp_id) + | JBir.StaticField (cn, fs) -> + let class_exp = + let classname = Mangled.from_string (JBasics.cn_name cn) in + let var_name = Sil.mk_pvar_global classname in + Sil.Lvar var_name in + let (idl, instrs, sil_expr) = [], [], class_exp in + let field_name = get_field_name program true tenv cn fs context in + let sil_type = + try + match JTransType.get_class_type_no_pointer program tenv cn with + | Sil.Tstruct (ftal, sftal, csu, nameo, supers, def_mthds, iann) -> + Sil.Tstruct (sftal, sftal, csu, nameo, supers, def_mthds, iann) + | t -> t + with Frontend_error msg -> assert false in + if JTransStaticField.is_static_final_field context cn fs && use_static_final_fields context + then + (* when accessing a static final field, we call the initialiser method. *) + let cfg = JContext.get_cfg context in + let callee_procdesc = + match get_method_procdesc program cfg tenv cn JBasics.clinit_signature Static with + | Called p | Defined p -> p in + let field_type = (JTransType.get_class_type program tenv (JBasics.make_cn JConfig.string_cl)) in + JTransStaticField.translate_instr_static_field context callee_procdesc fs field_type loc + else + let sil_expr = Sil.Lfield (sil_expr, field_name, sil_type) in + let tmp_id = Ident.create_fresh Ident.knormal in + let lderef_instr = Sil.Letderef (tmp_id, sil_expr, sil_type, loc) in + (idl @ [tmp_id], instrs @ [lderef_instr], Sil.Var tmp_id) + +let method_invocation context loc pc var_opt cn ms sil_obj_opt expr_list invoke_code is_static = + let cfg = JContext.get_cfg context in + let tenv = JContext.get_tenv context in + let program = JContext.get_program context in + let cf_virtual = match invoke_code with + | I_Virtual -> true + | _ -> false in + let call_flags = { Sil.cf_virtual = cf_virtual; Sil.cf_noreturn = false; Sil.cf_is_objc_block = false; } in + let callee_procdesc = + match get_method_procdesc program cfg tenv cn ms is_static with + | Called p | Defined p -> p in + let init = + match sil_obj_opt with + | None -> ([], [], []) + | Some (sil_obj_expr, sil_obj_type) -> + (* for non-constructors, add an instruction that dereferences the receiver *) + let ids, instrs = + let is_non_constructor_call = + match invoke_code with + | I_Special -> false + | _ -> true in + match sil_obj_expr with + | Sil.Var id when is_non_constructor_call && not !JConfig.translate_checks -> + let obj_typ_no_ptr = + match sil_obj_type with + | Sil.Tptr (typ, _) -> typ + | _ -> sil_obj_type in + let fresh_id, deref = create_sil_deref sil_obj_expr obj_typ_no_ptr loc in + [fresh_id], [deref] + | _ -> [], [] in + (ids, instrs, [(sil_obj_expr, sil_obj_type)]) in + let (idl, instrs, call_args) = + list_fold_left + (fun (idl_accu, instrs_accu, args_accu) expr -> + let (idl, instrs, sil_expr) = expression context pc expr in + let sil_expr_type = JTransType.expr_type context expr in + (idl_accu @ idl, instrs_accu @ instrs, args_accu @ [(sil_expr, sil_expr_type)])) + init + expr_list in + let callee_procname = + if JBasics.cn_equal cn JConfig.infer_builtins_cl then + Procname.from_string (JBasics.ms_name ms) + else Cfg.Procdesc.get_proc_name callee_procdesc in + let (call_idl, call_instrs) = + let callee_fun = Sil.Const (Sil.Cfun callee_procname) in + let return_type = Cfg.Procdesc.get_ret_type callee_procdesc in + let call_ret_instrs sil_var = + let ret_id = Ident.create_fresh Ident.knormal in + let call_instr = Sil.Call([ret_id], callee_fun, call_args, loc, call_flags) in + let set_instr = Sil.Set (Sil.Lvar sil_var, return_type, Sil.Var ret_id, loc) in + (idl @ [ret_id], instrs @ [call_instr; set_instr]) in + match var_opt with + | None -> + let call_instr = Sil.Call([], callee_fun, call_args, loc, call_flags) in + (idl, instrs @ [call_instr]) + | Some var -> + let sil_var = JContext.set_pvar context var return_type in + (call_ret_instrs sil_var) in + (callee_procdesc, callee_procname, call_idl, call_instrs) + +let get_array_size context pc expr_list content_type = + let get_expr_instr expr other_instrs = + let (idl, instrs, sil_size_expr) = expression context pc expr in + match other_instrs with + | (other_idl, other_instrs, other_exprs) -> + (idl@other_idl, instrs@other_instrs, sil_size_expr:: other_exprs) in + let (idl, instrs, sil_size_exprs) = (list_fold_right get_expr_instr expr_list ([],[],[])) in + let get_array_type sil_size_expr content_type = + Sil.Tarray (content_type, sil_size_expr) in + let array_type = (list_fold_right get_array_type sil_size_exprs content_type) in + let array_size = Sil.Sizeof (array_type, Sil.Subtype.exact) in + (idl, instrs, array_size) + +module Int = +struct + type t = int + let compare = (-) +end + +module IntSet = Set.Make(Int) + +let detect_loop entry_pc impl = + let code = (JBir.code impl) in + let pc_bound = Array.length code in + let empty = IntSet.empty in + let rec loop visited pc = + if (IntSet.mem pc visited) || pc >= pc_bound then + (false, visited) + else + begin + let visited_updated = IntSet.add pc visited in + match code.(pc) with + | JBir.Goto goto_pc when goto_pc = entry_pc -> (true, empty) + | JBir.Goto goto_pc -> loop visited_updated goto_pc + | JBir.Ifd (_, if_pc) when if_pc = entry_pc -> (true, empty) + | JBir.Ifd (_, if_pc) -> + let (loop_detected, visited_after) = loop visited_updated (pc + 1) in + if loop_detected then + (true, empty) + else + loop visited_after if_pc + | _ -> + if (pc + 1) = entry_pc then + (true, empty) + else + loop visited_updated (pc + 1) + end in + fst (loop empty entry_pc) + +type translation = + | Skip + | Instr of Cfg.Node.t + | Prune of Cfg.Node.t * Cfg.Node.t + | Loop of Cfg.Node.t * Cfg.Node.t * Cfg.Node.t + +(* TODO: this is a little bit hacky. The purpose of this is not so clear *) +(* This function tries to recursively search for the classname of the class *) +(* where the method is defined. It returns the classname given as argument*) +(* when this classname cannot be found *) +let resolve_method context cn ms = + let rec loop fallback_cn cn = + match JClasspath.lookup_node cn (JContext.get_program context) with + | None -> fallback_cn + | Some node -> + if Javalib.defines_method node ms then cn + else + match node with + | Javalib.JInterface jinterface -> fallback_cn + | Javalib.JClass jclass -> + begin + match jclass.Javalib.c_super_class with + | None -> fallback_cn + | Some super_cn -> loop fallback_cn super_cn + end in + loop cn cn + +(* TODO: unclear if this corresponds to what JControlFlow.resolve_method'*) +(* is trying to do. Normally, this implementation below goes deeper into *) +(* the type hierarchy and it is not clear why we should not do that *) +let rec extends context node1 node2 = + let is_matching cn = + JBasics.cn_equal cn (Javalib.get_name node2) in + let rec check cn_list = + if list_exists is_matching cn_list then true + else + iterate cn_list + and iterate cn_list = + let per_classname cn = + match JClasspath.lookup_node cn (JContext.get_program context) with + | None -> false (* TODO: should capture the class instead of returning false *) + | Some node -> + let super_cn_list = + match node with + | Javalib.JInterface jinterface -> + jinterface.Javalib.i_interfaces + | Javalib.JClass jclass -> + let cn_interfaces = jclass.Javalib.c_interfaces in + begin + match jclass.Javalib.c_super_class with + | None -> cn_interfaces + | Some super_cn -> super_cn :: cn_interfaces + end in + match super_cn_list with + | [] -> false + | l -> check l in + list_exists per_classname cn_list in + check [Javalib.get_name node1] + +let instruction_array_call ms obj_type obj args var_opt vt = + if is_clone ms then + (let cn = JBasics.make_cn JConfig.infer_array_cl in + let vt = (JBasics.TObject obj_type) in + let ms = JBasics.make_ms JConfig.clone_name [vt] (Some vt) in + JBir.InvokeStatic (var_opt, cn, ms, obj:: args)) + else + (let undef_cn, undef_ms = get_undefined_method_call (JBasics.ms_rtype ms) in + JBir.InvokeStatic (var_opt, undef_cn, undef_ms, [])) + +(* special translation of the method start() of a Thread or a Runnable object. +We translate it directly as the run() method *) +let instruction_thread_start context cn ms obj args var_opt = + match JClasspath.lookup_node cn (JContext.get_program context) with + | None -> + let () = JUtils.log "\t\t\tWARNING: %s should normally be found@." (JBasics.cn_name cn) in + None + | Some node -> + begin + match JClasspath.lookup_node (JBasics.make_cn JConfig.thread_class) (JContext.get_program context) with + | None -> None (* TODO: should load the class instead of returning None *) + | Some thread_node -> + if ((JBasics.ms_name ms) = JConfig.start_method) && (extends context node thread_node) then + let ms = JBasics.make_ms JConfig.run_method [] None in + Some (JBir.InvokeNonVirtual (var_opt, obj, cn, ms, args)) + else None + end + + +let is_this expr = + match expr with + | JBir.Var (_, var) -> JBir.var_name_debug var = Some JConfig.this + | _ -> false + + +let assume_not_null loc sil_expr = + let builtin_infer_assume = Sil.Const (Sil.Cfun SymExec.ModelBuiltins.__infer_assume) in + let not_null_expr = + Sil.BinOp (Sil.Ne, sil_expr, Sil.exp_null) in + let assume_call_flag = { Sil.cf_virtual = false; Sil.cf_noreturn = true; Sil.cf_is_objc_block = false; } in + let call_args = [(not_null_expr, Sil.Tint Sil.IBool)] in + Sil.Call ([], builtin_infer_assume, call_args, loc, assume_call_flag) + + +let rec instruction context pc instr : translation = + (* JUtils.log "\t\t\tinstr: %s@." (JBir.print_instr instr); *) + let cfg = JContext.get_cfg context in + let tenv = JContext.get_tenv context in + let cg = JContext.get_cg context in + let cn = JContext.get_cn context in + let program = JContext.get_program context in + let meth_kind = JContext.get_meth_kind context in + let loc = get_location (JContext.get_impl context) pc meth_kind cn in + let match_never_null = JContext.get_never_null_matcher context in + let create_node node_kind temps sil_instrs = + Cfg.Node.create + cfg (get_location (JContext.get_impl context) pc meth_kind cn) node_kind sil_instrs (JContext.get_procdesc context) temps in + let return_not_null () = + let proc_name = Cfg.Procdesc.get_proc_name (JContext.get_procdesc context) in + (match_never_null loc.Sil.file proc_name + || list_exists (fun p -> Procname.equal p proc_name) JTransType.never_returning_null) in + try + match instr with + | JBir.AffectVar (var, expr) -> + let (idl, stml, sil_expr) = expression context pc expr in + let sil_type = JTransType.expr_type context expr in + let pvar = (JContext.set_pvar context var sil_type) in + let sil_instr = Sil.Set (Sil.Lvar pvar, sil_type, sil_expr, loc) in + let node_kind = Cfg.Node.Stmt_node "method_body" in + let node = create_node node_kind idl (stml @ [sil_instr]) in + Instr node + | JBir.Return expr_option -> + let node_kind = Cfg.Node.Stmt_node "method_body" in + let node = + match expr_option with + | None -> + create_node node_kind [] [] + | Some expr -> + let (idl, stml, sil_expr) = expression context pc expr in + let ret_var = Cfg.Procdesc.get_ret_var (JContext.get_procdesc context) in + let ret_type = Cfg.Procdesc.get_ret_type (JContext.get_procdesc context) in + let sil_instrs = + let return_instr = Sil.Set (Sil.Lvar ret_var, ret_type, sil_expr, loc) in + if return_not_null () then + [assume_not_null loc sil_expr; return_instr] + else + [return_instr] in + create_node node_kind idl (stml @ sil_instrs) in + JContext.add_goto_jump context pc JContext.Exit; + Instr node + | JBir.AffectArray (array_ex, index_ex, value_ex) -> + let (idl_array, instrs_array, sil_expr_array) = expression context pc array_ex + and (idl_index, instrs_index, sil_expr_index) = expression context pc index_ex + and (idl_value, instrs_value, sil_expr_value) = expression context pc value_ex + and arr_type = JTransType.expr_type context array_ex in + let arr_type_np = JTransType.extract_cn_type_np arr_type in + let sil_instr = Sil.Set (Sil.Lindex (sil_expr_array, sil_expr_index), arr_type_np, sil_expr_value, loc) in + let final_idl = idl_array @ idl_index @ idl_value + and final_instrs = instrs_array @ instrs_index @ instrs_value @ [sil_instr] in + let node_kind = Cfg.Node.Stmt_node "method_body" in + let node = create_node node_kind final_idl final_instrs in + Instr node + | JBir.AffectField (e_lhs, cn, fs, e_rhs) -> + let (idl1, stml1, sil_expr_lhs) = expression context pc e_lhs in + let (idl2, stml2, sil_expr_rhs) = expression context pc e_rhs in + let field_name = get_field_name program false tenv cn fs context in + let type_of_the_surrounding_class = JTransType.get_class_type_no_pointer program tenv cn in + let type_of_the_root_of_e_lhs = type_of_the_surrounding_class in + let expr_off = Sil.Lfield(sil_expr_lhs, field_name, type_of_the_surrounding_class) in + let sil_instr = Sil.Set (expr_off, type_of_the_root_of_e_lhs, sil_expr_rhs, loc) in + let node_kind = Cfg.Node.Stmt_node "method_body" in + let node = create_node node_kind (idl1 @ idl2) (stml1 @ stml2 @ [sil_instr]) in + Instr node + | JBir.AffectStaticField (cn, fs, e_rhs) -> + let class_exp = + let classname = Mangled.from_string (JBasics.cn_name cn) in + let var_name = Sil.mk_pvar_global classname in + Sil.Lvar var_name in + let (idl1, stml1, sil_expr_lhs) = [], [], class_exp in + let (idl2, stml2, sil_expr_rhs) = expression context pc e_rhs in + let field_name = get_field_name program true tenv cn fs context in + let type_of_the_surrounding_class = + match JTransType.get_class_type_no_pointer program tenv cn with + | Sil.Tstruct (ftal, sftal, csu, nameo, supers, def_mthds, iann) -> + Sil.Tstruct (sftal, sftal, csu, nameo, supers, def_mthds, iann) + | t -> t in + let type_of_the_root_of_e_lhs = type_of_the_surrounding_class in + let expr_off = Sil.Lfield(sil_expr_lhs, field_name, type_of_the_surrounding_class) in + let sil_instr = Sil.Set (expr_off, type_of_the_root_of_e_lhs, sil_expr_rhs, loc) in + let node_kind = Cfg.Node.Stmt_node "method_body" in + let node = create_node node_kind (idl1 @ idl2) (stml1 @ stml2 @ [sil_instr]) in + Instr node + | JBir.Goto goto_pc -> + JContext.reset_pvar_type context; + JContext.add_goto_jump context pc (JContext.Jump goto_pc); + Skip + | JBir.Ifd ((op, e1, e2), if_pc) -> (* Note: JBir provides the condition for the false branch, under which to jump *) + JContext.reset_pvar_type context; + let (idl1, instrs1, sil_ex1) = expression context pc e1 + and (idl2, instrs2, sil_ex2) = expression context pc e2 in + let sil_op = get_test_operator op in + let sil_test_false = Sil.BinOp (sil_op, sil_ex1, sil_ex2) in + let sil_test_true = Sil.UnOp(Sil.LNot, sil_test_false, None) in + let sil_instrs_true = Sil.Prune (sil_test_true, loc, true, Sil.Ik_if) in + let sil_instrs_false = Sil.Prune (sil_test_false, loc, false, Sil.Ik_if) in + let node_kind_true = Cfg.Node.Prune_node (true, Sil.Ik_if, "method_body") in + let node_kind_false = Cfg.Node.Prune_node (false, Sil.Ik_if, "method_body") in + let prune_node_true = create_node node_kind_true (idl1 @ idl2) (instrs1 @ instrs2 @ [sil_instrs_true]) + and prune_node_false = create_node node_kind_false (idl1 @ idl2) (instrs1 @ instrs2 @ [sil_instrs_false]) in + JContext.add_if_jump context prune_node_false if_pc; + if detect_loop pc (JContext.get_impl context) then + let join_node_kind = Cfg.Node.Join_node in + let join_node = create_node join_node_kind [] [] in + Loop (join_node, prune_node_true, prune_node_false) + else + Prune (prune_node_true, prune_node_false) + | JBir.Throw expr -> + let node_kind = Cfg.Node.Stmt_node "throw" in + let (ids, instrs, sil_expr) = expression context pc expr in + let ret_var = Cfg.Procdesc.get_ret_var (JContext.get_procdesc context) in + let ret_type = Cfg.Procdesc.get_ret_type (JContext.get_procdesc context) in + let sil_exn = Sil.Const (Sil.Cexn sil_expr) in + let sil_instr = Sil.Set (Sil.Lvar ret_var, ret_type, sil_exn, loc) in + let node = create_node node_kind ids (instrs @ [sil_instr]) in + JContext.add_goto_jump context pc JContext.Exit; + Instr node + | JBir.New (var, cn, constr_type_list, constr_arg_list) -> + let builtin_new = Sil.Const (Sil.Cfun SymExec.ModelBuiltins.__new) in + let class_type = JTransType.get_class_type program tenv cn in + let class_type_np = + try + JTransType.get_class_type_no_pointer program tenv cn + with _ -> assert false in + let sizeof_exp = Sil.Sizeof (class_type_np, Sil.Subtype.exact) in + let args = [(sizeof_exp, class_type)] in + let ret_id = Ident.create_fresh Ident.knormal in + let new_instr = Sil.Call([ret_id], builtin_new, args, loc, Sil.cf_default) in + let constr_ms = JBasics.make_ms JConfig.constructor_name constr_type_list None in + let (constr_procdesc, constr_procname, call_ids, call_instrs) = + let ret_opt = Some (Sil.Var ret_id, class_type) in + method_invocation + context loc pc None cn constr_ms ret_opt constr_arg_list I_Special Static in + let pvar = JContext.set_pvar context var class_type in + let set_instr = Sil.Set (Sil.Lvar pvar, class_type, Sil.Var ret_id, loc) in + let node_kind = Cfg.Node.Stmt_node ("Call "^(Procname.to_string constr_procname)) in + let ids = ret_id :: call_ids in + let instrs = (new_instr :: call_instrs) @ [set_instr] in + let node = create_node node_kind ids instrs in + let caller_procname = (Cfg.Procdesc.get_proc_name (JContext.get_procdesc context)) in + Cg.add_edge cg caller_procname constr_procname; + Instr node + | JBir.NewArray (var, vt, expr_list) -> + let builtin_new_array = Sil.Const (Sil.Cfun SymExec.ModelBuiltins.__new_array) in + let content_type = JTransType.value_type program tenv vt in + let array_type = JTransType.create_array_type content_type (list_length expr_list) in + let array_name = JContext.set_pvar context var array_type in + let (idl, instrs, array_size) = get_array_size context pc expr_list content_type in + let call_args = [(array_size, array_type)] in + let ret_id = Ident.create_fresh Ident.knormal in + let call_instr = Sil.Call([ret_id], builtin_new_array, call_args, loc, Sil.cf_default) in + let set_instr = Sil.Set (Sil.Lvar array_name, array_type, Sil.Var ret_id, loc) in + let node_kind = Cfg.Node.Stmt_node "method_body" in + let node = create_node node_kind (idl @ [ret_id]) (instrs @ [call_instr; set_instr]) in + Instr node + | JBir.InvokeStatic (var_opt, cn, ms, args) -> + let cn = (resolve_method context cn ms) in + let sil_obj_opt, args, ids, instrs = + match args with + | [arg] when is_clone ms -> + (* hack to null check the receiver of clone when clone is an array. in the array.clone() + case, clone is a virtual call that we translate as a static call *) + let (ids, instrs, sil_arg_expr) = expression context pc arg in + let arg_typ = JTransType.expr_type context arg in + Some (sil_arg_expr, arg_typ), [], ids, instrs + | _ -> None, args, [], [] in + let (callee_procdesc, callee_procname, call_idl, call_instrs) = + method_invocation context loc pc var_opt cn ms sil_obj_opt args I_Static Static in + let node_kind = Cfg.Node.Stmt_node ("Call "^(Procname.to_string callee_procname)) in + let call_node = create_node node_kind (ids @ call_idl) (instrs @ call_instrs) in + let caller_procname = (Cfg.Procdesc.get_proc_name (JContext.get_procdesc context)) in + Cg.add_edge cg caller_procname callee_procname; + Instr call_node + | JBir.InvokeVirtual (var_opt, obj, call_kind, ms, args) -> + let caller_procname = (Cfg.Procdesc.get_proc_name (JContext.get_procdesc context)) in + let sil_obj_type = JTransType.expr_type context obj in + let create_call_node cn = + let (ids, instrs, sil_obj_expr) = expression context pc obj in + let (callee_procdesc, callee_procname, call_ids, call_instrs) = + let ret_opt = Some (sil_obj_expr, sil_obj_type) in + method_invocation context loc pc var_opt cn ms ret_opt args I_Virtual Non_Static in + let node_kind = Cfg.Node.Stmt_node ("Call "^(Procname.to_string callee_procname)) in + let call_node = create_node node_kind (ids @ call_ids) (instrs @ call_instrs) in + Cg.add_edge cg caller_procname callee_procname; + call_node in + let trans_virtual_call cn = + match instruction_thread_start context cn ms obj args var_opt with + | Some start_call -> instruction context pc start_call + | None -> + let cn = match (JTransType.extract_cn_no_obj sil_obj_type) with + | Some cn -> cn + | None -> cn in + let cn = (resolve_method context cn ms) in + let call_node = create_call_node cn in + Instr call_node in + begin + match call_kind with + | JBir.VirtualCall obj_type -> + begin + match obj_type with + | JBasics.TClass cn -> trans_virtual_call cn + | JBasics.TArray vt -> + let instr = instruction_array_call ms obj_type obj args var_opt vt in + instruction context pc instr + end + | JBir.InterfaceCall cn -> trans_virtual_call cn + end + | JBir.InvokeNonVirtual (var_opt, obj, cn, ms, args) -> + let cn = (resolve_method context cn ms) in + let (ids, instrs, sil_obj_expr) = expression context pc obj in + let sil_obj_type = JTransType.expr_type context obj in + let (callee_procdesc, callee_procname, call_ids, call_instrs) = + method_invocation context loc pc var_opt cn ms (Some (sil_obj_expr, sil_obj_type)) args I_Special Non_Static in + let node_kind = Cfg.Node.Stmt_node ("Call "^(Procname.to_string callee_procname)) in + let call_node = create_node node_kind (ids @ call_ids) (instrs @ call_instrs) in + let procdesc = (JContext.get_procdesc context) in + let caller_procname = (Cfg.Procdesc.get_proc_name procdesc) in + Cg.add_edge cg caller_procname callee_procname; + Instr call_node + + | JBir.Check (JBir.CheckNullPointer expr) when !JConfig.translate_checks && is_this expr -> + (* TODO #6509339: refactor the boilterplate code in the translattion of JVM checks *) + let (ids, instrs, sil_expr) = expression context pc expr in + let this_not_null_node = + create_node + (Cfg.Node.Stmt_node "this not null") ids (instrs @ [assume_not_null loc sil_expr]) in + Instr this_not_null_node + + | JBir.Check (JBir.CheckNullPointer expr) when !JConfig.translate_checks -> + let (ids, instrs, sil_expr) = expression context pc expr in + let not_null_node = + let sil_not_null = Sil.BinOp (Sil.Ne, sil_expr, Sil.exp_null) in + let sil_prune_not_null = Sil.Prune (sil_not_null, loc, true, Sil.Ik_if) + and not_null_kind = Cfg.Node.Prune_node (true, Sil.Ik_if, "Not null") in + create_node not_null_kind ids (instrs @ [sil_prune_not_null]) in + let throw_npe_node = + let sil_is_null = Sil.BinOp (Sil.Eq, sil_expr, Sil.exp_null) in + let sil_prune_null = Sil.Prune (sil_is_null, loc, true, Sil.Ik_if) + and npe_kind = Cfg.Node.Stmt_node "Throw NPE" + and npe_cn = JBasics.make_cn JConfig.npe_cl in + let class_type = JTransType.get_class_type program tenv npe_cn + and class_type_np = JTransType.get_class_type_no_pointer program tenv npe_cn in + let sizeof_exp = Sil.Sizeof (class_type_np, Sil.Subtype.exact) in + let args = [(sizeof_exp, class_type)] in + let ret_id = Ident.create_fresh Ident.knormal in + let new_instr = Sil.Call([ret_id], builtin_new, args, loc, Sil.cf_default) in + let constr_ms = JBasics.make_ms JConfig.constructor_name [] None in + let (constr_procdesc, constr_procname, call_ids, call_instrs) = + let ret_opt = Some (Sil.Var ret_id, class_type) in + method_invocation context loc pc None npe_cn constr_ms ret_opt [] I_Special Static in + let sil_exn = Sil.Const (Sil.Cexn (Sil.Var ret_id)) in + let ret_var = Cfg.Procdesc.get_ret_var (JContext.get_procdesc context) in + let ret_type = Cfg.Procdesc.get_ret_type (JContext.get_procdesc context) in + let set_instr = Sil.Set (Sil.Lvar ret_var, ret_type, sil_exn, loc) in + let npe_instrs = instrs @ [sil_prune_null] @ (new_instr :: call_instrs) @ [set_instr] in + create_node npe_kind (ids @ call_ids) npe_instrs in + Prune (not_null_node, throw_npe_node) + + | JBir.Check (JBir.CheckArrayBound (array_expr, index_expr)) when !JConfig.translate_checks -> + + let ids, instrs, sil_array_expr, sil_length_expr, sil_index_expr = + let array_ids, array_instrs, sil_array_expr = + expression context pc array_expr + and length_ids, length_instrs, sil_length_expr = + expression context pc (JBir.Unop (JBir.ArrayLength, array_expr)) + and index_ids, index_instrs, sil_index_expr = + expression context pc index_expr in + let ids = array_ids @ index_ids @ length_ids + and instrs = array_instrs @ index_instrs @ length_instrs in + (ids, instrs, sil_array_expr, sil_length_expr, sil_index_expr) in + + let in_bound_node = + let in_bound_node_kind = + Cfg.Node.Prune_node (true, Sil.Ik_if, "In bound") in + let sil_assume_in_bound = + let sil_in_bound = + let sil_positive_index = + Sil.BinOp (Sil.Ge, sil_index_expr, Sil.Const (Sil.Cint Sil.Int.zero)) + and sil_less_than_length = + Sil.BinOp (Sil.Lt, sil_index_expr, sil_length_expr) in + Sil.BinOp (Sil.LAnd, sil_positive_index, sil_less_than_length) in + Sil.Prune (sil_in_bound, loc, true, Sil.Ik_if) in + create_node in_bound_node_kind ids (instrs @ [sil_assume_in_bound]) + + and throw_out_of_bound_node = + let out_of_bound_node_kind = + Cfg.Node.Stmt_node "Out of bound" in + let sil_assume_out_of_bound = + let sil_out_of_bound = + let sil_negative_index = + Sil.BinOp (Sil.Lt, sil_index_expr, Sil.Const (Sil.Cint Sil.Int.zero)) + and sil_greater_than_length = + Sil.BinOp (Sil.Gt, sil_index_expr, sil_length_expr) in + Sil.BinOp (Sil.LOr, sil_negative_index, sil_greater_than_length) in + Sil.Prune (sil_out_of_bound, loc, true, Sil.Ik_if) in + let out_of_bound_cn = JBasics.make_cn JConfig.out_of_bound_cl in + let class_type = JTransType.get_class_type program tenv out_of_bound_cn + and class_type_np = JTransType.get_class_type_no_pointer program tenv out_of_bound_cn in + let sizeof_exp = Sil.Sizeof (class_type_np, Sil.Subtype.exact) in + let args = [(sizeof_exp, class_type)] in + let ret_id = Ident.create_fresh Ident.knormal in + let new_instr = Sil.Call([ret_id], builtin_new, args, loc, Sil.cf_default) in + let constr_ms = JBasics.make_ms JConfig.constructor_name [] None in + let (constr_procdesc, constr_procname, call_ids, call_instrs) = + method_invocation + context loc pc None out_of_bound_cn constr_ms + (Some (Sil.Var ret_id, class_type)) [] I_Special Static in + let sil_exn = Sil.Const (Sil.Cexn (Sil.Var ret_id)) in + let ret_var = Cfg.Procdesc.get_ret_var (JContext.get_procdesc context) in + let ret_type = Cfg.Procdesc.get_ret_type (JContext.get_procdesc context) in + let set_instr = Sil.Set (Sil.Lvar ret_var, ret_type, sil_exn, loc) in + let out_of_bound_instrs = + instrs @ [sil_assume_out_of_bound] @ (new_instr :: call_instrs) @ [set_instr] in + create_node out_of_bound_node_kind (ids @ call_ids) out_of_bound_instrs in + + Prune (in_bound_node, throw_out_of_bound_node) + + | JBir.Check (JBir.CheckCast (expr, object_type)) when !JConfig.translate_checks -> + let sil_type = JTransType.expr_type context expr + and ids, instrs, sil_expr = expression context pc expr + and ret_id = Ident.create_fresh Ident.knormal + and sizeof_expr = + JTransType.sizeof_of_object_type program tenv object_type Sil.Subtype.subtypes_instof in + let check_cast = Sil.Const (Sil.Cfun SymExec.ModelBuiltins.__instanceof) in + let args = [(sil_expr, sil_type); (sizeof_expr, Sil.Tvoid)] in + let call = Sil.Call([ret_id], check_cast, args, loc, Sil.cf_default) in + let res_ex = Sil.Var ret_id in + let is_instance_node = + let check_is_false = Sil.BinOp (Sil.Ne, res_ex, Sil.exp_zero) in + let asssume_instance_of = Sil.Prune (check_is_false, loc, true, Sil.Ik_if) + and instance_of_kind = Cfg.Node.Prune_node (true, Sil.Ik_if, "Is instance") in + create_node instance_of_kind ids (instrs @ [call; asssume_instance_of]) + and throw_cast_exception_node = + let check_is_true = Sil.BinOp (Sil.Ne, res_ex, Sil.exp_one) in + let asssume_not_instance_of = Sil.Prune (check_is_true, loc, true, Sil.Ik_if) + and throw_cast_exception_kind = Cfg.Node.Stmt_node "Class cast exception" + and cce_cn = JBasics.make_cn JConfig.cce_cl in + let class_type = JTransType.get_class_type program tenv cce_cn + and class_type_np = JTransType.get_class_type_no_pointer program tenv cce_cn in + let sizeof_exp = Sil.Sizeof (class_type_np, Sil.Subtype.exact) in + let args = [(sizeof_exp, class_type)] in + let ret_id = Ident.create_fresh Ident.knormal in + let new_instr = Sil.Call([ret_id], builtin_new, args, loc, Sil.cf_default) in + let constr_ms = JBasics.make_ms JConfig.constructor_name [] None in + let (constr_procdesc, constr_procname, call_ids, call_instrs) = + method_invocation context loc pc None cce_cn constr_ms + (Some (Sil.Var ret_id, class_type)) [] I_Special Static in + let sil_exn = Sil.Const (Sil.Cexn (Sil.Var ret_id)) in + let ret_var = Cfg.Procdesc.get_ret_var (JContext.get_procdesc context) in + let ret_type = Cfg.Procdesc.get_ret_type (JContext.get_procdesc context) in + let set_instr = Sil.Set (Sil.Lvar ret_var, ret_type, sil_exn, loc) in + let cce_instrs = + instrs @ [call; asssume_not_instance_of] @ (new_instr :: call_instrs) @ [set_instr] in + create_node throw_cast_exception_kind (ids @ call_ids) cce_instrs in + + Prune (is_instance_node, throw_cast_exception_node) + + | _ -> Skip + with Frontend_error s -> + JUtils.log "Skipping because of: %s@." s; + Skip diff --git a/infer/src/java/jTrans.mli b/infer/src/java/jTrans.mli new file mode 100644 index 000000000..0653e44a0 --- /dev/null +++ b/infer/src/java/jTrans.mli @@ -0,0 +1,43 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +open Javalib_pack +open Sawja_pack + +(** If active, disable special treatment of static final fields. *) +val no_static_final : bool ref + +(** Data structure for storing the results of the translation of an instruction. *) +type translation = + | Skip + | Instr of Cfg.Node.t + | Prune of Cfg.Node.t * Cfg.Node.t + | Loop of Cfg.Node.t * Cfg.Node.t * Cfg.Node.t + +(** data structure to identify whether a method is defined in the given program *) +type defined_status = + | Defined of Cfg.Procdesc.t + | Called of Cfg.Procdesc.t + +type method_kind = + | Static + | Non_Static + +(** returns the procedure description of the given method and creates it if it hasn't been created before *) +val get_method_procdesc : JClasspath.program -> Cfg.cfg -> Sil.tenv -> JBasics.class_name -> JBasics.method_signature -> method_kind -> defined_status + +(** [create_local_procdesc linereader cfg tenv program m] creates a procedure description for the method m and adds it to cfg *) +val create_local_procdesc : JClasspath.program -> Printer.LineReader.t -> Cfg.cfg -> Sil.tenv -> JCode.jcode Javalib.interface_or_class -> JCode.jcode Javalib.jmethod -> unit + +val get_method_kind : JCode.jcode Javalib.jmethod -> method_kind + +(** returns the implementation of a given method *) +val get_implementation : JCode.jcode Javalib.concrete_method -> JBir.t + +(** translates an instruction into a statement node or prune nodes in the cfg *) +val instruction : JContext.t -> int -> JBir.instr -> translation + +exception Frontend_error of string diff --git a/infer/src/java/jTransExn.ml b/infer/src/java/jTransExn.ml new file mode 100644 index 000000000..7b5c7e75a --- /dev/null +++ b/infer/src/java/jTransExn.ml @@ -0,0 +1,115 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +open Javalib_pack +open Sawja_pack + +module E = Logging + +let create_handler_table impl = + let handler_tb = Hashtbl.create 1 in + let collect (pc, exn_handler) = + try + let handlers = Hashtbl.find handler_tb pc in + Hashtbl.replace handler_tb pc (exn_handler:: handlers) + with Not_found -> + Hashtbl.add handler_tb pc [exn_handler] in + List.iter collect (JBir.exception_edges impl); + handler_tb + +let translate_exceptions context exit_nodes get_body_nodes handler_table = + let catch_block_table = Hashtbl.create 1 in + let exn_message = "exception handler" in + let procdesc = JContext.get_procdesc context in + let cfg = JContext.get_cfg context in + let create_node loc node_kind instrs temps = Cfg.Node.create cfg loc node_kind instrs procdesc temps in + let ret_var = Cfg.Procdesc.get_ret_var procdesc in + let ret_type = Cfg.Procdesc.get_ret_type procdesc in + let id_ret_val = Ident.create_fresh Ident.knormal in + let id_exn_val = Ident.create_fresh Ident.knormal in (* this is removed in the true branches, and in the false branch of the last handler *) + let create_entry_node loc = + let instr_get_ret_val = Sil.Letderef (id_ret_val, Sil.Lvar ret_var, ret_type, loc) in + let id_deactivate = Ident.create_fresh Ident.knormal in + let instr_deactivate_exn = Sil.Set (Sil.Lvar ret_var, ret_type, Sil.Var id_deactivate, loc) in + let instr_unwrap_ret_val = + let unwrap_builtin = Sil.Const (Sil.Cfun SymExec.ModelBuiltins.__unwrap_exception) in + Sil.Call([id_exn_val], unwrap_builtin, [(Sil.Var id_ret_val, ret_type)], loc, Sil.cf_default) in + create_node loc Cfg.Node.exn_handler_kind [instr_get_ret_val; instr_deactivate_exn; instr_unwrap_ret_val] [id_ret_val; id_deactivate] in + let create_entry_block pc handler_list = + try + ignore (Hashtbl.find catch_block_table handler_list) + with Not_found -> + let collect succ_nodes last_handler rethrow_exception handler = + let catch_nodes = get_body_nodes handler.JBir.e_handler in + let loc = match catch_nodes with + | n:: _ -> Cfg.Node.get_loc n + | [] -> Sil.dummy_location in + let exn_type = + let class_name = + match handler.JBir.e_catch_type with + | None -> JBasics.make_cn "java.lang.Exception" + | Some cn -> cn in + match JTransType.get_class_type (JContext.get_program context) (JContext.get_tenv context) class_name with + | Sil.Tptr (typ, _) -> typ + | _ -> assert false in + let id_instanceof = Ident.create_fresh Ident.knormal in + let instr_call_instanceof = + let instanceof_builtin = Sil.Const (Sil.Cfun SymExec.ModelBuiltins.__instanceof) in + let args = [(Sil.Var id_exn_val, Sil.Tptr(exn_type, Sil.Pk_pointer)); (Sil.Sizeof (exn_type, Sil.Subtype.exact), Sil.Tvoid)] in + Sil.Call ([id_instanceof], instanceof_builtin, args, loc, Sil.cf_default) in + let if_kind = Sil.Ik_switch in + let instr_prune_true = Sil.Prune (Sil.Var id_instanceof, loc, true, if_kind) in + let instr_prune_false = Sil.Prune (Sil.UnOp(Sil.LNot, Sil.Var id_instanceof, None), loc, false, if_kind) in + let instr_set_catch_var = + let catch_var = JContext.set_pvar context handler.JBir.e_catch_var ret_type in + Sil.Set (Sil.Lvar catch_var, ret_type, Sil.Var id_exn_val, loc) in + let instr_rethrow_exn = Sil.Set (Sil.Lvar ret_var, ret_type, Sil.Const (Sil.Cexn (Sil.Var id_exn_val)), loc) in + let node_kind_true = Cfg.Node.Prune_node (true, if_kind, exn_message) in + let node_kind_false = Cfg.Node.Prune_node (false, if_kind, exn_message) in + let node_true = + let instrs_true = [instr_call_instanceof; instr_prune_true; instr_set_catch_var] in + let ids_true = [id_exn_val; id_instanceof] in + create_node loc node_kind_true instrs_true ids_true in + let node_false = + let instrs_false = [instr_call_instanceof; instr_prune_false] @ (if rethrow_exception then [instr_rethrow_exn] else []) in + let ids_false = (if last_handler then [id_exn_val] else []) @ [id_instanceof] in + create_node loc node_kind_false instrs_false ids_false in + Cfg.Node.set_succs_exn node_true catch_nodes exit_nodes; + Cfg.Node.set_succs_exn node_false succ_nodes exit_nodes; + let is_finally = handler.JBir.e_catch_type = None in + if is_finally + then [node_true] (* TODO (#4759480): clean up the translation so prune nodes are not created at all *) + else [node_true; node_false] in + let is_last_handler = ref true in + let process_handler succ_nodes handler = (* process handlers starting from the last one *) + let is_finally_handler = handler.JBir.e_catch_type = None in + let remove_temps = !is_last_handler in (* remove temporary variables on last handler *) + let rethrow_exception = !is_last_handler && not is_finally_handler in (* rethrow exception if there is no finally *) + is_last_handler := false; + collect succ_nodes remove_temps rethrow_exception handler in + + let nodes_first_handler = List.fold_left process_handler exit_nodes (List.rev handler_list) in + let loc = match nodes_first_handler with + | n:: _ -> Cfg.Node.get_loc n + | [] -> Sil.dummy_location in + let entry_node = create_entry_node loc in + Cfg.Node.set_succs_exn entry_node nodes_first_handler exit_nodes; + Hashtbl.add catch_block_table handler_list [entry_node] in + Hashtbl.iter (fun pc handler_list -> create_entry_block pc handler_list) handler_table; + catch_block_table + +let create_exception_handlers context exit_nodes get_body_nodes impl = + match JBir.exc_tbl impl with + | [] -> fun pc -> exit_nodes + | _ -> + let handler_table = create_handler_table impl in + let catch_block_table = translate_exceptions context exit_nodes get_body_nodes handler_table in + fun pc -> + try + let handler_list = Hashtbl.find handler_table pc in + Hashtbl.find catch_block_table handler_list + with Not_found -> + exit_nodes diff --git a/infer/src/java/jTransExn.mli b/infer/src/java/jTransExn.mli new file mode 100644 index 000000000..7c7b433e5 --- /dev/null +++ b/infer/src/java/jTransExn.mli @@ -0,0 +1,5 @@ +open Javalib_pack +open Sawja_pack + + +val create_exception_handlers : JContext.t -> Cfg.Node.t list -> (int -> Cfg.Node.t list) -> JBir.t -> int -> Cfg.Node.t list diff --git a/infer/src/java/jTransStaticField.ml b/infer/src/java/jTransStaticField.ml new file mode 100644 index 000000000..ed44b9370 --- /dev/null +++ b/infer/src/java/jTransStaticField.ml @@ -0,0 +1,208 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +open Javalib_pack +open Sawja_pack + + +(* line numbers where the code for the initialization of the static final fields starts *) +let field_final_pcs : int list ref = ref [] + +(* line numbers where the code for the initialization of the static nonfinal fields starts *) +let field_nonfinal_pcs : int list ref = ref [] + +let reset_pcs () = + field_final_pcs := []; + field_nonfinal_pcs := [] + +let sort_pcs () = + field_final_pcs := (List.sort Pervasives.compare !field_final_pcs); + field_nonfinal_pcs := (List.sort Pervasives.compare !field_nonfinal_pcs) + +let is_basic_type fs = + let vt = (JBasics.fs_type fs) in + match vt with + | JBasics.TBasic bt -> true + | JBasics.TObject ot -> false + +(** Returns whether the node contains static final fields +that are not of a primitive type or String. *) +let rec has_static_final_fields node = + let detect fs f test = + test || (Javalib.is_static_field f && Javalib.is_final_field f) in + JBasics.FieldMap.fold detect (Javalib.get_fields node) false +(* Seems that there is no function "exists" on this implementation of *) +(* Patricia trees *) + +(** collects the code line where the fields are initialised. The list is +reversed in order to access the previous element in the list easier (as the successor.) *) +let collect_field_pc instrs field_pc_list = + let aux pc instr = + match instr with + | JBir.AffectStaticField (cn, fs, e) -> + field_pc_list := (fs, pc)::!field_pc_list + | _ -> () in + (Array.iteri aux instrs); + (List.rev !field_pc_list) + +(** Changes every position in the code where a static field is set to a value, +to returning that value *) +let add_return_field instrs = + let aux instr = + match instr with + | JBir.AffectStaticField (cn, fs, e) -> + JBir.Return (Some e) + | _ -> instr in + (Array.map aux instrs) + +(** Given a list with the lines where the fields are initialised, +finds the line where the code for the initialisation of the given field starts, +which is the line after the previous field has been initialised. *) +let rec find_pc field list = + match list with + | (fs, pc):: rest -> + if JBasics.fs_equal field fs then + try + let (nfs, npc) = List.hd rest in + npc + 1 + with hd -> 1 + else (find_pc field rest) + | [] -> -1 + +(* Removes the lines of code for initializing nonfinal static fields. *) +let remove_nonfinal_instrs code end_pc = + try + sort_pcs (); + let rec aux2 pc = + let next_pc = pc + 1 in + if not (List.mem pc !field_final_pcs) && not (List.mem pc !field_nonfinal_pcs) then + begin + Array.set code pc JBir.Nop; + if next_pc < end_pc then aux2 next_pc + end + else () in + let aux pc instr = + if List.mem pc !field_nonfinal_pcs then + begin + Array.set code pc JBir.Nop; + aux2 (pc +1) + end + else () in + Array.iteri aux code + with Invalid_argument s -> assert false + +let has_unclear_control_flow code = + let aux instr nok = + match instr with + | JBir.Goto n -> true + | _ -> nok in + Array.fold_right aux code false + + +(** In the initialiser of static fields, we add instructions +for returning the field selected by the parameter. *) +(* The constant s means the parameter field of the function. +Note that we remove the initialisation of non - final static fields. *) +let rec static_field_init_complex cn code fields length = + let code = Array.append [| (JBir.Goto length ) |] code in + let s = JConfig.field_cst in + let field_pc_list = ref [] in + let _ = collect_field_pc code field_pc_list in + let code = add_return_field code in + let rec aux s fields = + match fields with + | (fs, field):: rest -> + let pc = find_pc fs !field_pc_list in + if Javalib.is_static_field field && Javalib.is_final_field field && pc <> -1 then + let _ = field_final_pcs := pc::!field_final_pcs in + let fs_const = JBir.Const (`String (JBasics.make_jstr (JBasics.fs_name fs))) in + let arg_const = JBir.Const (`String (JBasics.make_jstr s)) in + let arr = [| JBir.Ifd ((`Eq, fs_const, arg_const), pc); JBir.Nop |] in + let rest_instrs = (aux s rest) in + Array.append arr rest_instrs + else + let _ = + if Javalib.is_static_field field && pc <> -1 then + field_nonfinal_pcs := pc::!field_nonfinal_pcs in + aux s rest + | [] -> [| JBir.Nop |] in + let new_instrs = aux s fields in + let code = Array.append code new_instrs in + remove_nonfinal_instrs code length; + reset_pcs (); + code + +(** In the initialiser of static fields, we add instructions +for returning the field selected by the parameter without changing +the control flow of the original code. *) +let rec static_field_init_simple cn code fields length = + let s = JConfig.field_cst in + let rec aux s pc fields = + match fields with + | (fs, field):: rest -> + if Javalib.is_static_field field && Javalib.is_final_field field then + let npc = pc + 2 in + let fs_const = JBir.Const (`String (JBasics.make_jstr (JBasics.fs_name fs))) in + let arg_const = JBir.Const (`String (JBasics.make_jstr s)) in + let arr = [| JBir.Ifd ((`Ne, fs_const, arg_const), npc); JBir.Return (Some (JBir.StaticField (cn, fs))) |] in + let rest_instrs = (aux s npc rest) in + Array.append arr rest_instrs + else (aux s pc rest) + | [] -> [| JBir.Nop |] in + let new_instrs = aux s length fields in + let code = Array.append code new_instrs in + code + +(** In the initialiser of static fields, we add instructions +for returning the field selected by the parameter. In normal +cases the code for the initialisation of each field is clearly separated +from the code for the initialisation of the next field. However, in some cases +the fields are initialised in static blocks in which they may use try and catch. +In these cases it is not possible to separate the code for the initialisation +of each field, so we do not change the original code, but append intructions +for returning the selected field. *) +let rec static_field_init node cn code = + try + let field_list = JBasics.FieldMap.elements (Javalib.get_fields node) in + (* TODO: this translation to a list can be removed and map iterators can be used afterward *) + let length = Array.length code in + Array.set code (length -1) JBir.Nop; + (* TODO: make sure this modification of the array has no side effect *) + let code = + if has_unclear_control_flow code then + static_field_init_simple cn code field_list length + else static_field_init_complex cn code field_list length in + code + with Not_found -> code + +(* when accessing a static final field, we call the initialiser method. *) +let translate_instr_static_field context callee_procdesc fs field_type loc = + let cg = JContext.get_cg context in + let caller_procdesc = JContext.get_procdesc context in + let ret_id = Ident.create_fresh Ident.knormal in + let caller_procname = (Cfg.Procdesc.get_proc_name caller_procdesc) in + let callee_procname = Cfg.Procdesc.get_proc_name callee_procdesc in + let callee_fun = Sil.Const (Sil.Cfun callee_procname) in + let field_arg = Sil.Const (Sil.Cstr (JBasics.fs_name fs)) in + let call_instr = Sil.Call([ret_id], callee_fun, [field_arg, field_type], loc, Sil.cf_default) in + Cg.add_edge cg caller_procname callee_procname; + ([ret_id], [call_instr], Sil.Var ret_id) + + +let is_static_final_field context cn fs = + let node = + match JClasspath.lookup_node cn (JContext.get_program context) with + | None -> assert false + | Some n -> n in + try + let f = Javalib.get_field node fs in + let is_static = Javalib.is_static_field f in + let is_final = Javalib.is_final_field f in + (is_static && is_final) + with Not_found -> false +(* assert false *) +(* TODO: should nornally not be reachable but it appears to be on the *) +(* standard library. Probably a JBir translation issue *) diff --git a/infer/src/java/jTransStaticField.mli b/infer/src/java/jTransStaticField.mli new file mode 100644 index 000000000..b6db336ee --- /dev/null +++ b/infer/src/java/jTransStaticField.mli @@ -0,0 +1,14 @@ + +open Javalib_pack +open Sawja_pack + + +val is_static_final_field : JContext.t -> JBasics.class_name -> JBasics.field_signature -> bool + +val has_static_final_fields : JCode.jcode Javalib.interface_or_class -> bool + +val translate_instr_static_field : JContext.t -> Cfg.Procdesc.t -> JBasics.field_signature -> Sil.typ -> +Sil.location -> Ident.t list * Sil.instr list * Sil.exp + + +val static_field_init : JCode.jcode Javalib.interface_or_class -> JBasics.class_name -> JBir.instr array -> JBir.instr array diff --git a/infer/src/java/jTransType.ml b/infer/src/java/jTransType.ml new file mode 100644 index 000000000..728902d2b --- /dev/null +++ b/infer/src/java/jTransType.ml @@ -0,0 +1,510 @@ +open Javalib_pack +open Sawja_pack + + +open Utils + +(** Type transformations between Javalib datatypes and sil datatypes *) + + +exception Type_tranlsation_error of string + + +let basic_type = function + | `Int -> Sil.Tint Sil.IInt + | `Bool -> Sil.Tint Sil.IBool + | `Byte -> Sil.Tint Sil.IChar + | `Char -> Sil.Tint Sil.IChar + | `Double -> Sil.Tfloat Sil.FDouble + | `Float -> Sil.Tfloat Sil.FFloat + | `Long -> Sil.Tint Sil.ILong + | `Short -> Sil.Tint Sil.IShort + + +let cast_type = function + | JBir.F2I + | JBir.L2I + | JBir.D2I -> Sil.Tint Sil.IInt + | JBir.D2L + | JBir.F2L + | JBir.I2L -> Sil.Tint Sil.ILong + | JBir.I2F + | JBir.L2F + | JBir.D2F -> Sil.Tfloat Sil.FFloat + | JBir.L2D + | JBir.F2D + | JBir.I2D -> Sil.Tfloat Sil.FDouble + | JBir.I2B -> Sil.Tint Sil.IBool + | JBir.I2C -> Sil.Tint Sil.IChar + | JBir.I2S -> Sil.Tint Sil.IShort + + +let const_type const = + match const with + | `String str -> (JBasics.TObject (JBasics.TClass (JBasics.make_cn JConfig.string_cl))) + | `Class cl -> (JBasics.TObject (JBasics.TClass (JBasics.make_cn JConfig.class_cl))) + | `Double _ -> (JBasics.TBasic `Double) + | `Int _ -> (JBasics.TBasic`Int) + | `Float _ -> (JBasics.TBasic`Float) + | `Long _ -> (JBasics.TBasic`Long) + | `ANull -> JConfig.obj_type + + +let typename_of_classname cn = + Sil.TN_csu (Sil.Class, (Mangled.from_string (JBasics.cn_name cn))) + + +let rec get_named_type vt = + match vt with + | JBasics.TBasic bt -> basic_type bt + | JBasics.TObject ot -> + begin + match ot with + | JBasics.TArray vt -> + let content_type = get_named_type vt in + let size = Sil.exp_get_undefined false in + Sil.Tptr (Sil.Tarray (content_type, size), Sil.Pk_pointer) (* unknown size *) + | JBasics.TClass cn -> Sil.Tptr (Sil.Tvar (typename_of_classname cn), Sil.Pk_pointer) + end + + +let extract_cn_type_np typ = + match typ with + | Sil.Tptr(vtyp, Sil.Pk_pointer) -> + vtyp + | _ -> typ + +let rec create_array_type typ dim = + if dim > 0 then + let content_typ = create_array_type typ (dim - 1) in + let size = Sil.exp_get_undefined false in + Sil.Tptr(Sil.Tarray (content_typ, size), Sil.Pk_pointer) + else typ + +let extract_cn_no_obj typ = + match typ with + | Sil.Tptr (Sil.Tstruct (_, _, Sil.Class, Some classname, _, _, _), Sil.Pk_pointer) -> + let class_name = (Mangled.to_string classname) in + if class_name = JConfig.object_cl then None + else + let jbir_class_name = (JBasics.make_cn class_name) in + Some jbir_class_name + | _ -> None + +(** Printing types *) +let rec array_type_to_string vt = + let s = + match vt with + | JBasics.TBasic bt -> + (match bt with + | `Bool -> JConfig.boolean_code + | `Byte -> JConfig.byte_code + | `Char -> JConfig.char_code + | `Double -> JConfig.double_code + | `Float -> JConfig.float_code + | `Int -> JConfig.int_code + | `Long -> JConfig.long_code + | `Short -> JConfig.short_code) + | JBasics.TObject ot -> object_type_to_string' ot in + "["^s +and object_type_to_string' ot = + match ot with + | JBasics.TClass class_name -> JConfig.class_code (JBasics.cn_name class_name) + | JBasics.TArray vt -> (array_type_to_string vt) + +let object_type_to_string ot = + match ot with + | JBasics.TClass class_name -> (JBasics.cn_name class_name) + | JBasics.TArray vt -> (array_type_to_string vt) + +let string_of_basic_type = function + | `Bool -> JConfig.boolean_st + | `Byte -> JConfig.byte_st + | `Char -> JConfig.char_st + | `Double -> JConfig.double_st + | `Float -> JConfig.float_st + | `Int -> JConfig.int_st + | `Long -> JConfig.long_st + | `Short -> JConfig.short_st + +let rec string_of_type vt = + match vt with + | JBasics.TBasic bt -> string_of_basic_type bt + | JBasics.TObject ot -> + begin + match ot with + | JBasics.TArray vt -> (string_of_type vt)^"[]" + | JBasics.TClass cn -> JBasics.cn_name cn + end + +let package_to_string p = + let rec aux p = + match p with + | [] -> "" + | p::[] -> p + | p:: rest -> p^"."^(aux rest) in + match p with + | [] -> None + | _ -> Some (aux p) + + +let cn_to_java_type cn = + (package_to_string (JBasics.cn_package cn), + (JBasics.cn_simple_name cn)) + + +let rec vt_to_java_type vt = + match vt with + | JBasics.TBasic bt -> None, string_of_basic_type bt + | JBasics.TObject ot -> + begin + match ot with + | JBasics.TArray vt -> None, (string_of_type vt)^"[]" + | JBasics.TClass cn -> cn_to_java_type cn + + end + +let method_signature_names ms = + let return_type_name = + match JBasics.ms_rtype ms with + | None -> + if JBasics.ms_name ms = JConfig.constructor_name then + None + else + Some (None, JConfig.void) + | Some vt -> Some (vt_to_java_type vt) in + let rec args_to_signature l = + match l with + | [] -> [] + | vt:: tail -> (vt_to_java_type vt) :: (args_to_signature tail) in + let method_name = JBasics.ms_name ms in + let args_types = args_to_signature (JBasics.ms_args ms) in + (return_type_name, method_name, args_types) + + +(* create a mangled procname from an abstract or concrete method *) +let get_method_procname cn ms = + let return_type_name, method_name, args_type_name = method_signature_names ms in + let class_name = cn_to_java_type cn in + Procname.mangled_java class_name return_type_name method_name args_type_name + + +let get_class_procnames cn node = + let collect jmethod procnames = + let ms = (Javalib.get_method_signature jmethod) in + (get_method_procname cn ms) :: procnames in + Javalib.m_fold collect node [] + + +let create_fieldname cn fs = + let fieldname cn fs = + let fieldname = (JBasics.fs_name fs) in + let classname = (JBasics.cn_name cn) in + Mangled.from_string (classname^"."^fieldname) in + Ident.create_fieldname (fieldname cn fs) 0 + + +let create_sil_class_field cn cf = + let fs = cf.Javalib.cf_signature in + let field_name = create_fieldname cn fs + and field_type = get_named_type (JBasics.fs_type fs) + and annotation = JAnnotation.translate_item cf.Javalib.cf_annotations in + (field_name, field_type, annotation) + + +(** Collect static field if static is true, otherwise non-static ones. *) +let collect_class_field static cn cf l = + if Javalib.is_static_field (Javalib.ClassField cf) <> static then l + else (create_sil_class_field cn cf) :: l + + +(** Collect an interface field. *) +let collect_interface_field cn inf l = + let fs = inf.Javalib.if_signature in + let field_type = get_named_type (JBasics.fs_type fs) in + let field_name = create_fieldname cn fs in + let annotation = JAnnotation.translate_item inf.Javalib.if_annotations in + (field_name, field_type, annotation) :: l + + +let get_all_fields program static cn = + let compare (name1, _, _) (name2, _, _) = + Ident.fieldname_compare name1 name2 in + let rec loop classname = + match JClasspath.lookup_node classname program with + | Some (Javalib.JClass jclass) -> + let super_fields = + match jclass.Javalib.c_super_class with + | None -> [] + | Some super_classname -> loop super_classname in + let current_fields = + Javalib.cf_fold (collect_class_field static classname) (Javalib.JClass jclass) [] in + (list_sort compare current_fields) @ super_fields + | Some (Javalib.JInterface jinterface) when static -> + let current_fields = + Javalib.if_fold (collect_interface_field classname) (Javalib.JInterface jinterface) [] in + list_sort compare current_fields + | _ -> [] in + loop cn + + +let dummy_type cn = + let classname = Mangled.from_string (JBasics.cn_name cn) in + Sil.Tstruct ([], [], Sil.Class, Some classname, [], [], Sil.item_annotation_empty) + + +let collect_models_class_fields classpath_field_map static cn cf l = + if Javalib.is_static_field (Javalib.ClassField cf) <> static then l + else + let (field_name, field_type, annotation) = create_sil_class_field cn cf in + try + let classpath_ft = Ident.FieldMap.find field_name classpath_field_map in + if Sil.typ_equal classpath_ft field_type then l + else + (* TODO (#6711750): fix type equality for arrays before failing here *) + let () = Logging.stderr "Found inconsistent types for %s\n\tclasspath: %a\n\tmodels: %a\n@." + (Ident.fieldname_to_string field_name) + (Sil.pp_typ_full pe_text) classpath_ft + (Sil.pp_typ_full pe_text) field_type in l + with Not_found -> + (field_name, field_type, annotation):: l + + +let add_model_fields program (static_fields, nonstatic_fields) cn = + let collect_fields = + list_fold_left (fun map (fn, ft, _) -> Ident.FieldMap.add fn ft map) Ident.FieldMap.empty in + try + match JBasics.ClassMap.find cn (JClasspath.get_models program) with + | Javalib.JClass _ as jclass -> + let updated_static_fields = + Javalib.cf_fold + (collect_models_class_fields (collect_fields static_fields) true cn) + jclass + static_fields + and updated_nonstatic_fields = + Javalib.cf_fold + (collect_models_class_fields (collect_fields nonstatic_fields) false cn) + jclass + nonstatic_fields in + (updated_static_fields, updated_nonstatic_fields) + | _ -> (static_fields, nonstatic_fields) + with Not_found -> (static_fields, nonstatic_fields) + + +let rec create_sil_type program tenv cn = + match JClasspath.lookup_node cn program with + | None -> dummy_type cn + | Some node -> + let create_super_list interface_names = + (list_map (fun i -> Mangled.from_string (JBasics.cn_name i)) interface_names) in + let (super_list, nonstatic_fields, static_fields, item_annotation) = + match node with + | Javalib.JInterface jinterface -> + let static_fields = get_all_fields program true cn in + let sil_interface_list = list_map (fun c -> (Sil.Class, c)) (create_super_list jinterface.Javalib.i_interfaces) in + let item_annotation = JAnnotation.translate_item jinterface.Javalib.i_annotations in + (sil_interface_list, [], static_fields, item_annotation) + | Javalib.JClass jclass -> + (* TODO: create two functions to get static fields and non-static ones *) + let static_fields, nonstatic_fields = + let classpath_static_fields = get_all_fields program true cn + and classpath_nonstatic_fields = get_all_fields program false cn in + add_model_fields program (classpath_static_fields, classpath_nonstatic_fields) cn in + let item_annotation = JAnnotation.translate_item jclass.Javalib.c_annotations in + let interface_list = create_super_list jclass.Javalib.c_interfaces in + let super_classname_list = + match jclass.Javalib.c_super_class with + | None -> interface_list (* base case of the recursion *) + | Some super_cn -> + let super_classname = + match get_class_type_no_pointer program tenv super_cn with + | Sil.Tstruct (_, _, _, Some classname, _, _, _) -> classname + | _ -> assert false in + super_classname :: interface_list in + let super_sil_classname_list = + list_map (fun c -> (Sil.Class, c)) super_classname_list in + (super_sil_classname_list, nonstatic_fields, static_fields, item_annotation) in + let classname = Mangled.from_string (JBasics.cn_name cn) in + let method_procnames = get_class_procnames cn node in + Sil.Tstruct (nonstatic_fields, static_fields, Sil.Class, Some classname, super_list, method_procnames, item_annotation) + +and get_class_type_no_pointer program tenv cn = + let named_type = typename_of_classname cn in + let class_type_np = + match Sil.tenv_lookup tenv named_type with + | None -> create_sil_type program tenv cn + | Some t -> t in + Sil.tenv_add tenv named_type class_type_np; + class_type_np + + +let get_class_type program tenv cn = + let t = get_class_type_no_pointer program tenv cn in + Sil.Tptr (t, Sil.Pk_pointer) + + +(** translate an object type *) +let rec object_type program tenv ot = + match ot with + | JBasics.TClass cn -> get_class_type program tenv cn + | JBasics.TArray at -> + let size = Sil.exp_get_undefined false in + Sil.Tptr (Sil.Tarray (value_type program tenv at, size), Sil.Pk_pointer) +(** translate a value type *) +and value_type program tenv vt = + match vt with + | JBasics.TBasic bt -> basic_type bt + | JBasics.TObject ot -> object_type program tenv ot + + +(** Translate object types into Sil.Sizeof expressions *) +let sizeof_of_object_type program tenv ot subtypes = + match object_type program tenv ot with + | Sil.Tptr (Sil.Tarray (vtyp, s), Sil.Pk_pointer) -> + let typ = (Sil.Tarray (vtyp, s)) in + Sil.Sizeof (typ, subtypes) + | Sil.Tptr (typ, _) -> + Sil.Sizeof (typ, subtypes) + | _ -> + raise (Type_tranlsation_error "Pointer or array type expected in tenv") + + +(** return the name and type of a formal parameter, looking up the class name in case of "this" *) +let param_type program tenv cn name vt = + if (JBir.var_name_g name) = JConfig.this + then get_class_type program tenv cn + else value_type program tenv vt + + +let get_var_type_from_sig context var = + let program = JContext.get_program context in + try + let tenv = JContext.get_tenv context in + let vt', var' = + list_find + (fun (vt', var') -> JBir.var_equal var var') + (JBir.params (JContext.get_impl context)) in + Some (param_type program tenv (JContext.get_cn context) var' vt') + with Not_found -> None + + +let get_var_type context var = + let typ_opt = JContext.get_var_type context var in + match typ_opt with + | Some atype -> typ_opt + | None -> get_var_type_from_sig context var + + +let extract_array_type typ = + match typ with + | Sil.Tptr(Sil.Tarray (vtyp, _), Sil.Pk_pointer) -> vtyp + | _ -> typ + + +(** translate the type of an expression, looking in the method signature for formal parameters +this is because variables in expressions do not have accurate types *) +let rec expr_type context expr = + let program = JContext.get_program context in + let tenv = JContext.get_tenv context in + match expr with + | JBir.Const const -> value_type program tenv (const_type const) + | JBir.Var (vt, var) -> + (match get_var_type context var with + | Some typ -> typ + | None -> (value_type program tenv vt)) + | JBir.Binop ((JBir.ArrayLoad typ), e1, e2) -> + let typ = expr_type context e1 in + (extract_array_type typ) + | _ -> value_type program tenv (JBir.type_of_expr expr) + + +(** Returns the return type of the method based on the return type +specified in ms. If the method is the initialiser, return the type +Object instead. *) +let return_type program tenv ms meth_kind = + if meth_kind = JContext.Init then + get_class_type program tenv (JBasics.make_cn JConfig.object_cl) + else + match JBasics.ms_rtype ms with + | None -> Sil.Tvoid + | Some vt -> value_type program tenv vt + + +let update_tenv tenv program = + let add cn _ = + let class_typename = typename_of_classname cn in + Sil.tenv_add tenv class_typename (create_sil_type program tenv cn) in + JBasics.ClassMap.iter add (JClasspath.get_classmap program) + + +(* Update a type environment with the types found in the classpath *) +let saturate_tenv_with_classpath classpath tenv = + let jar_tenv_filename = + let root = Filename.concat Config.default_in_zip_results_dir Config.captured_dir_name in + Filename.concat root Config.global_tenv_filename in + let temp_tenv_filename = + DB.filename_from_string (Filename.temp_file "tmp_" Config.global_tenv_filename) in + let typename_of_classname classname = + Sil.TN_csu (Sil.Class, classname) in + let rec is_useful_subtype jar_tenv = function + | Sil.TN_csu (Sil.Class, classname) when + Mangled.equal classname JConfig.java_lang_object_classname -> false + | typename when Sil.tenv_mem tenv typename -> true + | typename -> + begin + match Sil.tenv_lookup jar_tenv typename with + | None + | Some (Sil.Tstruct (_, _, _, _, [], _, _)) -> false + | Some (Sil.Tstruct (_, _, _, _, supers, _, _)) -> + list_exists + (is_useful_subtype jar_tenv) + (list_map (fun (_, c) -> typename_of_classname c) supers) + | _ -> assert false + end in + let transfer_type jar_tenv typename typ = + if not (Sil.tenv_mem tenv typename) then + if is_useful_subtype jar_tenv typename then + Sil.tenv_add tenv typename typ in + let extract_tenv zip_channel = + try + let entry = Zip.find_entry zip_channel jar_tenv_filename in + let temp_tenv_file = DB.filename_to_string temp_tenv_filename in + let () = Zip.copy_entry_to_file zip_channel entry temp_tenv_file in + match Sil.load_tenv_from_file temp_tenv_filename with + | None -> None + | Some jar_tenv -> Some jar_tenv + with Not_found -> None in + let update path = + if not (Filename.check_suffix path ".jar") then () + else + let zip_channel = Zip.open_in path in + match extract_tenv zip_channel with + | None -> () + | Some jar_tenv -> Sil.tenv_iter (transfer_type jar_tenv) jar_tenv; + Zip.close_in zip_channel in + let paths = + let l = JClasspath.split_classpath classpath in + if !JClasspath.models_jar = "" then l + else !JClasspath.models_jar :: l in + list_iter update paths; + DB.file_remove temp_tenv_filename + + +(* TODO #6604630: remove *) +let never_returning_null = + (* class, method name, [arg type], ret_type of methods to model as never returning null *) + let fragment_type = "android.support.v4.app.Fragment" in + let never_null_method_sigs = + [ + (fragment_type, "getContext", [], "android.content.Context"); + (fragment_type, "getActivity", [], "android.support.v4.app.FragmentActivity") + ] in + let make_procname = function + | (class_name, method_name, arg_types, ret_type) -> + let return_cn = JBasics.make_cn ret_type in + let cn = JBasics.make_cn class_name + and ms = + JBasics.make_ms + method_name arg_types (Some (JBasics.TObject (JBasics.TClass return_cn))) in + get_method_procname cn ms in + list_map make_procname never_null_method_sigs diff --git a/infer/src/java/jTransType.mli b/infer/src/java/jTransType.mli new file mode 100644 index 000000000..05e8c60a0 --- /dev/null +++ b/infer/src/java/jTransType.mli @@ -0,0 +1,74 @@ +open Javalib_pack +open Sawja_pack + +(** transforms a Java type into a Sil named type *) +val get_named_type : JBasics.value_type -> Sil.typ + +(** transforms a Java class name into a Sil class name *) +val typename_of_classname : JBasics.class_name -> Sil.typename + +(** returns a name for a field based on a class name and a field name *) +val create_fieldname : JBasics.class_name -> JBasics.field_signature -> Ident.fieldname + +(** returns a procedure name based on the class name and the method's signature. *) +val get_method_procname : JBasics.class_name -> JBasics.method_signature -> Procname.t + +(** [get_class_type_no_pointer tenv cn] returns the sil type representation of the class without the pointer part *) +val get_class_type_no_pointer: JClasspath.program -> Sil.tenv -> JBasics.class_name -> Sil.typ + +(** [get_class_type tenv cn] returns the sil type representation of the class *) +val get_class_type : JClasspath.program -> Sil.tenv -> JBasics.class_name -> Sil.typ + +(** transforms a Java object type to a Sil type *) +val object_type : JClasspath.program -> Sil.tenv -> JBasics.object_type -> Sil.typ + +(** create sizeof expressions from the object type and the list of subtypes *) +val sizeof_of_object_type : JClasspath.program -> Sil.tenv -> JBasics.object_type -> Sil.Subtype.t +-> Sil.exp + +(** transforms a Java type to a Sil type. *) +val value_type : JClasspath.program -> Sil.tenv -> JBasics.value_type -> Sil.typ + +(** return the type of a formal parameter, looking up the class name in case of "this" *) +val param_type : JClasspath.program -> Sil.tenv -> JBasics.class_name -> JBir.var -> JBasics.value_type -> Sil.typ + +(** Returns the return type of the method based on the return type specified in ms. +If the method is the initialiser, return the type Object instead. *) +val return_type : JClasspath.program -> Sil.tenv -> JBasics.method_signature -> JContext.meth_kind -> Sil.typ + +(** translates the type of an expression *) +val expr_type : JContext.t -> JBir.expr -> Sil.typ + +(** translates a conversion type from Java to Sil. *) +val cast_type : JBir.conv -> Sil.typ + +(** [create_array_type typ dim] creates an array type with dimension dim and content typ *) +val create_array_type : Sil.typ -> int -> Sil.typ + +(** [extract_cn_type_np] returns the internal type of type when typ is a pointer type, otherwise returns typ *) +val extract_cn_type_np : Sil.typ -> Sil.typ + +(** [extract_cn_type_np] returns the Java class name of typ when typ is a pointer type, otherwise returns None *) +val extract_cn_no_obj : Sil.typ -> JBasics.class_name option + +(** returns a string representation of a Java basic type. *) +val string_of_basic_type : JBasics.java_basic_type -> string + +(** returns a string representation of a Java type *) +val string_of_type : JBasics.value_type -> string + +(** returns a string representation of an object Java type *) +val object_type_to_string : JBasics.object_type -> string + +val vt_to_java_type : JBasics.value_type -> Procname.java_type + +val cn_to_java_type : JBasics.class_name -> Procname.java_type + +(** [update_tenv program] update the type environment with all the types found in [program] *) +val update_tenv : Sil.tenv -> JClasspath.program -> unit + +(** Update a type environment with the types found in the classpath *) +val saturate_tenv_with_classpath : string -> Sil.tenv -> unit + +(** list of methods that are never returning null *) +val never_returning_null : Procname.t list diff --git a/infer/src/java/jUtils.ml b/infer/src/java/jUtils.ml new file mode 100644 index 000000000..acd8d0ae3 --- /dev/null +++ b/infer/src/java/jUtils.ml @@ -0,0 +1,27 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +open Javalib_pack + + +let get_native_methods program = + let select_native m l = + let is_native cm = (cm.Javalib.cm_implementation = Javalib.Native) in + match m with + | Javalib.ConcreteMethod cm when is_native cm -> + let (cn, ms) = JBasics.cms_split + cm.Javalib.cm_class_method_signature in ((JBasics.cn_name cn)^"."^(JBasics.ms_name ms)):: l + | _ -> l in + let collect _ node l = + (Javalib.m_fold select_native node l) in + JBasics.ClassMap.fold collect program [] + + +let rec log fmt = + if !JConfig.debug_mode then + Logging.stdout fmt + else + Obj.magic log diff --git a/infer/src/java/jUtils.mli b/infer/src/java/jUtils.mli new file mode 100644 index 000000000..6a5271288 --- /dev/null +++ b/infer/src/java/jUtils.mli @@ -0,0 +1,11 @@ +(* +* Copyright (c) 2009 -2013 Monoidics ltd. +* Copyright (c) 2013 - Facebook. +* All rights reserved. +*) + +open Javalib_pack + + +(** TODO: replace by a Infer-wide debug mode printing *) +val log : ('a, Format.formatter, unit) format -> 'a diff --git a/infer/src/java/jVerbose.ml b/infer/src/java/jVerbose.ml new file mode 100644 index 000000000..d82739412 --- /dev/null +++ b/infer/src/java/jVerbose.ml @@ -0,0 +1,4 @@ +type parsed_data = + | Source of string + | Class of string + | Classpath of string list diff --git a/infer/src/java/jVerboseLexer.mll b/infer/src/java/jVerboseLexer.mll new file mode 100644 index 000000000..c568b97c3 --- /dev/null +++ b/infer/src/java/jVerboseLexer.mll @@ -0,0 +1,49 @@ +{ + open JVerboseParser +} + +let space = [' ' '\t'] + +let lowerletter = ['a'-'z'] +let upperletter = ['A'-'Z'] +let letter = lowerletter | upperletter + +let underscore = '_' +let minus = '-' +let dot = '.' +let hash = '#' +let dollar = '$' + +let digit = ['0'-'9'] +let number = digit* | digit+ '.' digit* | digit* '.' digit+ +let char = letter | digit + +let dir_sep = '/' + +let dot_java = dot "java" +let dot_class = dot "class" +let path = (char | dir_sep | underscore | minus | dot | hash | dollar)+ +let source_basename = path dot_java +let class_basename = path dot_class +let source_filename = (dir_sep path | path)* source_basename +let class_filename = (dir_sep path | path)* class_basename + +let search_path = "search path for class files" + +rule token = parse +| [' ' '\t'] { token lexbuf } (* skip blanks *) +| ['\n'] { EOL } +| '[' { LEFT_SQUARE_BRACKET } +| ']' { RIGHT_SQUARE_BRACKET } +| ':' { COLON } +| ',' { COMMA } +| "parsing" { PARSING } +| "started" { STARTED } +| "wrote" { WROTE } +| search_path { SEARCH_PATH } +| "RegularFileObject" { REGULARFILEOBJECT } +| "ZipFileIndexFileObject" { ZIPFILEINDEXFILEOBJECT } +| source_filename as p { SOURCE_FILENAME p } +| class_filename as p { CLASS_FILENAME p } +| path as p { PATH p } +| eof { EOF } diff --git a/infer/src/java/jVerboseParser.mly b/infer/src/java/jVerboseParser.mly new file mode 100644 index 000000000..53eb990e6 --- /dev/null +++ b/infer/src/java/jVerboseParser.mly @@ -0,0 +1,39 @@ +%{ + +%} + +%token EOL EOF +%token LEFT_SQUARE_BRACKET RIGHT_SQUARE_BRACKET +%token COLON COMMA +%token PARSING STARTED WROTE SEARCH_PATH +%token REGULARFILEOBJECT ZIPFILEINDEXFILEOBJECT +%token CLASS_FILENAME +%token SOURCE_FILENAME +%token PATH +%start line +%type line +%% + + +line: + | source_filename { $1 } + | class_filename { $1 } + | classpath { $1 } +; + +source_filename: + LEFT_SQUARE_BRACKET PARSING STARTED REGULARFILEOBJECT LEFT_SQUARE_BRACKET SOURCE_FILENAME RIGHT_SQUARE_BRACKET RIGHT_SQUARE_BRACKET { JVerbose.Source $6 } +; + +class_filename: + LEFT_SQUARE_BRACKET WROTE REGULARFILEOBJECT LEFT_SQUARE_BRACKET CLASS_FILENAME RIGHT_SQUARE_BRACKET RIGHT_SQUARE_BRACKET { JVerbose.Class $5 } +; + +classpath: + LEFT_SQUARE_BRACKET SEARCH_PATH COLON classpath_parts RIGHT_SQUARE_BRACKET { JVerbose.Classpath $4 } +; + +classpath_parts: + | PATH { [$1] } + | PATH COMMA classpath_parts { $1 :: $3 } +; diff --git a/infer/src/opensource/facebook.ml b/infer/src/opensource/facebook.ml new file mode 100644 index 000000000..b054f15f4 --- /dev/null +++ b/infer/src/opensource/facebook.ml @@ -0,0 +1,7 @@ +(* +* Copyright (c) 2014 - Facebook. +* All rights reserved. +*) + +let analyzer_mode s = () +let register_checkers () = () diff --git a/infer/tests/.idea/.name b/infer/tests/.idea/.name new file mode 100644 index 000000000..68a07e599 --- /dev/null +++ b/infer/tests/.idea/.name @@ -0,0 +1 @@ +infer \ No newline at end of file diff --git a/infer/tests/.idea/checkstyle-idea.xml b/infer/tests/.idea/checkstyle-idea.xml new file mode 100644 index 000000000..3d7271f8f --- /dev/null +++ b/infer/tests/.idea/checkstyle-idea.xml @@ -0,0 +1,19 @@ + + + + + + + diff --git a/infer/tests/.idea/codeStyleSettings.xml b/infer/tests/.idea/codeStyleSettings.xml new file mode 100644 index 000000000..cca92d568 --- /dev/null +++ b/infer/tests/.idea/codeStyleSettings.xml @@ -0,0 +1,51 @@ + + + + + + + diff --git a/infer/tests/.idea/compiler.xml b/infer/tests/.idea/compiler.xml new file mode 100644 index 000000000..115fa492b --- /dev/null +++ b/infer/tests/.idea/compiler.xml @@ -0,0 +1,24 @@ + + + + + + diff --git a/infer/tests/.idea/copyright/Facebook.xml b/infer/tests/.idea/copyright/Facebook.xml new file mode 100644 index 000000000..a593d82d7 --- /dev/null +++ b/infer/tests/.idea/copyright/Facebook.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/infer/tests/.idea/encodings.xml b/infer/tests/.idea/encodings.xml new file mode 100644 index 000000000..e206d70d8 --- /dev/null +++ b/infer/tests/.idea/encodings.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/infer/tests/.idea/excludeFromValidation.xml b/infer/tests/.idea/excludeFromValidation.xml new file mode 100644 index 000000000..c26b13bf2 --- /dev/null +++ b/infer/tests/.idea/excludeFromValidation.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/infer/tests/.idea/libraries/infer_dependencies.xml b/infer/tests/.idea/libraries/infer_dependencies.xml new file mode 100644 index 000000000..7c2a68ea1 --- /dev/null +++ b/infer/tests/.idea/libraries/infer_dependencies.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/infer/tests/.idea/misc.xml b/infer/tests/.idea/misc.xml new file mode 100644 index 000000000..ed38838e7 --- /dev/null +++ b/infer/tests/.idea/misc.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/infer/tests/.idea/modules.xml b/infer/tests/.idea/modules.xml new file mode 100644 index 000000000..dc9152f02 --- /dev/null +++ b/infer/tests/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/infer/tests/.idea/scopes/scope_settings.xml b/infer/tests/.idea/scopes/scope_settings.xml new file mode 100644 index 000000000..922003b84 --- /dev/null +++ b/infer/tests/.idea/scopes/scope_settings.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/infer/tests/.idea/vcs.xml b/infer/tests/.idea/vcs.xml new file mode 100644 index 000000000..a5dd08645 --- /dev/null +++ b/infer/tests/.idea/vcs.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/infer/tests/BUCK b/infer/tests/BUCK new file mode 100644 index 000000000..1fa063dec --- /dev/null +++ b/infer/tests/BUCK @@ -0,0 +1,65 @@ +java_test( + name='integration_tests', + deps=[ + '//dependencies/java:java_libraries', + '//infer/tests/endtoend:objc_endtoend_tests', + '//infer/tests/frontend:objc_frontend_tests', + '//infer/tests/endtoend/c:infer', + '//infer/tests/frontend:c_frontend_tests', + '//infer/tests/endtoend:cpp_endtoend_tests', + '//infer/tests/frontend:cpp_frontend_tests', + '//infer/tests/endtoend:objcpp_endtoend_tests', + '//infer/tests/frontend:objcpp_frontend_tests', + '//infer/tests/endtoend:java_endtoend_tests', + ], + source='7', + target='7', +) + + +java_test( + name='objc_tests', + deps=[ + '//infer/tests/endtoend:objc_endtoend_tests', + '//infer/tests/frontend:objc_frontend_tests', + ], +) + +java_test( + name='c_tests', + deps=[ + '//infer/tests/endtoend/c:infer', + '//infer/tests/frontend:c_frontend_tests', + ], +) + +java_test( + name='cpp_tests', + deps=[ + '//infer/tests/endtoend:cpp_endtoend_tests', + '//infer/tests/frontend:cpp_frontend_tests', + ], +) + +java_test( + name='objcpp_tests', + deps=[ + '//infer/tests/endtoend:objcpp_endtoend_tests', + '//infer/tests/frontend:objcpp_frontend_tests', + ], +) + +java_test( + name='clang_tests', + deps=[ + '//infer/tests:c_tests', + '//infer/tests:objc_tests', + '//infer/tests:cpp_tests', + '//infer/tests:objcpp_tests', + ], +) + +project_config( + src_target=':integration_tests', + src_roots=[ 'src' ], +) diff --git a/infer/tests/Makefile b/infer/tests/Makefile new file mode 100644 index 000000000..5f3af6447 --- /dev/null +++ b/infer/tests/Makefile @@ -0,0 +1,57 @@ + +ANNOTATIONS_JAR = ../annotations/annotations.jar +JACKSON_JAR = ../../dependencies/java/jackson/jackson-2.2.3.jar +ANDROID_JAR = ../lib/java/android/android-19.jar +GUAVA_JAR = ../../dependencies/java/guava/guava-10.0.1-fork.jar +JSR_JAR = ../../dependencies/java/jsr-305/jsr305.jar + +INFERJ = inferJ + +TRACING_OUT = tracing_out +TRACING_REPORT = $(TRACING_OUT)/report.csv +TRACING_SOURCES = $(shell find codetoanalyze/java/tracing -name "*.java") +TRACING_JAVAC_CMD = javac -cp $(ANNOTATIONS_JAR) $(TRACING_SOURCES) +TRACING_CMD = $(INFERJ) -o $(TRACING_OUT) -a tracing $(TRACING_JAVAC_CMD) + +INFER_OUT = infer_out +INFER_REPORT = $(INFER_OUT)/report.csv +INFER_SOURCES = $(shell find codetoanalyze/java/infer -name "*.java") +INFER_JAVAC_CMD = javac -cp $(JACKSON_JAR):$(ANDROID_JAR) $(INFER_SOURCES) +INFER_CMD = $(INFERJ) -o $(INFER_OUT) -a infer $(INFER_JAVAC_CMD) + +ERADICATE_OUT = eradicate_out +ERADICATE_REPORT = $(ERADICATE_OUT)/report.csv +ERADICATE_SOURCES = $(shell find codetoanalyze/java/eradicate -name "*.java") +ERADICATE_JAVAC_CMD = javac -cp $(JSR_JAR):$(ANNOTATIONS_JAR):$(GUAVA_JAR):$(ANDROID_JAR) $(ERADICATE_SOURCES) +ERADICATE_CMD = $(INFERJ) -o $(ERADICATE_OUT) -a eradicate $(ERADICATE_JAVAC_CMD) + +CHECKERS_OUT = checkers_out +CHECKERS_REPORT = $(CHECKERS_OUT)/report.csv +CHECKERS_SOURCES = $(shell find codetoanalyze/java/checkers -name "*.java") +CHECKERS_JAVAC_CMD = javac -cp $(ANNOTATIONS_JAR):$(GUAVA_JAR):$(ANDROID_JAR) $(CHECKERS_SOURCES) +CHECKERS_CMD = $(INFERJ) -o $(CHECKERS_OUT) -a checkers $(CHECKERS_JAVAC_CMD) + +.PHONY: tracing infer eradicate checkers + +all: tracing infer eradicate checkers + + +tracing: + $(TRACING_CMD) + +infer: + $(INFER_CMD) + +eradicate: + $(ERADICATE_CMD) + +checkers: + $(CHECKERS_CMD) + +clean: + rm -rf $(TRACING_OUT) + rm -rf $(INFER_OUT) + rm -rf $(CHECKERS_OUT) + rm -rf $(ERADICATE_OUT) + find . -name "*.o" | xargs rm + find . -name "*.class" | xargs rm diff --git a/infer/tests/codetoanalyze/c/errors/BUCK b/infer/tests/codetoanalyze/c/errors/BUCK new file mode 100644 index 000000000..c021dc742 --- /dev/null +++ b/infer/tests/codetoanalyze/c/errors/BUCK @@ -0,0 +1,20 @@ +sources = glob(['**/*.c']) +sources += glob(['**/Makefile']) + +out = 'out' + +clean_cmd = ' '.join(['rm', '-rf', out]) +env_cmd = ' '.join(['export', 'REPORT_ASSERTION_FAILURE=1']) +infer_cmd = ' '.join(['infer', '-o', 'out', '--testing_mode', '--', 'make']) +copy_cmd = ' '.join(['cp', out + '/report.csv', '$OUT']) +command = ' && '.join([clean_cmd, env_cmd, infer_cmd, copy_cmd]) + +genrule( + name = 'analyze', + srcs = sources, + out = 'report.csv', + cmd = command, + visibility = [ + 'PUBLIC', + ], +) diff --git a/infer/tests/codetoanalyze/c/errors/Makefile b/infer/tests/codetoanalyze/c/errors/Makefile new file mode 100644 index 000000000..1d512580e --- /dev/null +++ b/infer/tests/codetoanalyze/c/errors/Makefile @@ -0,0 +1,19 @@ + +all: + make -C arithmetic + make -C assertions + make -C initialization + make -C local_vars + make -C null_dereference + make -C resource_leaks + make -C memory_leaks + +clean: + make -C arithmetic clean + make -C assertions clean + make -C initialization clean + make -C local_vars clean + make -C null_dereference clean + make -C resource_leaks clean + make -C memory_leaks + diff --git a/infer/tests/codetoanalyze/c/errors/arithmetic/Makefile b/infer/tests/codetoanalyze/c/errors/arithmetic/Makefile new file mode 120000 index 000000000..67091171e --- /dev/null +++ b/infer/tests/codetoanalyze/c/errors/arithmetic/Makefile @@ -0,0 +1 @@ +../generic.mk \ No newline at end of file diff --git a/infer/tests/codetoanalyze/c/errors/arithmetic/array_out_of_bounds.c b/infer/tests/codetoanalyze/c/errors/arithmetic/array_out_of_bounds.c new file mode 100644 index 000000000..3281ff6cc --- /dev/null +++ b/infer/tests/codetoanalyze/c/errors/arithmetic/array_out_of_bounds.c @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +void bound_error() { + int a[7]; + a[7] = 4; +} + +void nested_array_ok() { + int a[3][4][5]; + a[2][3][4] = 0; +} + +void bound_error_nested() { + int a[3][4][5]; + a[4][3][2] = 0; +} diff --git a/infer/tests/codetoanalyze/c/errors/arithmetic/divide_by_zero.c b/infer/tests/codetoanalyze/c/errors/arithmetic/divide_by_zero.c new file mode 100644 index 000000000..ca6d722ed --- /dev/null +++ b/infer/tests/codetoanalyze/c/errors/arithmetic/divide_by_zero.c @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +int divide_by_zero() { + int x = 0; + int y = 5; + return y / x; +} diff --git a/infer/tests/codetoanalyze/c/errors/assertions/Makefile b/infer/tests/codetoanalyze/c/errors/assertions/Makefile new file mode 120000 index 000000000..67091171e --- /dev/null +++ b/infer/tests/codetoanalyze/c/errors/assertions/Makefile @@ -0,0 +1 @@ +../generic.mk \ No newline at end of file diff --git a/infer/tests/codetoanalyze/c/errors/assertions/assertion_failure.c b/infer/tests/codetoanalyze/c/errors/assertions/assertion_failure.c new file mode 100644 index 000000000..2fc079fd6 --- /dev/null +++ b/infer/tests/codetoanalyze/c/errors/assertions/assertion_failure.c @@ -0,0 +1,83 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +#include +#include + +void simple_check(int x) { + assert(x < 3); +} + +void simple_assertion_failure() { + int x = 4; + simple_check(x); +} + +void no_assertion_failure() { + int x = 2; + simple_check(x); +} + +typedef struct { + int value; +} node; + +void check_node(node *n) { + assert(n->value < 3); +} + +node* assertion_failure_with_heap() { + node *n = malloc(sizeof(node)); + if (n != NULL) { + n->value = 4; + check_node(n); + } + return n; +} + +node* no_assertion_failure_with_heap() { + node *n = malloc(sizeof(node)); + if (n != NULL) { + n->value = 2; + check_node(n); + } + return n; +} + +void __infer_fail(char*); + +void my_assert(int x) { + if (!x) { + __infer_fail("Assertion_failure"); + } +} + +void should_not_report_assertion_failure(int x) { + my_assert(x); +} + +void should_report_assertion_failure(int x) { + x = 0; + my_assert(x); +} + +int global; + +void check_global() { + assert(global != 0); +} + +void skip() {} + +void assignment_after_check() { + check_global(); + global = 0; + skip(); +} + +void assignemt_before_check() { + global = 0; + check_global(); +} diff --git a/infer/tests/codetoanalyze/c/errors/generic.mk b/infer/tests/codetoanalyze/c/errors/generic.mk new file mode 100644 index 000000000..ec67ebe4f --- /dev/null +++ b/infer/tests/codetoanalyze/c/errors/generic.mk @@ -0,0 +1,12 @@ + +SOURCES = $(shell ls *.c) +OBJECTS = $(SOURCES:.c=.o) + +all: clean $(OBJECTS) + echo $(OBJECTS) + +.c.o: + ${CC} -c $< + +clean: + rm -rf $(OBJECTS) diff --git a/infer/tests/codetoanalyze/c/errors/initialization/Makefile b/infer/tests/codetoanalyze/c/errors/initialization/Makefile new file mode 120000 index 000000000..67091171e --- /dev/null +++ b/infer/tests/codetoanalyze/c/errors/initialization/Makefile @@ -0,0 +1 @@ +../generic.mk \ No newline at end of file diff --git a/infer/tests/codetoanalyze/c/errors/initialization/initlistexpr.c b/infer/tests/codetoanalyze/c/errors/initialization/initlistexpr.c new file mode 100644 index 000000000..8b8da3997 --- /dev/null +++ b/infer/tests/codetoanalyze/c/errors/initialization/initlistexpr.c @@ -0,0 +1,8 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ +int divide_by_zero() { + int t[2][3][2] = {{{1,1},{2,2},{3,3}},{{4,4},{5,5},{1,0}}}; + return t[0][1][0]/t[1][2][1]; +} diff --git a/infer/tests/codetoanalyze/c/errors/local_vars/Makefile b/infer/tests/codetoanalyze/c/errors/local_vars/Makefile new file mode 120000 index 000000000..67091171e --- /dev/null +++ b/infer/tests/codetoanalyze/c/errors/local_vars/Makefile @@ -0,0 +1 @@ +../generic.mk \ No newline at end of file diff --git a/infer/tests/codetoanalyze/c/errors/local_vars/local_vars.c b/infer/tests/codetoanalyze/c/errors/local_vars/local_vars.c new file mode 100644 index 000000000..23f577402 --- /dev/null +++ b/infer/tests/codetoanalyze/c/errors/local_vars/local_vars.c @@ -0,0 +1,42 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + + +int m(int z) { + int y = 0; + int x = 5; + if (z < 10) { + int x = 7; + if (x == 7) return x/y; + } + if (x == 5) + return x/y; + else return 0; +} + +int mm() { + int y = 0; + int x = 0; + { + int x = 5; + if (x == 5) return x/y; + } + if (x == 0) + return x/y; + else return 0; +} + +int t() { + int y = 0; + int x = 1; + int z = 0; + + for (int x = 0; x < 10; x++) { + int x = 9; + if (x == 9) return x/y; + else return 0; + } + return 0; +} diff --git a/infer/tests/codetoanalyze/c/errors/memory_leaks/Makefile b/infer/tests/codetoanalyze/c/errors/memory_leaks/Makefile new file mode 120000 index 000000000..67091171e --- /dev/null +++ b/infer/tests/codetoanalyze/c/errors/memory_leaks/Makefile @@ -0,0 +1 @@ +../generic.mk \ No newline at end of file diff --git a/infer/tests/codetoanalyze/c/errors/memory_leaks/test.c b/infer/tests/codetoanalyze/c/errors/memory_leaks/test.c new file mode 100644 index 000000000..617664f36 --- /dev/null +++ b/infer/tests/codetoanalyze/c/errors/memory_leaks/test.c @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +#include + +void simple_leak() { + int *p; + p = (int*) malloc(sizeof(int)); +} + +void common_realloc_leak() { + int *p, *q; + p = (int*) malloc(sizeof(int)); + q = (int*) realloc(p, sizeof(int) * 42); + // if realloc fails, then p becomes unreachable + if (q != NULL) free(q); +} + +int* allocate() { + int *p = NULL; + do { + p = (int*) malloc(sizeof(int)); + } while (p == NULL); + return p; +} + +void uses_allocator() { + int *p; + p = allocate(); + *p = 42; +} diff --git a/infer/tests/codetoanalyze/c/errors/null_dereference/Makefile b/infer/tests/codetoanalyze/c/errors/null_dereference/Makefile new file mode 120000 index 000000000..67091171e --- /dev/null +++ b/infer/tests/codetoanalyze/c/errors/null_dereference/Makefile @@ -0,0 +1 @@ +../generic.mk \ No newline at end of file diff --git a/infer/tests/codetoanalyze/c/errors/null_dereference/angelism.c b/infer/tests/codetoanalyze/c/errors/null_dereference/angelism.c new file mode 100644 index 000000000..e66eeaf3d --- /dev/null +++ b/infer/tests/codetoanalyze/c/errors/null_dereference/angelism.c @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2015 - Facebook. + * All rights reserved. + */ + +#include + +struct delicious { + int yum; +}; + +struct delicious *bake(struct delicious **cake) { + int *zero = NULL; + *zero = 3; + return NULL; +} + +struct delicious *skip_function_with_no_spec(void) { + struct delicious *cake = NULL; + int i; + + if(bake(&cake) == NULL) { + return 0; + } + + i = cake->yum; + return cake; +} + +extern struct delicious *bakery(struct delicious **cake); + +struct delicious *skip_external_function(void) { + struct delicious *cake = NULL; + int i; + + if(bakery(&cake) == NULL) { + return 0; + } + + i = cake->yum; + return cake; +} diff --git a/infer/tests/codetoanalyze/c/errors/null_dereference/null_pointer_dereference.c b/infer/tests/codetoanalyze/c/errors/null_dereference/null_pointer_dereference.c new file mode 100644 index 000000000..5aeeb4c2b --- /dev/null +++ b/infer/tests/codetoanalyze/c/errors/null_dereference/null_pointer_dereference.c @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2015 - Facebook. + * All rights reserved. + */ + +#include + +struct Person { + int age; + int height; + int weight; +}; + +int simple_null_pointer() { + struct Person *max = 0; + return max->age; +} + +struct Person *Person_create(int age, int height, int weight) { + struct Person *who = 0; + return who; +} + +int get_age(struct Person *who) { + return who->age; +} + +int null_pointer_interproc() { + struct Person *joe = Person_create(32, 64, 140); + return get_age(joe); +} + +int negation_in_conditional() { + int *x = 0; + if (!x) return 0; + else return *x; // this never happens +} + +int * foo() { + return 0; +} + +void null_pointer_with_function_pointer() { + int * (*fp)(); + fp = foo; + int *x = fp(); + *x = 3; +} + +void use_exit (struct Person *htbl) { + if (!htbl) + exit(0); + int x = htbl->age; +} + +void basic_null_dereference() { + int *p = NULL; + *p = 42; // NULL dereference +} + +void no_check_for_null_after_malloc() { + int *p; + p = (int*) malloc(sizeof(int)); + *p = 42; // NULL dereference + free(p); +} + +void no_check_for_null_after_realloc() { + int *p; + p = (int*) malloc(sizeof(int) * 5); + if (p) { + p[3] = 42; + } + int *q = (int*) realloc(p, sizeof(int) * 10); + if (!q) + free(p); + q[7] = 0; // NULL dereference + free(q); +} + +void assign(int *p, int n) { + *p = n; +} + +void potentially_null_pointer_passed_as_argument() { + int *p = NULL; + p = (int*) malloc(sizeof(int)); + assign(p, 42); // NULL dereference + free(p); +} + +void allocated_pointer_passed_as_argument() { + int *p = NULL; + p = (int*) malloc(sizeof(int)); + if (p) { + assign(p, 42); + free(p); + } +} + +int* unsafe_allocate() { + int *p = NULL; + p = (int*) malloc(sizeof(int)); + return p; +} + +int* safe_allocate() { + int *p = NULL; + while (!p) { + p = (int*) malloc(sizeof(int)); + } + return p; +} + +void function_call_can_return_null_pointer() { + int *p = NULL; + p = unsafe_allocate(); + assign(p, 42); // NULL dereference + free(p); +} + +void function_call_returns_allocated_pointer() { + int *p = NULL; + p = safe_allocate(); + assign(p, 42); + free(p); +} diff --git a/infer/tests/codetoanalyze/c/errors/resource_leaks/Makefile b/infer/tests/codetoanalyze/c/errors/resource_leaks/Makefile new file mode 120000 index 000000000..67091171e --- /dev/null +++ b/infer/tests/codetoanalyze/c/errors/resource_leaks/Makefile @@ -0,0 +1 @@ +../generic.mk \ No newline at end of file diff --git a/infer/tests/codetoanalyze/c/errors/resource_leaks/leak.c b/infer/tests/codetoanalyze/c/errors/resource_leaks/leak.c new file mode 100644 index 000000000..d01852ff0 --- /dev/null +++ b/infer/tests/codetoanalyze/c/errors/resource_leaks/leak.c @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +void fileNotClosed() +{ + int fd = open("hi.txt", O_WRONLY | O_CREAT | O_TRUNC, 0600); + if (fd != -1) { + char buffer[256]; + // We can easily batch that by separating with space + write(fd, buffer, strlen(buffer)); + } +} + +void fileClosed() +{ + int fd = open("hi.txt", O_WRONLY | O_CREAT | O_TRUNC, 0600); + if (fd != -1) { + char buffer[256]; + // We can easily batch that by separating with space + write(fd, buffer, strlen(buffer)); + close(fd); + } +} diff --git a/infer/tests/codetoanalyze/c/frontend/arithmetic/compound_assignment.c b/infer/tests/codetoanalyze/c/frontend/arithmetic/compound_assignment.c new file mode 100644 index 000000000..f1196fdff --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/arithmetic/compound_assignment.c @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +int main() { + double x = 1.0; + x += 1.0; + x -= 1.0; + x /= 1.0; + x *= 1.0; + int b = 1; + b <<= 1; + b >>= 1; + b %= 1; + b &= 1; + b |= 1; + b ^= 1; + return 0; +} diff --git a/infer/tests/codetoanalyze/c/frontend/arithmetic/compound_assignment.dot b/infer/tests/codetoanalyze/c/frontend/arithmetic/compound_assignment.dot new file mode 100644 index 000000000..89300bf2c --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/arithmetic/compound_assignment.dot @@ -0,0 +1,61 @@ +digraph iCFG { +15 [label="15: DeclStmt \n *&x:double =1.000000 [line 7]\n " shape="box"] + + + 15 -> 14 ; +14 [label="14: ComppoundAssignStmt \n n$9=*&x:double [line 8]\n *&x:double =(n$9 + 1.000000) [line 8]\n REMOVE_TEMPS(n$9); [line 8]\n " shape="box"] + + + 14 -> 13 ; +13 [label="13: ComppoundAssignStmt \n n$8=*&x:double [line 9]\n *&x:double =(n$8 - 1.000000) [line 9]\n REMOVE_TEMPS(n$8); [line 9]\n " shape="box"] + + + 13 -> 12 ; +12 [label="12: ComppoundAssignStmt \n n$7=*&x:double [line 10]\n *&x:double =(n$7 / 1.000000) [line 10]\n REMOVE_TEMPS(n$7); [line 10]\n " shape="box"] + + + 12 -> 11 ; +11 [label="11: ComppoundAssignStmt \n n$6=*&x:double [line 11]\n *&x:double =(n$6 * 1.000000) [line 11]\n REMOVE_TEMPS(n$6); [line 11]\n NULLIFY(&x,false); [line 11]\n " shape="box"] + + + 11 -> 10 ; +10 [label="10: DeclStmt \n *&b:int =1 [line 12]\n " shape="box"] + + + 10 -> 9 ; +9 [label="9: ComppoundAssignStmt \n n$5=*&b:int [line 13]\n *&b:int =(n$5 << 1) [line 13]\n REMOVE_TEMPS(n$5); [line 13]\n " shape="box"] + + + 9 -> 8 ; +8 [label="8: ComppoundAssignStmt \n n$4=*&b:int [line 14]\n *&b:int =(n$4 >> 1) [line 14]\n REMOVE_TEMPS(n$4); [line 14]\n " shape="box"] + + + 8 -> 7 ; +7 [label="7: ComppoundAssignStmt \n n$3=*&b:int [line 15]\n *&b:int =(n$3 % 1) [line 15]\n REMOVE_TEMPS(n$3); [line 15]\n " shape="box"] + + + 7 -> 6 ; +6 [label="6: ComppoundAssignStmt \n n$2=*&b:int [line 16]\n *&b:int =(n$2 & 1) [line 16]\n REMOVE_TEMPS(n$2); [line 16]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: ComppoundAssignStmt \n n$1=*&b:int [line 17]\n *&b:int =(n$1 | 1) [line 17]\n REMOVE_TEMPS(n$1); [line 17]\n " shape="box"] + + + 5 -> 4 ; +4 [label="4: ComppoundAssignStmt \n n$0=*&b:int [line 18]\n *&b:int =(n$0 ^ 1) [line 18]\n REMOVE_TEMPS(n$0); [line 18]\n NULLIFY(&b,false); [line 18]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n *&return:int =0 [line 19]\n APPLY_ABSTRACTION; [line 19]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: x:double b:int \n DECLARE_LOCALS(&return,&x,&b); [line 6]\n NULLIFY(&b,false); [line 6]\n NULLIFY(&x,false); [line 6]\n " color=yellow style=filled] + + + 1 -> 15 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/arithmetic/int_const.c b/infer/tests/codetoanalyze/c/frontend/arithmetic/int_const.c new file mode 100644 index 000000000..e0c5ba0be --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/arithmetic/int_const.c @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +int main() { + +int volatile a; +int *volatile b; +float *const c; +long double d; + + static const int kDuration = 3; + return 0; +} diff --git a/infer/tests/codetoanalyze/c/frontend/arithmetic/int_const.dot b/infer/tests/codetoanalyze/c/frontend/arithmetic/int_const.dot new file mode 100644 index 000000000..db3d9af13 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/arithmetic/int_const.dot @@ -0,0 +1,17 @@ +digraph iCFG { +4 [label="4: DeclStmt \n *&#GB$main_kDuration:int =3 [line 13]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n *&return:int =0 [line 14]\n APPLY_ABSTRACTION; [line 14]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: a:int b:int * c:float * d:long double \n DECLARE_LOCALS(&return,&a,&b,&c,&d); [line 6]\n NULLIFY(&a,false); [line 6]\n NULLIFY(&b,false); [line 6]\n NULLIFY(&c,false); [line 6]\n NULLIFY(&d,false); [line 6]\n " color=yellow style=filled] + + + 1 -> 4 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/arithmetic/plus_expr.c b/infer/tests/codetoanalyze/c/frontend/arithmetic/plus_expr.c new file mode 100644 index 000000000..7f559df60 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/arithmetic/plus_expr.c @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +int main() { + int x = 2; + int z = 3; + return x + z; +} diff --git a/infer/tests/codetoanalyze/c/frontend/arithmetic/plus_expr.dot b/infer/tests/codetoanalyze/c/frontend/arithmetic/plus_expr.dot new file mode 100644 index 000000000..cfabaf786 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/arithmetic/plus_expr.dot @@ -0,0 +1,21 @@ +digraph iCFG { +5 [label="5: DeclStmt \n *&x:int =2 [line 7]\n " shape="box"] + + + 5 -> 4 ; +4 [label="4: DeclStmt \n *&z:int =3 [line 8]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n n$0=*&x:int [line 9]\n n$1=*&z:int [line 9]\n *&return:int =(n$0 + n$1) [line 9]\n REMOVE_TEMPS(n$0,n$1); [line 9]\n NULLIFY(&x,false); [line 9]\n NULLIFY(&z,false); [line 9]\n APPLY_ABSTRACTION; [line 9]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: x:int z:int \n DECLARE_LOCALS(&return,&x,&z); [line 6]\n NULLIFY(&x,false); [line 6]\n NULLIFY(&z,false); [line 6]\n " color=yellow style=filled] + + + 1 -> 5 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/arithmetic/unary.c b/infer/tests/codetoanalyze/c/frontend/arithmetic/unary.c new file mode 100644 index 000000000..4d2dfb257 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/arithmetic/unary.c @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +int main() { + int x = 1; + int y; + + y = ~x; + y = -x; + y = +x; + + y = x++; + y = ++x; + + y = --x; + y = x--; + + int a; + int *b; + + b = &a; + a = *(b+1); + *b = *b + 1; + a = *(&a); + + return 0; +} diff --git a/infer/tests/codetoanalyze/c/frontend/arithmetic/unary.dot b/infer/tests/codetoanalyze/c/frontend/arithmetic/unary.dot new file mode 100644 index 000000000..f777dde79 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/arithmetic/unary.dot @@ -0,0 +1,61 @@ +digraph iCFG { +15 [label="15: DeclStmt \n *&x:int =1 [line 7]\n " shape="box"] + + + 15 -> 14 ; +14 [label="14: BinaryOperatorStmt: Assign \n n$12=*&x:int [line 10]\n *&y:int =~n$12 [line 10]\n REMOVE_TEMPS(n$12); [line 10]\n NULLIFY(&y,false); [line 10]\n " shape="box"] + + + 14 -> 13 ; +13 [label="13: BinaryOperatorStmt: Assign \n n$11=*&x:int [line 11]\n *&y:int =-n$11 [line 11]\n REMOVE_TEMPS(n$11); [line 11]\n NULLIFY(&y,false); [line 11]\n " shape="box"] + + + 13 -> 12 ; +12 [label="12: BinaryOperatorStmt: Assign \n n$10=*&x:int [line 12]\n *&y:int =n$10 [line 12]\n REMOVE_TEMPS(n$10); [line 12]\n NULLIFY(&y,false); [line 12]\n " shape="box"] + + + 12 -> 11 ; +11 [label="11: BinaryOperatorStmt: Assign \n n$9=*&x:int [line 14]\n *&x:int =(n$9 + 1) [line 14]\n *&y:int =n$9 [line 14]\n REMOVE_TEMPS(n$9); [line 14]\n NULLIFY(&y,false); [line 14]\n " shape="box"] + + + 11 -> 10 ; +10 [label="10: BinaryOperatorStmt: Assign \n n$8=*&x:int [line 15]\n *&x:int =(n$8 + 1) [line 15]\n *&y:int =(n$8 + 1) [line 15]\n REMOVE_TEMPS(n$8); [line 15]\n NULLIFY(&y,false); [line 15]\n " shape="box"] + + + 10 -> 9 ; +9 [label="9: BinaryOperatorStmt: Assign \n n$7=*&x:int [line 17]\n *&x:int =(n$7 - 1) [line 17]\n *&y:int =(n$7 - 1) [line 17]\n REMOVE_TEMPS(n$7); [line 17]\n NULLIFY(&y,false); [line 17]\n " shape="box"] + + + 9 -> 8 ; +8 [label="8: BinaryOperatorStmt: Assign \n n$6=*&x:int [line 18]\n *&x:int =(n$6 - 1) [line 18]\n *&y:int =n$6 [line 18]\n REMOVE_TEMPS(n$6); [line 18]\n NULLIFY(&x,false); [line 18]\n NULLIFY(&y,false); [line 18]\n " shape="box"] + + + 8 -> 7 ; +7 [label="7: BinaryOperatorStmt: Assign \n *&b:int *=&a [line 23]\n " shape="box"] + + + 7 -> 6 ; +6 [label="6: BinaryOperatorStmt: Assign \n n$4=*&b:int * [line 24]\n n$5=*(n$4 + 1):int [line 24]\n *&a:int =n$5 [line 24]\n REMOVE_TEMPS(n$4,n$5); [line 24]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: BinaryOperatorStmt: Assign \n n$1=*&b:int * [line 25]\n n$2=*&b:int * [line 25]\n n$3=*n$2:int [line 25]\n *n$1:int =(n$3 + 1) [line 25]\n REMOVE_TEMPS(n$1,n$2,n$3); [line 25]\n NULLIFY(&b,false); [line 25]\n " shape="box"] + + + 5 -> 4 ; +4 [label="4: BinaryOperatorStmt: Assign \n n$0=*&a:int [line 26]\n *&a:int =n$0 [line 26]\n REMOVE_TEMPS(n$0); [line 26]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n *&return:int =0 [line 28]\n NULLIFY(&a,false); [line 28]\n APPLY_ABSTRACTION; [line 28]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: x:int y:int a:int b:int * \n DECLARE_LOCALS(&return,&x,&y,&a,&b); [line 6]\n NULLIFY(&b,false); [line 6]\n NULLIFY(&x,false); [line 6]\n NULLIFY(&y,false); [line 6]\n " color=yellow style=filled] + + + 1 -> 15 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/booleans/bool_example.c b/infer/tests/codetoanalyze/c/frontend/booleans/bool_example.c new file mode 100644 index 000000000..6957708d9 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/booleans/bool_example.c @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +#include + +bool revert(bool e) { + return e; +} diff --git a/infer/tests/codetoanalyze/c/frontend/booleans/bool_example.dot b/infer/tests/codetoanalyze/c/frontend/booleans/bool_example.dot new file mode 100644 index 000000000..5750eb2c1 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/booleans/bool_example.dot @@ -0,0 +1,13 @@ +digraph iCFG { +3 [label="3: Return Stmt \n n$0=*&e:_Bool [line 9]\n *&return:_Bool =n$0 [line 9]\n REMOVE_TEMPS(n$0); [line 9]\n NULLIFY(&e,false); [line 9]\n APPLY_ABSTRACTION; [line 9]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit revert \n " color=yellow style=filled] + + +1 [label="1: Start revert\nFormals: e:_Bool \nLocals: \n DECLARE_LOCALS(&return); [line 10]\n " color=yellow style=filled] + + + 1 -> 3 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/booleans/condition_as_param.c b/infer/tests/codetoanalyze/c/frontend/booleans/condition_as_param.c new file mode 100644 index 000000000..b47765c3c --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/booleans/condition_as_param.c @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +void check(int x) { +} + +void main() { + int x = 3; + check(x<2); +} diff --git a/infer/tests/codetoanalyze/c/frontend/booleans/condition_as_param.dot b/infer/tests/codetoanalyze/c/frontend/booleans/condition_as_param.dot new file mode 100644 index 000000000..ac630c0d4 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/booleans/condition_as_param.dot @@ -0,0 +1,24 @@ +digraph iCFG { +6 [label="6: DeclStmt \n *&x:int =3 [line 10]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: Call _fun_check \n n$0=*&x:int [line 11]\n _fun_check((n$0 < 2):int ) [line 11]\n REMOVE_TEMPS(n$0); [line 11]\n NULLIFY(&x,false); [line 11]\n APPLY_ABSTRACTION; [line 11]\n " shape="box"] + + + 5 -> 4 ; +4 [label="4: Exit main \n " color=yellow style=filled] + + +3 [label="3: Start main\nFormals: \nLocals: x:int \n DECLARE_LOCALS(&return,&x); [line 9]\n NULLIFY(&x,false); [line 9]\n " color=yellow style=filled] + + + 3 -> 6 ; +2 [label="2: Exit check \n " color=yellow style=filled] + + +1 [label="1: Start check\nFormals: x:int \nLocals: \n DECLARE_LOCALS(&return); [line 6]\n NULLIFY(&x,false); [line 6]\n " color=yellow style=filled] + + + 1 -> 2 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/c_prototype/prototype.c b/infer/tests/codetoanalyze/c/frontend/c_prototype/prototype.c new file mode 100644 index 000000000..1128aca7a --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/c_prototype/prototype.c @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +/* + A simple example of a function prototype allowing the function + to be used before it is defined. + */ + +int sum (int, int); + +int main (void) { + int total; + + total = sum (2, 3); + + return 0; +} + +int sum (int a, int b) { + return a + b; +} diff --git a/infer/tests/codetoanalyze/c/frontend/c_prototype/prototype.dot b/infer/tests/codetoanalyze/c/frontend/c_prototype/prototype.dot new file mode 100644 index 000000000..cfa23ddac --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/c_prototype/prototype.dot @@ -0,0 +1,28 @@ +digraph iCFG { +7 [label="7: Return Stmt \n n$1=*&a:int [line 22]\n n$2=*&b:int [line 22]\n *&return:int =(n$1 + n$2) [line 22]\n REMOVE_TEMPS(n$1,n$2); [line 22]\n NULLIFY(&a,false); [line 22]\n NULLIFY(&b,false); [line 22]\n APPLY_ABSTRACTION; [line 22]\n " shape="box"] + + + 7 -> 6 ; +6 [label="6: Exit sum \n " color=yellow style=filled] + + +5 [label="5: Start sum\nFormals: a:int b:int \nLocals: \n DECLARE_LOCALS(&return); [line 21]\n " color=yellow style=filled] + + + 5 -> 7 ; +4 [label="4: BinaryOperatorStmt: Assign \n n$0=_fun_sum(2:int ,3:int ) [line 16]\n *&total:int =n$0 [line 16]\n REMOVE_TEMPS(n$0); [line 16]\n NULLIFY(&total,false); [line 16]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n *&return:int =0 [line 18]\n APPLY_ABSTRACTION; [line 18]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: total:int \n DECLARE_LOCALS(&return,&total); [line 13]\n NULLIFY(&total,false); [line 13]\n " color=yellow style=filled] + + + 1 -> 4 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/comma/comma.c b/infer/tests/codetoanalyze/c/frontend/comma/comma.c new file mode 100644 index 000000000..640268799 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/comma/comma.c @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +int comma_1() { + int a = 9, b = 7; + int d = (a = a * 2, b = 7 * a++); + return d; +} + +int comma_2() { + int a = 9, b = 7; + int d = (a = a * 2, b = 7 * a++, a+b+9); + return d; +} + +int comma_3() { + int a = 9, b = 7, c = 3; + int d = (a = a * 2, b = 7 * a++, c = a+b+9, c); + return d; +} diff --git a/infer/tests/codetoanalyze/c/frontend/comma/comma.dot b/infer/tests/codetoanalyze/c/frontend/comma/comma.dot new file mode 100644 index 000000000..09ed570ae --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/comma/comma.dot @@ -0,0 +1,75 @@ +digraph iCFG { +19 [label="19: DeclStmt \n *&a:int =9 [line 19]\n " shape="box"] + + + 19 -> 18 ; +18 [label="18: DeclStmt \n *&b:int =7 [line 19]\n NULLIFY(&b,false); [line 19]\n " shape="box"] + + + 18 -> 17 ; +17 [label="17: DeclStmt \n *&c:int =3 [line 19]\n NULLIFY(&c,false); [line 19]\n " shape="box"] + + + 17 -> 16 ; +16 [label="16: DeclStmt \n n$13=*&a:int [line 20]\n *&a:int =(n$13 * 2) [line 20]\n n$14=*&a:int [line 20]\n n$15=*&a:int [line 20]\n *&a:int =(n$15 + 1) [line 20]\n *&b:int =(7 * n$15) [line 20]\n n$16=*&b:int [line 20]\n n$17=*&a:int [line 20]\n n$18=*&b:int [line 20]\n *&c:int =((n$17 + n$18) + 9) [line 20]\n n$19=*&c:int [line 20]\n n$20=*&c:int [line 20]\n *&d:int =n$20 [line 20]\n REMOVE_TEMPS(n$13,n$14,n$15,n$16,n$17,n$18,n$19,n$20); [line 20]\n NULLIFY(&a,false); [line 20]\n NULLIFY(&b,false); [line 20]\n NULLIFY(&c,false); [line 20]\n " shape="box"] + + + 16 -> 15 ; +15 [label="15: Return Stmt \n n$12=*&d:int [line 21]\n *&return:int =n$12 [line 21]\n REMOVE_TEMPS(n$12); [line 21]\n NULLIFY(&d,false); [line 21]\n APPLY_ABSTRACTION; [line 21]\n " shape="box"] + + + 15 -> 14 ; +14 [label="14: Exit comma_3 \n " color=yellow style=filled] + + +13 [label="13: Start comma_3\nFormals: \nLocals: a:int b:int c:int d:int \n DECLARE_LOCALS(&return,&a,&b,&c,&d); [line 18]\n NULLIFY(&a,false); [line 18]\n NULLIFY(&b,false); [line 18]\n NULLIFY(&c,false); [line 18]\n NULLIFY(&d,false); [line 18]\n " color=yellow style=filled] + + + 13 -> 19 ; +12 [label="12: DeclStmt \n *&a:int =9 [line 13]\n " shape="box"] + + + 12 -> 11 ; +11 [label="11: DeclStmt \n *&b:int =7 [line 13]\n NULLIFY(&b,false); [line 13]\n " shape="box"] + + + 11 -> 10 ; +10 [label="10: DeclStmt \n n$6=*&a:int [line 14]\n *&a:int =(n$6 * 2) [line 14]\n n$7=*&a:int [line 14]\n n$8=*&a:int [line 14]\n *&a:int =(n$8 + 1) [line 14]\n *&b:int =(7 * n$8) [line 14]\n n$9=*&b:int [line 14]\n n$10=*&a:int [line 14]\n n$11=*&b:int [line 14]\n *&d:int =((n$10 + n$11) + 9) [line 14]\n REMOVE_TEMPS(n$6,n$7,n$8,n$9,n$10,n$11); [line 14]\n NULLIFY(&a,false); [line 14]\n NULLIFY(&b,false); [line 14]\n " shape="box"] + + + 10 -> 9 ; +9 [label="9: Return Stmt \n n$5=*&d:int [line 15]\n *&return:int =n$5 [line 15]\n REMOVE_TEMPS(n$5); [line 15]\n NULLIFY(&d,false); [line 15]\n APPLY_ABSTRACTION; [line 15]\n " shape="box"] + + + 9 -> 8 ; +8 [label="8: Exit comma_2 \n " color=yellow style=filled] + + +7 [label="7: Start comma_2\nFormals: \nLocals: a:int b:int d:int \n DECLARE_LOCALS(&return,&a,&b,&d); [line 12]\n NULLIFY(&a,false); [line 12]\n NULLIFY(&b,false); [line 12]\n NULLIFY(&d,false); [line 12]\n " color=yellow style=filled] + + + 7 -> 12 ; +6 [label="6: DeclStmt \n *&a:int =9 [line 7]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: DeclStmt \n *&b:int =7 [line 7]\n NULLIFY(&b,false); [line 7]\n " shape="box"] + + + 5 -> 4 ; +4 [label="4: DeclStmt \n n$1=*&a:int [line 8]\n *&a:int =(n$1 * 2) [line 8]\n n$2=*&a:int [line 8]\n n$3=*&a:int [line 8]\n *&a:int =(n$3 + 1) [line 8]\n *&b:int =(7 * n$3) [line 8]\n n$4=*&b:int [line 8]\n *&d:int =n$4 [line 8]\n REMOVE_TEMPS(n$1,n$2,n$3,n$4); [line 8]\n NULLIFY(&a,false); [line 8]\n NULLIFY(&b,false); [line 8]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n n$0=*&d:int [line 9]\n *&return:int =n$0 [line 9]\n REMOVE_TEMPS(n$0); [line 9]\n NULLIFY(&d,false); [line 9]\n APPLY_ABSTRACTION; [line 9]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit comma_1 \n " color=yellow style=filled] + + +1 [label="1: Start comma_1\nFormals: \nLocals: a:int b:int d:int \n DECLARE_LOCALS(&return,&a,&b,&d); [line 6]\n NULLIFY(&a,false); [line 6]\n NULLIFY(&b,false); [line 6]\n NULLIFY(&d,false); [line 6]\n " color=yellow style=filled] + + + 1 -> 6 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/conditional_operator/array_access.c b/infer/tests/codetoanalyze/c/frontend/conditional_operator/array_access.c new file mode 100644 index 000000000..b690c893f --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/conditional_operator/array_access.c @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +void dereference_in_array_access(int **p) { + if (p[0]); + if ((*p)[1]); + if (p[**p]); + if ((*p)[**p]); +} diff --git a/infer/tests/codetoanalyze/c/frontend/conditional_operator/array_access.dot b/infer/tests/codetoanalyze/c/frontend/conditional_operator/array_access.dot new file mode 100644 index 000000000..2f967a9d5 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/conditional_operator/array_access.dot @@ -0,0 +1,77 @@ +digraph iCFG { +18 [label="18: Prune (false branch) \n n$14=*&p:int ** [line 7]\n n$15=*n$14[0]:int * [line 7]\n PRUNE((n$15 == 0), false); [line 7]\n REMOVE_TEMPS(n$14,n$15); [line 7]\n APPLY_ABSTRACTION; [line 7]\n " shape="invhouse"] + + + 18 -> 16 ; +17 [label="17: Prune (true branch) \n n$14=*&p:int ** [line 7]\n n$15=*n$14[0]:int * [line 7]\n PRUNE((n$15 != 0), true); [line 7]\n REMOVE_TEMPS(n$14,n$15); [line 7]\n APPLY_ABSTRACTION; [line 7]\n " shape="invhouse"] + + + 17 -> 16 ; +16 [label="16: + \n " ] + + + 16 -> 13 ; +15 [label="15: Prune (false branch) \n n$12=*n$11:int * [line 8]\n n$13=*n$12[1]:int [line 8]\n PRUNE((n$13 == 0), false); [line 8]\n REMOVE_TEMPS(n$11,n$12,n$13); [line 8]\n APPLY_ABSTRACTION; [line 8]\n " shape="invhouse"] + + + 15 -> 12 ; +14 [label="14: Prune (true branch) \n n$12=*n$11:int * [line 8]\n n$13=*n$12[1]:int [line 8]\n PRUNE((n$13 != 0), true); [line 8]\n REMOVE_TEMPS(n$11,n$12,n$13); [line 8]\n APPLY_ABSTRACTION; [line 8]\n " shape="invhouse"] + + + 14 -> 12 ; +13 [label="13: UnaryOperator \n n$11=*&p:int ** [line 8]\n " shape="box"] + + + 13 -> 14 ; + 13 -> 15 ; +12 [label="12: + \n " ] + + + 12 -> 9 ; +11 [label="11: Prune (false branch) \n n$6=*&p:int ** [line 9]\n n$9=*n$8:int [line 9]\n n$10=*n$6[n$9]:int * [line 9]\n PRUNE((n$10 == 0), false); [line 9]\n REMOVE_TEMPS(n$7,n$8,n$9,n$6,n$10); [line 9]\n APPLY_ABSTRACTION; [line 9]\n " shape="invhouse"] + + + 11 -> 8 ; +10 [label="10: Prune (true branch) \n n$6=*&p:int ** [line 9]\n n$9=*n$8:int [line 9]\n n$10=*n$6[n$9]:int * [line 9]\n PRUNE((n$10 != 0), true); [line 9]\n REMOVE_TEMPS(n$7,n$8,n$9,n$6,n$10); [line 9]\n APPLY_ABSTRACTION; [line 9]\n " shape="invhouse"] + + + 10 -> 8 ; +9 [label="9: UnaryOperator \n n$7=*&p:int ** [line 9]\n n$8=*n$7:int * [line 9]\n " shape="box"] + + + 9 -> 10 ; + 9 -> 11 ; +8 [label="8: + \n " ] + + + 8 -> 4 ; +7 [label="7: Prune (false branch) \n n$1=*n$0:int * [line 10]\n n$4=*n$3:int [line 10]\n n$5=*n$1[n$4]:int [line 10]\n PRUNE((n$5 == 0), false); [line 10]\n REMOVE_TEMPS(n$2,n$3,n$4,n$0,n$1,n$5); [line 10]\n APPLY_ABSTRACTION; [line 10]\n " shape="invhouse"] + + + 7 -> 3 ; +6 [label="6: Prune (true branch) \n n$1=*n$0:int * [line 10]\n n$4=*n$3:int [line 10]\n n$5=*n$1[n$4]:int [line 10]\n PRUNE((n$5 != 0), true); [line 10]\n REMOVE_TEMPS(n$2,n$3,n$4,n$0,n$1,n$5); [line 10]\n APPLY_ABSTRACTION; [line 10]\n " shape="invhouse"] + + + 6 -> 3 ; +5 [label="5: UnaryOperator \n n$2=*&p:int ** [line 10]\n n$3=*n$2:int * [line 10]\n NULLIFY(&p,false); [line 10]\n " shape="box"] + + + 5 -> 6 ; + 5 -> 7 ; +4 [label="4: UnaryOperator \n n$0=*&p:int ** [line 10]\n " shape="box"] + + + 4 -> 5 ; +3 [label="3: + \n " ] + + + 3 -> 2 ; +2 [label="2: Exit dereference_in_array_access \n " color=yellow style=filled] + + +1 [label="1: Start dereference_in_array_access\nFormals: p:int **\nLocals: \n DECLARE_LOCALS(&return); [line 6]\n " color=yellow style=filled] + + + 1 -> 17 ; + 1 -> 18 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/conditional_operator/assert_example.c b/infer/tests/codetoanalyze/c/frontend/conditional_operator/assert_example.c new file mode 100644 index 000000000..39e05afad --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/conditional_operator/assert_example.c @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +#include + +struct MyPoint { + int x; +}; + +int test (struct MyPoint* activeq) { + assert(activeq != 0); + return activeq->x; +} diff --git a/infer/tests/codetoanalyze/c/frontend/conditional_operator/assert_example.dot b/infer/tests/codetoanalyze/c/frontend/conditional_operator/assert_example.dot new file mode 100644 index 000000000..b28d3ffc6 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/conditional_operator/assert_example.dot @@ -0,0 +1,42 @@ +digraph iCFG { +10 [label="10: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___4); [line -1]\n *&SIL_temp_conditional___4:void =0 [line -1]\n " shape="box"] + + + 10 -> 4 ; +9 [label="9: Prune (true branch) \n PRUNE(1, true); [line -1]\n APPLY_ABSTRACTION; [line -1]\n " shape="invhouse"] + + + 9 -> 8 ; +8 [label="8: + \n DECLARE_LOCALS(&SIL_temp_conditional___4); [line -1]\n *&SIL_temp_conditional___4:void =-1 [line -1]\n " ] + + + 8 -> 9 ; +7 [label="7: Prune (false branch) \n PRUNE(!n$3, false); [line -1]\n REMOVE_TEMPS(n$2,n$3); [line -1]\n " shape="invhouse"] + + + 7 -> 10 ; +6 [label="6: Prune (true branch) \n PRUNE(n$3, true); [line -1]\n REMOVE_TEMPS(n$2,n$3); [line -1]\n APPLY_ABSTRACTION; [line -1]\n " shape="invhouse"] + + + 6 -> 8 ; +5 [label="5: Call _fun_infer__builtin_expect \n n$2=*&activeq:struct MyPoint * [line 8]\n n$3=_fun_infer__builtin_expect(!(n$2 != 0):long ,0:long ) [line -1]\n " shape="box"] + + + 5 -> 6 ; + 5 -> 7 ; +4 [label="4: + \n " ] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n n$0=*&activeq:struct MyPoint * [line 9]\n n$1=*n$0.x:int [line 9]\n *&return:int =n$1 [line 9]\n REMOVE_TEMPS(n$0,n$1); [line 9]\n NULLIFY(&activeq,false); [line 9]\n APPLY_ABSTRACTION; [line 9]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit test \n " color=yellow style=filled] + + +1 [label="1: Start test\nFormals: activeq:struct MyPoint *\nLocals: \n DECLARE_LOCALS(&return); [line 7]\n " color=yellow style=filled] + + + 1 -> 5 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/conditional_operator/binary_operator.c b/infer/tests/codetoanalyze/c/frontend/conditional_operator/binary_operator.c new file mode 100644 index 000000000..228341db3 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/conditional_operator/binary_operator.c @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +void binop_with_side_effects(int z) { + // simple assignment + int x1; + x1 = (1 ? z : z) + 77; + + int x2; + x2 = 77 + (1 ? z : z); + + int x3; + x3 = (1 ? z : z) + (1 ? z : z); + + + // initializer + int y1 = (1 ? z : z) + 77; + + int y2 = 77 + (1 ? z : z); + + int y3 = (1 ? z : z) + (1 ? z : z); +} diff --git a/infer/tests/codetoanalyze/c/frontend/conditional_operator/binary_operator.dot b/infer/tests/codetoanalyze/c/frontend/conditional_operator/binary_operator.dot new file mode 100644 index 000000000..c5c90d91f --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/conditional_operator/binary_operator.dot @@ -0,0 +1,201 @@ +digraph iCFG { +48 [label="48: BinaryOperatorStmt: Assign \n n$23=*&SIL_temp_conditional___43:int [line 9]\n NULLIFY(&SIL_temp_conditional___43,true); [line 9]\n *&x1:int =(n$23 + 77) [line 9]\n REMOVE_TEMPS(n$23); [line 9]\n NULLIFY(&x1,false); [line 9]\n " shape="box"] + + + 48 -> 38 ; + 48 -> 39 ; +47 [label="47: ConditinalStmt Branch \n n$22=*&z:int [line 9]\n DECLARE_LOCALS(&SIL_temp_conditional___43); [line 9]\n *&SIL_temp_conditional___43:int =n$22 [line 9]\n REMOVE_TEMPS(n$22); [line 9]\n APPLY_ABSTRACTION; [line 9]\n " shape="box"] + + + 47 -> 43 ; +46 [label="46: ConditinalStmt Branch \n n$21=*&z:int [line 9]\n DECLARE_LOCALS(&SIL_temp_conditional___43); [line 9]\n *&SIL_temp_conditional___43:int =n$21 [line 9]\n REMOVE_TEMPS(n$21); [line 9]\n APPLY_ABSTRACTION; [line 9]\n " shape="box"] + + + 46 -> 43 ; +45 [label="45: Prune (false branch) \n PRUNE((1 == 0), false); [line 9]\n " shape="invhouse"] + + + 45 -> 47 ; +44 [label="44: Prune (true branch) \n PRUNE((1 != 0), true); [line 9]\n " shape="invhouse"] + + + 44 -> 46 ; +43 [label="43: + \n " ] + + + 43 -> 48 ; +42 [label="42: BinaryOperatorStmt: Assign \n n$20=*&SIL_temp_conditional___37:int [line 12]\n NULLIFY(&SIL_temp_conditional___37,true); [line 12]\n *&x2:int =(77 + n$20) [line 12]\n REMOVE_TEMPS(n$20); [line 12]\n NULLIFY(&x2,false); [line 12]\n " shape="box"] + + + 42 -> 27 ; + 42 -> 28 ; +41 [label="41: ConditinalStmt Branch \n n$19=*&z:int [line 12]\n DECLARE_LOCALS(&SIL_temp_conditional___37); [line 12]\n *&SIL_temp_conditional___37:int =n$19 [line 12]\n REMOVE_TEMPS(n$19); [line 12]\n APPLY_ABSTRACTION; [line 12]\n " shape="box"] + + + 41 -> 37 ; +40 [label="40: ConditinalStmt Branch \n n$18=*&z:int [line 12]\n DECLARE_LOCALS(&SIL_temp_conditional___37); [line 12]\n *&SIL_temp_conditional___37:int =n$18 [line 12]\n REMOVE_TEMPS(n$18); [line 12]\n APPLY_ABSTRACTION; [line 12]\n " shape="box"] + + + 40 -> 37 ; +39 [label="39: Prune (false branch) \n PRUNE((1 == 0), false); [line 12]\n " shape="invhouse"] + + + 39 -> 41 ; +38 [label="38: Prune (true branch) \n PRUNE((1 != 0), true); [line 12]\n " shape="invhouse"] + + + 38 -> 40 ; +37 [label="37: + \n " ] + + + 37 -> 42 ; +36 [label="36: BinaryOperatorStmt: Assign \n n$14=*&SIL_temp_conditional___26:int [line 15]\n NULLIFY(&SIL_temp_conditional___26,true); [line 15]\n n$17=*&SIL_temp_conditional___31:int [line 15]\n NULLIFY(&SIL_temp_conditional___31,true); [line 15]\n *&x3:int =(n$14 + n$17) [line 15]\n REMOVE_TEMPS(n$14,n$17); [line 15]\n NULLIFY(&x3,false); [line 15]\n " shape="box"] + + + 36 -> 22 ; + 36 -> 23 ; +35 [label="35: ConditinalStmt Branch \n n$16=*&z:int [line 15]\n DECLARE_LOCALS(&SIL_temp_conditional___31); [line 15]\n *&SIL_temp_conditional___31:int =n$16 [line 15]\n REMOVE_TEMPS(n$16); [line 15]\n APPLY_ABSTRACTION; [line 15]\n " shape="box"] + + + 35 -> 31 ; +34 [label="34: ConditinalStmt Branch \n n$15=*&z:int [line 15]\n DECLARE_LOCALS(&SIL_temp_conditional___31); [line 15]\n *&SIL_temp_conditional___31:int =n$15 [line 15]\n REMOVE_TEMPS(n$15); [line 15]\n APPLY_ABSTRACTION; [line 15]\n " shape="box"] + + + 34 -> 31 ; +33 [label="33: Prune (false branch) \n PRUNE((1 == 0), false); [line 15]\n " shape="invhouse"] + + + 33 -> 35 ; +32 [label="32: Prune (true branch) \n PRUNE((1 != 0), true); [line 15]\n " shape="invhouse"] + + + 32 -> 34 ; +31 [label="31: + \n " ] + + + 31 -> 36 ; +30 [label="30: ConditinalStmt Branch \n n$13=*&z:int [line 15]\n DECLARE_LOCALS(&SIL_temp_conditional___26); [line 15]\n *&SIL_temp_conditional___26:int =n$13 [line 15]\n REMOVE_TEMPS(n$13); [line 15]\n APPLY_ABSTRACTION; [line 15]\n " shape="box"] + + + 30 -> 26 ; +29 [label="29: ConditinalStmt Branch \n n$12=*&z:int [line 15]\n DECLARE_LOCALS(&SIL_temp_conditional___26); [line 15]\n *&SIL_temp_conditional___26:int =n$12 [line 15]\n REMOVE_TEMPS(n$12); [line 15]\n APPLY_ABSTRACTION; [line 15]\n " shape="box"] + + + 29 -> 26 ; +28 [label="28: Prune (false branch) \n PRUNE((1 == 0), false); [line 15]\n " shape="invhouse"] + + + 28 -> 30 ; +27 [label="27: Prune (true branch) \n PRUNE((1 != 0), true); [line 15]\n " shape="invhouse"] + + + 27 -> 29 ; +26 [label="26: + \n " ] + + + 26 -> 32 ; + 26 -> 33 ; +25 [label="25: ConditinalStmt Branch \n n$10=*&z:int [line 19]\n DECLARE_LOCALS(&SIL_temp_conditional___21); [line 19]\n *&SIL_temp_conditional___21:int =n$10 [line 19]\n REMOVE_TEMPS(n$10); [line 19]\n APPLY_ABSTRACTION; [line 19]\n " shape="box"] + + + 25 -> 21 ; +24 [label="24: ConditinalStmt Branch \n n$9=*&z:int [line 19]\n DECLARE_LOCALS(&SIL_temp_conditional___21); [line 19]\n *&SIL_temp_conditional___21:int =n$9 [line 19]\n REMOVE_TEMPS(n$9); [line 19]\n APPLY_ABSTRACTION; [line 19]\n " shape="box"] + + + 24 -> 21 ; +23 [label="23: Prune (false branch) \n PRUNE((1 == 0), false); [line 19]\n " shape="invhouse"] + + + 23 -> 25 ; +22 [label="22: Prune (true branch) \n PRUNE((1 != 0), true); [line 19]\n " shape="invhouse"] + + + 22 -> 24 ; +21 [label="21: + \n " ] + + + 21 -> 20 ; +20 [label="20: DeclStmt \n n$11=*&SIL_temp_conditional___21:int [line 19]\n NULLIFY(&SIL_temp_conditional___21,true); [line 19]\n *&y1:int =(n$11 + 77) [line 19]\n REMOVE_TEMPS(n$11); [line 19]\n NULLIFY(&y1,false); [line 19]\n " shape="box"] + + + 20 -> 16 ; + 20 -> 17 ; +19 [label="19: ConditinalStmt Branch \n n$7=*&z:int [line 21]\n DECLARE_LOCALS(&SIL_temp_conditional___15); [line 21]\n *&SIL_temp_conditional___15:int =n$7 [line 21]\n REMOVE_TEMPS(n$7); [line 21]\n APPLY_ABSTRACTION; [line 21]\n " shape="box"] + + + 19 -> 15 ; +18 [label="18: ConditinalStmt Branch \n n$6=*&z:int [line 21]\n DECLARE_LOCALS(&SIL_temp_conditional___15); [line 21]\n *&SIL_temp_conditional___15:int =n$6 [line 21]\n REMOVE_TEMPS(n$6); [line 21]\n APPLY_ABSTRACTION; [line 21]\n " shape="box"] + + + 18 -> 15 ; +17 [label="17: Prune (false branch) \n PRUNE((1 == 0), false); [line 21]\n " shape="invhouse"] + + + 17 -> 19 ; +16 [label="16: Prune (true branch) \n PRUNE((1 != 0), true); [line 21]\n " shape="invhouse"] + + + 16 -> 18 ; +15 [label="15: + \n " ] + + + 15 -> 14 ; +14 [label="14: DeclStmt \n n$8=*&SIL_temp_conditional___15:int [line 21]\n NULLIFY(&SIL_temp_conditional___15,true); [line 21]\n *&y2:int =(77 + n$8) [line 21]\n REMOVE_TEMPS(n$8); [line 21]\n NULLIFY(&y2,false); [line 21]\n " shape="box"] + + + 14 -> 5 ; + 14 -> 6 ; +13 [label="13: ConditinalStmt Branch \n n$4=*&z:int [line 23]\n DECLARE_LOCALS(&SIL_temp_conditional___9); [line 23]\n *&SIL_temp_conditional___9:int =n$4 [line 23]\n REMOVE_TEMPS(n$4); [line 23]\n NULLIFY(&z,false); [line 23]\n APPLY_ABSTRACTION; [line 23]\n " shape="box"] + + + 13 -> 9 ; +12 [label="12: ConditinalStmt Branch \n n$3=*&z:int [line 23]\n DECLARE_LOCALS(&SIL_temp_conditional___9); [line 23]\n *&SIL_temp_conditional___9:int =n$3 [line 23]\n REMOVE_TEMPS(n$3); [line 23]\n NULLIFY(&z,false); [line 23]\n APPLY_ABSTRACTION; [line 23]\n " shape="box"] + + + 12 -> 9 ; +11 [label="11: Prune (false branch) \n PRUNE((1 == 0), false); [line 23]\n " shape="invhouse"] + + + 11 -> 13 ; +10 [label="10: Prune (true branch) \n PRUNE((1 != 0), true); [line 23]\n " shape="invhouse"] + + + 10 -> 12 ; +9 [label="9: + \n " ] + + + 9 -> 3 ; +8 [label="8: ConditinalStmt Branch \n n$1=*&z:int [line 23]\n DECLARE_LOCALS(&SIL_temp_conditional___4); [line 23]\n *&SIL_temp_conditional___4:int =n$1 [line 23]\n REMOVE_TEMPS(n$1); [line 23]\n APPLY_ABSTRACTION; [line 23]\n " shape="box"] + + + 8 -> 4 ; +7 [label="7: ConditinalStmt Branch \n n$0=*&z:int [line 23]\n DECLARE_LOCALS(&SIL_temp_conditional___4); [line 23]\n *&SIL_temp_conditional___4:int =n$0 [line 23]\n REMOVE_TEMPS(n$0); [line 23]\n APPLY_ABSTRACTION; [line 23]\n " shape="box"] + + + 7 -> 4 ; +6 [label="6: Prune (false branch) \n PRUNE((1 == 0), false); [line 23]\n " shape="invhouse"] + + + 6 -> 8 ; +5 [label="5: Prune (true branch) \n PRUNE((1 != 0), true); [line 23]\n " shape="invhouse"] + + + 5 -> 7 ; +4 [label="4: + \n " ] + + + 4 -> 10 ; + 4 -> 11 ; +3 [label="3: DeclStmt \n n$2=*&SIL_temp_conditional___4:int [line 23]\n NULLIFY(&SIL_temp_conditional___4,true); [line 23]\n n$5=*&SIL_temp_conditional___9:int [line 23]\n NULLIFY(&SIL_temp_conditional___9,true); [line 23]\n *&y3:int =(n$2 + n$5) [line 23]\n REMOVE_TEMPS(n$2,n$5); [line 23]\n NULLIFY(&y3,false); [line 23]\n APPLY_ABSTRACTION; [line 23]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit binop_with_side_effects \n " color=yellow style=filled] + + +1 [label="1: Start binop_with_side_effects\nFormals: z:int \nLocals: x1:int x2:int x3:int y1:int y2:int y3:int \n DECLARE_LOCALS(&return,&x1,&x2,&x3,&y1,&y2,&y3); [line 6]\n NULLIFY(&x1,false); [line 6]\n NULLIFY(&x2,false); [line 6]\n NULLIFY(&x3,false); [line 6]\n NULLIFY(&y1,false); [line 6]\n NULLIFY(&y2,false); [line 6]\n NULLIFY(&y3,false); [line 6]\n " color=yellow style=filled] + + + 1 -> 44 ; + 1 -> 45 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/conditional_operator/cond2.c b/infer/tests/codetoanalyze/c/frontend/conditional_operator/cond2.c new file mode 100644 index 000000000..249591e03 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/conditional_operator/cond2.c @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +int foo() +{ + int x=5; + if (3 < 4 || 7<(x++)) { x=0;}; + int y =19; + int n = ((3 < 4 || 7<((x++)-y )) ? 1 : 2); + n = (2 < 1 ? 1 : (5>4 ? 1:2)); + return (0 + (7 > 9 ? 1 : 0)); + +} + +int bar() { + int x,y; + y=(x=1) > 1 ? (++x) : (x--); + return (0 + ( (3>4 ? 1:2) > 1 ? (x=1) : 0)); +} diff --git a/infer/tests/codetoanalyze/c/frontend/conditional_operator/cond2.dot b/infer/tests/codetoanalyze/c/frontend/conditional_operator/cond2.dot new file mode 100644 index 000000000..cb757c968 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/conditional_operator/cond2.dot @@ -0,0 +1,238 @@ +digraph iCFG { +57 [label="57: BinaryOperatorStmt: Assign \n n$14=*&SIL_temp_conditional___51:int [line 19]\n NULLIFY(&SIL_temp_conditional___51,true); [line 19]\n *&y:int =n$14 [line 19]\n REMOVE_TEMPS(n$14); [line 19]\n NULLIFY(&y,false); [line 19]\n " shape="box"] + + + 57 -> 42 ; + 57 -> 43 ; +56 [label="56: UnaryOperator \n n$13=*&x:int [line 19]\n *&x:int =(n$13 - 1) [line 19]\n DECLARE_LOCALS(&SIL_temp_conditional___51); [line 19]\n *&SIL_temp_conditional___51:int =n$13 [line 19]\n REMOVE_TEMPS(n$13); [line 19]\n NULLIFY(&x,false); [line 19]\n APPLY_ABSTRACTION; [line 19]\n " shape="box"] + + + 56 -> 51 ; +55 [label="55: UnaryOperator \n n$12=*&x:int [line 19]\n *&x:int =(n$12 + 1) [line 19]\n DECLARE_LOCALS(&SIL_temp_conditional___51); [line 19]\n *&SIL_temp_conditional___51:int =(n$12 + 1) [line 19]\n REMOVE_TEMPS(n$12); [line 19]\n NULLIFY(&x,false); [line 19]\n APPLY_ABSTRACTION; [line 19]\n " shape="box"] + + + 55 -> 51 ; +54 [label="54: Prune (false branch) \n PRUNE(((n$11 > 1) == 0), false); [line 19]\n REMOVE_TEMPS(n$11); [line 19]\n " shape="invhouse"] + + + 54 -> 56 ; +53 [label="53: Prune (true branch) \n PRUNE(((n$11 > 1) != 0), true); [line 19]\n REMOVE_TEMPS(n$11); [line 19]\n " shape="invhouse"] + + + 53 -> 55 ; +52 [label="52: BinaryOperatorStmt: GT \n *&x:int =1 [line 19]\n n$11=*&x:int [line 19]\n " shape="box"] + + + 52 -> 53 ; + 52 -> 54 ; +51 [label="51: + \n " ] + + + 51 -> 57 ; +50 [label="50: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___40); [line 20]\n *&SIL_temp_conditional___40:int =0 [line 20]\n APPLY_ABSTRACTION; [line 20]\n " shape="box"] + + + 50 -> 40 ; +49 [label="49: BinaryOperatorStmt: Assign \n *&x:int =1 [line 20]\n n$9=*&x:int [line 20]\n DECLARE_LOCALS(&SIL_temp_conditional___40); [line 20]\n *&SIL_temp_conditional___40:int =n$9 [line 20]\n REMOVE_TEMPS(n$9); [line 20]\n NULLIFY(&x,false); [line 20]\n APPLY_ABSTRACTION; [line 20]\n " shape="box"] + + + 49 -> 40 ; +48 [label="48: Prune (false branch) \n PRUNE(((n$8 > 1) == 0), false); [line 20]\n REMOVE_TEMPS(n$8); [line 20]\n " shape="invhouse"] + + + 48 -> 50 ; +47 [label="47: Prune (true branch) \n PRUNE(((n$8 > 1) != 0), true); [line 20]\n REMOVE_TEMPS(n$8); [line 20]\n " shape="invhouse"] + + + 47 -> 49 ; +46 [label="46: BinaryOperatorStmt: GT \n n$8=*&SIL_temp_conditional___41:int [line 20]\n NULLIFY(&SIL_temp_conditional___41,true); [line 20]\n " shape="box"] + + + 46 -> 47 ; + 46 -> 48 ; +45 [label="45: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___41); [line 20]\n *&SIL_temp_conditional___41:int =2 [line 20]\n APPLY_ABSTRACTION; [line 20]\n " shape="box"] + + + 45 -> 41 ; +44 [label="44: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___41); [line 20]\n *&SIL_temp_conditional___41:int =1 [line 20]\n APPLY_ABSTRACTION; [line 20]\n " shape="box"] + + + 44 -> 41 ; +43 [label="43: Prune (false branch) \n PRUNE(((3 > 4) == 0), false); [line 20]\n " shape="invhouse"] + + + 43 -> 45 ; +42 [label="42: Prune (true branch) \n PRUNE(((3 > 4) != 0), true); [line 20]\n " shape="invhouse"] + + + 42 -> 44 ; +41 [label="41: + \n " ] + + + 41 -> 46 ; +40 [label="40: + \n " ] + + + 40 -> 39 ; +39 [label="39: Return Stmt \n n$10=*&SIL_temp_conditional___40:int [line 20]\n NULLIFY(&SIL_temp_conditional___40,true); [line 20]\n *&return:int =(0 + n$10) [line 20]\n REMOVE_TEMPS(n$10); [line 20]\n APPLY_ABSTRACTION; [line 20]\n " shape="box"] + + + 39 -> 38 ; +38 [label="38: Exit bar \n " color=yellow style=filled] + + +37 [label="37: Start bar\nFormals: \nLocals: x:int y:int \n DECLARE_LOCALS(&return,&x,&y); [line 17]\n NULLIFY(&x,false); [line 17]\n NULLIFY(&y,false); [line 17]\n " color=yellow style=filled] + + + 37 -> 52 ; +36 [label="36: DeclStmt \n *&x:int =5 [line 8]\n " shape="box"] + + + 36 -> 30 ; + 36 -> 31 ; +35 [label="35: BinaryOperatorStmt: Assign \n NULLIFY(&x,false); [line 9]\n *&x:int =0 [line 9]\n APPLY_ABSTRACTION; [line 9]\n " shape="box"] + + + 35 -> 29 ; +34 [label="34: Prune (false branch) \n PRUNE(((7 < n$7) == 0), false); [line 9]\n REMOVE_TEMPS(n$7); [line 9]\n APPLY_ABSTRACTION; [line 9]\n " shape="invhouse"] + + + 34 -> 29 ; +33 [label="33: Prune (true branch) \n PRUNE(((7 < n$7) != 0), true); [line 9]\n REMOVE_TEMPS(n$7); [line 9]\n APPLY_ABSTRACTION; [line 9]\n " shape="invhouse"] + + + 33 -> 35 ; +32 [label="32: BinaryOperatorStmt: LT \n n$7=*&x:int [line 9]\n *&x:int =(n$7 + 1) [line 9]\n " shape="box"] + + + 32 -> 33 ; + 32 -> 34 ; +31 [label="31: Prune (false branch) \n PRUNE(((3 < 4) == 0), false); [line 9]\n " shape="invhouse"] + + + 31 -> 32 ; +30 [label="30: Prune (true branch) \n PRUNE(((3 < 4) != 0), true); [line 9]\n APPLY_ABSTRACTION; [line 9]\n " shape="invhouse"] + + + 30 -> 35 ; +29 [label="29: + \n " ] + + + 29 -> 28 ; +28 [label="28: DeclStmt \n *&y:int =19 [line 10]\n " shape="box"] + + + 28 -> 21 ; + 28 -> 22 ; +27 [label="27: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___20); [line 11]\n *&SIL_temp_conditional___20:int =2 [line 11]\n APPLY_ABSTRACTION; [line 11]\n " shape="box"] + + + 27 -> 20 ; +26 [label="26: ConditinalStmt Branch \n NULLIFY(&x,false); [line 11]\n NULLIFY(&y,false); [line 11]\n DECLARE_LOCALS(&SIL_temp_conditional___20); [line 11]\n *&SIL_temp_conditional___20:int =1 [line 11]\n APPLY_ABSTRACTION; [line 11]\n " shape="box"] + + + 26 -> 20 ; +25 [label="25: Prune (false branch) \n PRUNE(((7 < (n$4 - n$5)) == 0), false); [line 11]\n REMOVE_TEMPS(n$4,n$5); [line 11]\n " shape="invhouse"] + + + 25 -> 27 ; +24 [label="24: Prune (true branch) \n PRUNE(((7 < (n$4 - n$5)) != 0), true); [line 11]\n REMOVE_TEMPS(n$4,n$5); [line 11]\n APPLY_ABSTRACTION; [line 11]\n " shape="invhouse"] + + + 24 -> 26 ; +23 [label="23: BinaryOperatorStmt: LT \n n$4=*&x:int [line 11]\n *&x:int =(n$4 + 1) [line 11]\n n$5=*&y:int [line 11]\n NULLIFY(&x,false); [line 11]\n NULLIFY(&y,false); [line 11]\n " shape="box"] + + + 23 -> 24 ; + 23 -> 25 ; +22 [label="22: Prune (false branch) \n PRUNE(((3 < 4) == 0), false); [line 11]\n " shape="invhouse"] + + + 22 -> 23 ; +21 [label="21: Prune (true branch) \n PRUNE(((3 < 4) != 0), true); [line 11]\n APPLY_ABSTRACTION; [line 11]\n " shape="invhouse"] + + + 21 -> 26 ; +20 [label="20: + \n " ] + + + 20 -> 19 ; +19 [label="19: DeclStmt \n n$6=*&SIL_temp_conditional___20:int [line 11]\n NULLIFY(&SIL_temp_conditional___20,true); [line 11]\n *&n:int =n$6 [line 11]\n REMOVE_TEMPS(n$6); [line 11]\n NULLIFY(&n,false); [line 11]\n " shape="box"] + + + 19 -> 10 ; + 19 -> 11 ; +18 [label="18: BinaryOperatorStmt: Assign \n n$3=*&SIL_temp_conditional___9:int [line 12]\n NULLIFY(&SIL_temp_conditional___9,true); [line 12]\n *&n:int =n$3 [line 12]\n REMOVE_TEMPS(n$3); [line 12]\n NULLIFY(&n,false); [line 12]\n " shape="box"] + + + 18 -> 5 ; + 18 -> 6 ; +17 [label="17: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___13); [line 12]\n *&SIL_temp_conditional___13:int =2 [line 12]\n APPLY_ABSTRACTION; [line 12]\n " shape="box"] + + + 17 -> 13 ; +16 [label="16: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___13); [line 12]\n *&SIL_temp_conditional___13:int =1 [line 12]\n APPLY_ABSTRACTION; [line 12]\n " shape="box"] + + + 16 -> 13 ; +15 [label="15: Prune (false branch) \n PRUNE(((5 > 4) == 0), false); [line 12]\n " shape="invhouse"] + + + 15 -> 17 ; +14 [label="14: Prune (true branch) \n PRUNE(((5 > 4) != 0), true); [line 12]\n " shape="invhouse"] + + + 14 -> 16 ; +13 [label="13: Temp Join Node \n n$1=*&SIL_temp_conditional___13:int [line 12]\n DECLARE_LOCALS(&SIL_temp_conditional___9); [line 12]\n *&SIL_temp_conditional___9:int =n$1 [line 12]\n NULLIFY(&SIL_temp_conditional___13,true); [line 12]\n REMOVE_TEMPS(n$1); [line 12]\n APPLY_ABSTRACTION; [line 12]\n " shape="box"] + + + 13 -> 9 ; +12 [label="12: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___9); [line 12]\n *&SIL_temp_conditional___9:int =1 [line 12]\n APPLY_ABSTRACTION; [line 12]\n " shape="box"] + + + 12 -> 9 ; +11 [label="11: Prune (false branch) \n PRUNE(((2 < 1) == 0), false); [line 12]\n " shape="invhouse"] + + + 11 -> 14 ; + 11 -> 15 ; +10 [label="10: Prune (true branch) \n PRUNE(((2 < 1) != 0), true); [line 12]\n " shape="invhouse"] + + + 10 -> 12 ; +9 [label="9: + \n " ] + + + 9 -> 18 ; +8 [label="8: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___4); [line 13]\n *&SIL_temp_conditional___4:int =0 [line 13]\n APPLY_ABSTRACTION; [line 13]\n " shape="box"] + + + 8 -> 4 ; +7 [label="7: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___4); [line 13]\n *&SIL_temp_conditional___4:int =1 [line 13]\n APPLY_ABSTRACTION; [line 13]\n " shape="box"] + + + 7 -> 4 ; +6 [label="6: Prune (false branch) \n PRUNE(((7 > 9) == 0), false); [line 13]\n " shape="invhouse"] + + + 6 -> 8 ; +5 [label="5: Prune (true branch) \n PRUNE(((7 > 9) != 0), true); [line 13]\n " shape="invhouse"] + + + 5 -> 7 ; +4 [label="4: + \n " ] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n n$0=*&SIL_temp_conditional___4:int [line 13]\n NULLIFY(&SIL_temp_conditional___4,true); [line 13]\n *&return:int =(0 + n$0) [line 13]\n REMOVE_TEMPS(n$0); [line 13]\n APPLY_ABSTRACTION; [line 13]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit foo \n " color=yellow style=filled] + + +1 [label="1: Start foo\nFormals: \nLocals: x:int y:int n:int \n DECLARE_LOCALS(&return,&x,&y,&n); [line 6]\n NULLIFY(&n,false); [line 6]\n NULLIFY(&x,false); [line 6]\n NULLIFY(&y,false); [line 6]\n " color=yellow style=filled] + + + 1 -> 36 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/conditional_operator/conditional_operator.c b/infer/tests/codetoanalyze/c/frontend/conditional_operator/conditional_operator.c new file mode 100644 index 000000000..d1cc3598e --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/conditional_operator/conditional_operator.c @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +#include + +int test2(int x) { + return x; +} + + +int test(int b) { + return test2(b ? b : 1); +} + +int test1(int b) { + int x = b ? b : 1; + return x; +} + +int test3(int b) { + int x = b ? : 1; + return x; +} + + +int test4(int b) { + return test2(b ? : 1); +} + + +int test5(int b) { + return b ? : 1; +} + +int test6(int *p) { + int z = 1 ? *p : 0; + return z; +} diff --git a/infer/tests/codetoanalyze/c/frontend/conditional_operator/conditional_operator.dot b/infer/tests/codetoanalyze/c/frontend/conditional_operator/conditional_operator.dot new file mode 100644 index 000000000..77f249f0b --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/conditional_operator/conditional_operator.dot @@ -0,0 +1,217 @@ +digraph iCFG { +54 [label="54: ConditinalStmt Branch \n NULLIFY(&p,false); [line 38]\n DECLARE_LOCALS(&SIL_temp_conditional___50); [line 38]\n *&SIL_temp_conditional___50:int =0 [line 38]\n APPLY_ABSTRACTION; [line 38]\n " shape="box"] + + + 54 -> 50 ; +53 [label="53: UnaryOperator \n n$21=*&p:int * [line 38]\n n$22=*n$21:int [line 38]\n DECLARE_LOCALS(&SIL_temp_conditional___50); [line 38]\n *&SIL_temp_conditional___50:int =n$22 [line 38]\n REMOVE_TEMPS(n$21,n$22); [line 38]\n NULLIFY(&p,false); [line 38]\n APPLY_ABSTRACTION; [line 38]\n " shape="box"] + + + 53 -> 50 ; +52 [label="52: Prune (false branch) \n PRUNE((1 == 0), false); [line 38]\n " shape="invhouse"] + + + 52 -> 54 ; +51 [label="51: Prune (true branch) \n PRUNE((1 != 0), true); [line 38]\n " shape="invhouse"] + + + 51 -> 53 ; +50 [label="50: + \n " ] + + + 50 -> 49 ; +49 [label="49: DeclStmt \n n$23=*&SIL_temp_conditional___50:int [line 38]\n NULLIFY(&SIL_temp_conditional___50,true); [line 38]\n *&z:int =n$23 [line 38]\n REMOVE_TEMPS(n$23); [line 38]\n " shape="box"] + + + 49 -> 48 ; +48 [label="48: Return Stmt \n n$20=*&z:int [line 39]\n *&return:int =n$20 [line 39]\n REMOVE_TEMPS(n$20); [line 39]\n NULLIFY(&z,false); [line 39]\n APPLY_ABSTRACTION; [line 39]\n " shape="box"] + + + 48 -> 47 ; +47 [label="47: Exit test6 \n " color=yellow style=filled] + + +46 [label="46: Start test6\nFormals: p:int *\nLocals: z:int \n DECLARE_LOCALS(&return,&z); [line 37]\n NULLIFY(&z,false); [line 37]\n " color=yellow style=filled] + + + 46 -> 51 ; + 46 -> 52 ; +45 [label="45: ConditinalStmt Branch \n NULLIFY(&b,false); [line 34]\n DECLARE_LOCALS(&SIL_temp_conditional___41); [line 34]\n *&SIL_temp_conditional___41:int =1 [line 34]\n APPLY_ABSTRACTION; [line 34]\n " shape="box"] + + + 45 -> 41 ; +44 [label="44: ConditinalStmt Branch \n n$18=*&b:int [line 34]\n DECLARE_LOCALS(&SIL_temp_conditional___41); [line 34]\n *&SIL_temp_conditional___41:int =n$18 [line 34]\n REMOVE_TEMPS(n$18); [line 34]\n NULLIFY(&b,false); [line 34]\n APPLY_ABSTRACTION; [line 34]\n " shape="box"] + + + 44 -> 41 ; +43 [label="43: Prune (false branch) \n n$17=*&b:int [line 34]\n PRUNE((n$17 == 0), false); [line 34]\n REMOVE_TEMPS(n$17); [line 34]\n " shape="invhouse"] + + + 43 -> 45 ; +42 [label="42: Prune (true branch) \n n$17=*&b:int [line 34]\n PRUNE((n$17 != 0), true); [line 34]\n REMOVE_TEMPS(n$17); [line 34]\n " shape="invhouse"] + + + 42 -> 44 ; +41 [label="41: + \n " ] + + + 41 -> 40 ; +40 [label="40: Return Stmt \n n$19=*&SIL_temp_conditional___41:int [line 34]\n NULLIFY(&SIL_temp_conditional___41,true); [line 34]\n *&return:int =n$19 [line 34]\n REMOVE_TEMPS(n$19); [line 34]\n APPLY_ABSTRACTION; [line 34]\n " shape="box"] + + + 40 -> 39 ; +39 [label="39: Exit test5 \n " color=yellow style=filled] + + +38 [label="38: Start test5\nFormals: b:int \nLocals: \n DECLARE_LOCALS(&return); [line 33]\n " color=yellow style=filled] + + + 38 -> 42 ; + 38 -> 43 ; +37 [label="37: ConditinalStmt Branch \n NULLIFY(&b,false); [line 29]\n DECLARE_LOCALS(&SIL_temp_conditional___33); [line 29]\n *&SIL_temp_conditional___33:int =1 [line 29]\n APPLY_ABSTRACTION; [line 29]\n " shape="box"] + + + 37 -> 33 ; +36 [label="36: ConditinalStmt Branch \n n$14=*&b:int [line 29]\n DECLARE_LOCALS(&SIL_temp_conditional___33); [line 29]\n *&SIL_temp_conditional___33:int =n$14 [line 29]\n REMOVE_TEMPS(n$14); [line 29]\n NULLIFY(&b,false); [line 29]\n APPLY_ABSTRACTION; [line 29]\n " shape="box"] + + + 36 -> 33 ; +35 [label="35: Prune (false branch) \n n$13=*&b:int [line 29]\n PRUNE((n$13 == 0), false); [line 29]\n REMOVE_TEMPS(n$13); [line 29]\n " shape="invhouse"] + + + 35 -> 37 ; +34 [label="34: Prune (true branch) \n n$13=*&b:int [line 29]\n PRUNE((n$13 != 0), true); [line 29]\n REMOVE_TEMPS(n$13); [line 29]\n " shape="invhouse"] + + + 34 -> 36 ; +33 [label="33: + \n " ] + + + 33 -> 32 ; +32 [label="32: Return Stmt \n n$15=*&SIL_temp_conditional___33:int [line 29]\n NULLIFY(&SIL_temp_conditional___33,true); [line 29]\n n$16=_fun_test2(n$15:int ) [line 29]\n *&return:int =n$16 [line 29]\n REMOVE_TEMPS(n$15,n$16); [line 29]\n APPLY_ABSTRACTION; [line 29]\n " shape="box"] + + + 32 -> 31 ; +31 [label="31: Exit test4 \n " color=yellow style=filled] + + +30 [label="30: Start test4\nFormals: b:int \nLocals: \n DECLARE_LOCALS(&return); [line 28]\n " color=yellow style=filled] + + + 30 -> 34 ; + 30 -> 35 ; +29 [label="29: ConditinalStmt Branch \n NULLIFY(&b,false); [line 23]\n DECLARE_LOCALS(&SIL_temp_conditional___25); [line 23]\n *&SIL_temp_conditional___25:int =1 [line 23]\n APPLY_ABSTRACTION; [line 23]\n " shape="box"] + + + 29 -> 25 ; +28 [label="28: ConditinalStmt Branch \n n$11=*&b:int [line 23]\n DECLARE_LOCALS(&SIL_temp_conditional___25); [line 23]\n *&SIL_temp_conditional___25:int =n$11 [line 23]\n REMOVE_TEMPS(n$11); [line 23]\n NULLIFY(&b,false); [line 23]\n APPLY_ABSTRACTION; [line 23]\n " shape="box"] + + + 28 -> 25 ; +27 [label="27: Prune (false branch) \n n$10=*&b:int [line 23]\n PRUNE((n$10 == 0), false); [line 23]\n REMOVE_TEMPS(n$10); [line 23]\n " shape="invhouse"] + + + 27 -> 29 ; +26 [label="26: Prune (true branch) \n n$10=*&b:int [line 23]\n PRUNE((n$10 != 0), true); [line 23]\n REMOVE_TEMPS(n$10); [line 23]\n " shape="invhouse"] + + + 26 -> 28 ; +25 [label="25: + \n " ] + + + 25 -> 24 ; +24 [label="24: DeclStmt \n n$12=*&SIL_temp_conditional___25:int [line 23]\n NULLIFY(&SIL_temp_conditional___25,true); [line 23]\n *&x:int =n$12 [line 23]\n REMOVE_TEMPS(n$12); [line 23]\n " shape="box"] + + + 24 -> 23 ; +23 [label="23: Return Stmt \n n$9=*&x:int [line 24]\n *&return:int =n$9 [line 24]\n REMOVE_TEMPS(n$9); [line 24]\n NULLIFY(&x,false); [line 24]\n APPLY_ABSTRACTION; [line 24]\n " shape="box"] + + + 23 -> 22 ; +22 [label="22: Exit test3 \n " color=yellow style=filled] + + +21 [label="21: Start test3\nFormals: b:int \nLocals: x:int \n DECLARE_LOCALS(&return,&x); [line 22]\n NULLIFY(&x,false); [line 22]\n " color=yellow style=filled] + + + 21 -> 26 ; + 21 -> 27 ; +20 [label="20: ConditinalStmt Branch \n NULLIFY(&b,false); [line 18]\n DECLARE_LOCALS(&SIL_temp_conditional___16); [line 18]\n *&SIL_temp_conditional___16:int =1 [line 18]\n APPLY_ABSTRACTION; [line 18]\n " shape="box"] + + + 20 -> 16 ; +19 [label="19: ConditinalStmt Branch \n n$7=*&b:int [line 18]\n DECLARE_LOCALS(&SIL_temp_conditional___16); [line 18]\n *&SIL_temp_conditional___16:int =n$7 [line 18]\n REMOVE_TEMPS(n$7); [line 18]\n NULLIFY(&b,false); [line 18]\n APPLY_ABSTRACTION; [line 18]\n " shape="box"] + + + 19 -> 16 ; +18 [label="18: Prune (false branch) \n n$6=*&b:int [line 18]\n PRUNE((n$6 == 0), false); [line 18]\n REMOVE_TEMPS(n$6); [line 18]\n " shape="invhouse"] + + + 18 -> 20 ; +17 [label="17: Prune (true branch) \n n$6=*&b:int [line 18]\n PRUNE((n$6 != 0), true); [line 18]\n REMOVE_TEMPS(n$6); [line 18]\n " shape="invhouse"] + + + 17 -> 19 ; +16 [label="16: + \n " ] + + + 16 -> 15 ; +15 [label="15: DeclStmt \n n$8=*&SIL_temp_conditional___16:int [line 18]\n NULLIFY(&SIL_temp_conditional___16,true); [line 18]\n *&x:int =n$8 [line 18]\n REMOVE_TEMPS(n$8); [line 18]\n " shape="box"] + + + 15 -> 14 ; +14 [label="14: Return Stmt \n n$5=*&x:int [line 19]\n *&return:int =n$5 [line 19]\n REMOVE_TEMPS(n$5); [line 19]\n NULLIFY(&x,false); [line 19]\n APPLY_ABSTRACTION; [line 19]\n " shape="box"] + + + 14 -> 13 ; +13 [label="13: Exit test1 \n " color=yellow style=filled] + + +12 [label="12: Start test1\nFormals: b:int \nLocals: x:int \n DECLARE_LOCALS(&return,&x); [line 17]\n NULLIFY(&x,false); [line 17]\n " color=yellow style=filled] + + + 12 -> 17 ; + 12 -> 18 ; +11 [label="11: ConditinalStmt Branch \n NULLIFY(&b,false); [line 14]\n DECLARE_LOCALS(&SIL_temp_conditional___7); [line 14]\n *&SIL_temp_conditional___7:int =1 [line 14]\n APPLY_ABSTRACTION; [line 14]\n " shape="box"] + + + 11 -> 7 ; +10 [label="10: ConditinalStmt Branch \n n$2=*&b:int [line 14]\n DECLARE_LOCALS(&SIL_temp_conditional___7); [line 14]\n *&SIL_temp_conditional___7:int =n$2 [line 14]\n REMOVE_TEMPS(n$2); [line 14]\n NULLIFY(&b,false); [line 14]\n APPLY_ABSTRACTION; [line 14]\n " shape="box"] + + + 10 -> 7 ; +9 [label="9: Prune (false branch) \n n$1=*&b:int [line 14]\n PRUNE((n$1 == 0), false); [line 14]\n REMOVE_TEMPS(n$1); [line 14]\n " shape="invhouse"] + + + 9 -> 11 ; +8 [label="8: Prune (true branch) \n n$1=*&b:int [line 14]\n PRUNE((n$1 != 0), true); [line 14]\n REMOVE_TEMPS(n$1); [line 14]\n " shape="invhouse"] + + + 8 -> 10 ; +7 [label="7: + \n " ] + + + 7 -> 6 ; +6 [label="6: Return Stmt \n n$3=*&SIL_temp_conditional___7:int [line 14]\n NULLIFY(&SIL_temp_conditional___7,true); [line 14]\n n$4=_fun_test2(n$3:int ) [line 14]\n *&return:int =n$4 [line 14]\n REMOVE_TEMPS(n$3,n$4); [line 14]\n APPLY_ABSTRACTION; [line 14]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: Exit test \n " color=yellow style=filled] + + +4 [label="4: Start test\nFormals: b:int \nLocals: \n DECLARE_LOCALS(&return); [line 13]\n " color=yellow style=filled] + + + 4 -> 8 ; + 4 -> 9 ; +3 [label="3: Return Stmt \n n$0=*&x:int [line 9]\n *&return:int =n$0 [line 9]\n REMOVE_TEMPS(n$0); [line 9]\n NULLIFY(&x,false); [line 9]\n APPLY_ABSTRACTION; [line 9]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit test2 \n " color=yellow style=filled] + + +1 [label="1: Start test2\nFormals: x:int \nLocals: \n DECLARE_LOCALS(&return); [line 8]\n " color=yellow style=filled] + + + 1 -> 3 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/conditional_operator/function_call.c b/infer/tests/codetoanalyze/c/frontend/conditional_operator/function_call.c new file mode 100644 index 000000000..f40cc2e56 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/conditional_operator/function_call.c @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +void some_f(int, int, int); + +void fun_ifthenelse1() { + (1 ? some_f : some_f)(1, 2, 3); +} + +void fun_ifthenelse2() { + (1 ? some_f : some_f)(0 ? 1 : 1, 0 ? 2 : 2, 0 ? 3 : 3); +} + +void fun_ifthenelse3() { + some_f(0 ? 1 : 1, 0 ? 2 : 2, 0 ? 3 : 3); +} + +void fun_ifthenelse4() { + (1 ? some_f : some_f)(0 ? 1 : 1, 2, 0 ? 3 : 3); +} diff --git a/infer/tests/codetoanalyze/c/frontend/conditional_operator/function_call.dot b/infer/tests/codetoanalyze/c/frontend/conditional_operator/function_call.dot new file mode 100644 index 000000000..e2f84e612 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/conditional_operator/function_call.dot @@ -0,0 +1,277 @@ +digraph iCFG { +67 [label="67: Call n$8 \n n$8=*&SIL_temp_conditional___52:_fn_ (*) [line 21]\n NULLIFY(&SIL_temp_conditional___52,true); [line 21]\n n$9=*&SIL_temp_conditional___57:int [line 21]\n NULLIFY(&SIL_temp_conditional___57,true); [line 21]\n n$10=*&SIL_temp_conditional___62:int [line 21]\n NULLIFY(&SIL_temp_conditional___62,true); [line 21]\n n$8(n$9:int ,2:int ,n$10:int ) [line 21]\n REMOVE_TEMPS(n$8,n$9,n$10); [line 21]\n APPLY_ABSTRACTION; [line 21]\n " shape="box"] + + + 67 -> 51 ; +66 [label="66: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___62); [line 21]\n *&SIL_temp_conditional___62:int =3 [line 21]\n APPLY_ABSTRACTION; [line 21]\n " shape="box"] + + + 66 -> 62 ; +65 [label="65: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___62); [line 21]\n *&SIL_temp_conditional___62:int =3 [line 21]\n APPLY_ABSTRACTION; [line 21]\n " shape="box"] + + + 65 -> 62 ; +64 [label="64: Prune (false branch) \n PRUNE((0 == 0), false); [line 21]\n " shape="invhouse"] + + + 64 -> 66 ; +63 [label="63: Prune (true branch) \n PRUNE((0 != 0), true); [line 21]\n " shape="invhouse"] + + + 63 -> 65 ; +62 [label="62: + \n " ] + + + 62 -> 67 ; +61 [label="61: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___57); [line 21]\n *&SIL_temp_conditional___57:int =1 [line 21]\n APPLY_ABSTRACTION; [line 21]\n " shape="box"] + + + 61 -> 57 ; +60 [label="60: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___57); [line 21]\n *&SIL_temp_conditional___57:int =1 [line 21]\n APPLY_ABSTRACTION; [line 21]\n " shape="box"] + + + 60 -> 57 ; +59 [label="59: Prune (false branch) \n PRUNE((0 == 0), false); [line 21]\n " shape="invhouse"] + + + 59 -> 61 ; +58 [label="58: Prune (true branch) \n PRUNE((0 != 0), true); [line 21]\n " shape="invhouse"] + + + 58 -> 60 ; +57 [label="57: + \n " ] + + + 57 -> 63 ; + 57 -> 64 ; +56 [label="56: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___52); [line 21]\n *&SIL_temp_conditional___52:_fn_ (*)=_fun_some_f [line 21]\n APPLY_ABSTRACTION; [line 21]\n " shape="box"] + + + 56 -> 52 ; +55 [label="55: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___52); [line 21]\n *&SIL_temp_conditional___52:_fn_ (*)=_fun_some_f [line 21]\n APPLY_ABSTRACTION; [line 21]\n " shape="box"] + + + 55 -> 52 ; +54 [label="54: Prune (false branch) \n PRUNE((1 == 0), false); [line 21]\n " shape="invhouse"] + + + 54 -> 56 ; +53 [label="53: Prune (true branch) \n PRUNE((1 != 0), true); [line 21]\n " shape="invhouse"] + + + 53 -> 55 ; +52 [label="52: + \n " ] + + + 52 -> 58 ; + 52 -> 59 ; +51 [label="51: Exit fun_ifthenelse4 \n " color=yellow style=filled] + + +50 [label="50: Start fun_ifthenelse4\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 20]\n " color=yellow style=filled] + + + 50 -> 53 ; + 50 -> 54 ; +49 [label="49: Call _fun_some_f \n n$5=*&SIL_temp_conditional___34:int [line 17]\n NULLIFY(&SIL_temp_conditional___34,true); [line 17]\n n$6=*&SIL_temp_conditional___39:int [line 17]\n NULLIFY(&SIL_temp_conditional___39,true); [line 17]\n n$7=*&SIL_temp_conditional___44:int [line 17]\n NULLIFY(&SIL_temp_conditional___44,true); [line 17]\n _fun_some_f(n$5:int ,n$6:int ,n$7:int ) [line 17]\n REMOVE_TEMPS(n$5,n$6,n$7); [line 17]\n APPLY_ABSTRACTION; [line 17]\n " shape="box"] + + + 49 -> 33 ; +48 [label="48: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___44); [line 17]\n *&SIL_temp_conditional___44:int =3 [line 17]\n APPLY_ABSTRACTION; [line 17]\n " shape="box"] + + + 48 -> 44 ; +47 [label="47: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___44); [line 17]\n *&SIL_temp_conditional___44:int =3 [line 17]\n APPLY_ABSTRACTION; [line 17]\n " shape="box"] + + + 47 -> 44 ; +46 [label="46: Prune (false branch) \n PRUNE((0 == 0), false); [line 17]\n " shape="invhouse"] + + + 46 -> 48 ; +45 [label="45: Prune (true branch) \n PRUNE((0 != 0), true); [line 17]\n " shape="invhouse"] + + + 45 -> 47 ; +44 [label="44: + \n " ] + + + 44 -> 49 ; +43 [label="43: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___39); [line 17]\n *&SIL_temp_conditional___39:int =2 [line 17]\n APPLY_ABSTRACTION; [line 17]\n " shape="box"] + + + 43 -> 39 ; +42 [label="42: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___39); [line 17]\n *&SIL_temp_conditional___39:int =2 [line 17]\n APPLY_ABSTRACTION; [line 17]\n " shape="box"] + + + 42 -> 39 ; +41 [label="41: Prune (false branch) \n PRUNE((0 == 0), false); [line 17]\n " shape="invhouse"] + + + 41 -> 43 ; +40 [label="40: Prune (true branch) \n PRUNE((0 != 0), true); [line 17]\n " shape="invhouse"] + + + 40 -> 42 ; +39 [label="39: + \n " ] + + + 39 -> 45 ; + 39 -> 46 ; +38 [label="38: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___34); [line 17]\n *&SIL_temp_conditional___34:int =1 [line 17]\n APPLY_ABSTRACTION; [line 17]\n " shape="box"] + + + 38 -> 34 ; +37 [label="37: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___34); [line 17]\n *&SIL_temp_conditional___34:int =1 [line 17]\n APPLY_ABSTRACTION; [line 17]\n " shape="box"] + + + 37 -> 34 ; +36 [label="36: Prune (false branch) \n PRUNE((0 == 0), false); [line 17]\n " shape="invhouse"] + + + 36 -> 38 ; +35 [label="35: Prune (true branch) \n PRUNE((0 != 0), true); [line 17]\n " shape="invhouse"] + + + 35 -> 37 ; +34 [label="34: + \n " ] + + + 34 -> 40 ; + 34 -> 41 ; +33 [label="33: Exit fun_ifthenelse3 \n " color=yellow style=filled] + + +32 [label="32: Start fun_ifthenelse3\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 16]\n " color=yellow style=filled] + + + 32 -> 35 ; + 32 -> 36 ; +31 [label="31: Call n$1 \n n$1=*&SIL_temp_conditional___11:_fn_ (*) [line 13]\n NULLIFY(&SIL_temp_conditional___11,true); [line 13]\n n$2=*&SIL_temp_conditional___16:int [line 13]\n NULLIFY(&SIL_temp_conditional___16,true); [line 13]\n n$3=*&SIL_temp_conditional___21:int [line 13]\n NULLIFY(&SIL_temp_conditional___21,true); [line 13]\n n$4=*&SIL_temp_conditional___26:int [line 13]\n NULLIFY(&SIL_temp_conditional___26,true); [line 13]\n n$1(n$2:int ,n$3:int ,n$4:int ) [line 13]\n REMOVE_TEMPS(n$1,n$2,n$3,n$4); [line 13]\n APPLY_ABSTRACTION; [line 13]\n " shape="box"] + + + 31 -> 10 ; +30 [label="30: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___26); [line 13]\n *&SIL_temp_conditional___26:int =3 [line 13]\n APPLY_ABSTRACTION; [line 13]\n " shape="box"] + + + 30 -> 26 ; +29 [label="29: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___26); [line 13]\n *&SIL_temp_conditional___26:int =3 [line 13]\n APPLY_ABSTRACTION; [line 13]\n " shape="box"] + + + 29 -> 26 ; +28 [label="28: Prune (false branch) \n PRUNE((0 == 0), false); [line 13]\n " shape="invhouse"] + + + 28 -> 30 ; +27 [label="27: Prune (true branch) \n PRUNE((0 != 0), true); [line 13]\n " shape="invhouse"] + + + 27 -> 29 ; +26 [label="26: + \n " ] + + + 26 -> 31 ; +25 [label="25: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___21); [line 13]\n *&SIL_temp_conditional___21:int =2 [line 13]\n APPLY_ABSTRACTION; [line 13]\n " shape="box"] + + + 25 -> 21 ; +24 [label="24: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___21); [line 13]\n *&SIL_temp_conditional___21:int =2 [line 13]\n APPLY_ABSTRACTION; [line 13]\n " shape="box"] + + + 24 -> 21 ; +23 [label="23: Prune (false branch) \n PRUNE((0 == 0), false); [line 13]\n " shape="invhouse"] + + + 23 -> 25 ; +22 [label="22: Prune (true branch) \n PRUNE((0 != 0), true); [line 13]\n " shape="invhouse"] + + + 22 -> 24 ; +21 [label="21: + \n " ] + + + 21 -> 27 ; + 21 -> 28 ; +20 [label="20: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___16); [line 13]\n *&SIL_temp_conditional___16:int =1 [line 13]\n APPLY_ABSTRACTION; [line 13]\n " shape="box"] + + + 20 -> 16 ; +19 [label="19: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___16); [line 13]\n *&SIL_temp_conditional___16:int =1 [line 13]\n APPLY_ABSTRACTION; [line 13]\n " shape="box"] + + + 19 -> 16 ; +18 [label="18: Prune (false branch) \n PRUNE((0 == 0), false); [line 13]\n " shape="invhouse"] + + + 18 -> 20 ; +17 [label="17: Prune (true branch) \n PRUNE((0 != 0), true); [line 13]\n " shape="invhouse"] + + + 17 -> 19 ; +16 [label="16: + \n " ] + + + 16 -> 22 ; + 16 -> 23 ; +15 [label="15: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___11); [line 13]\n *&SIL_temp_conditional___11:_fn_ (*)=_fun_some_f [line 13]\n APPLY_ABSTRACTION; [line 13]\n " shape="box"] + + + 15 -> 11 ; +14 [label="14: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___11); [line 13]\n *&SIL_temp_conditional___11:_fn_ (*)=_fun_some_f [line 13]\n APPLY_ABSTRACTION; [line 13]\n " shape="box"] + + + 14 -> 11 ; +13 [label="13: Prune (false branch) \n PRUNE((1 == 0), false); [line 13]\n " shape="invhouse"] + + + 13 -> 15 ; +12 [label="12: Prune (true branch) \n PRUNE((1 != 0), true); [line 13]\n " shape="invhouse"] + + + 12 -> 14 ; +11 [label="11: + \n " ] + + + 11 -> 17 ; + 11 -> 18 ; +10 [label="10: Exit fun_ifthenelse2 \n " color=yellow style=filled] + + +9 [label="9: Start fun_ifthenelse2\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 12]\n " color=yellow style=filled] + + + 9 -> 12 ; + 9 -> 13 ; +8 [label="8: Call n$0 \n n$0=*&SIL_temp_conditional___3:_fn_ (*) [line 9]\n NULLIFY(&SIL_temp_conditional___3,true); [line 9]\n n$0(1:int ,2:int ,3:int ) [line 9]\n REMOVE_TEMPS(n$0); [line 9]\n APPLY_ABSTRACTION; [line 9]\n " shape="box"] + + + 8 -> 2 ; +7 [label="7: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___3); [line 9]\n *&SIL_temp_conditional___3:_fn_ (*)=_fun_some_f [line 9]\n APPLY_ABSTRACTION; [line 9]\n " shape="box"] + + + 7 -> 3 ; +6 [label="6: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___3); [line 9]\n *&SIL_temp_conditional___3:_fn_ (*)=_fun_some_f [line 9]\n APPLY_ABSTRACTION; [line 9]\n " shape="box"] + + + 6 -> 3 ; +5 [label="5: Prune (false branch) \n PRUNE((1 == 0), false); [line 9]\n " shape="invhouse"] + + + 5 -> 7 ; +4 [label="4: Prune (true branch) \n PRUNE((1 != 0), true); [line 9]\n " shape="invhouse"] + + + 4 -> 6 ; +3 [label="3: + \n " ] + + + 3 -> 8 ; +2 [label="2: Exit fun_ifthenelse1 \n " color=yellow style=filled] + + +1 [label="1: Start fun_ifthenelse1\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 8]\n " color=yellow style=filled] + + + 1 -> 4 ; + 1 -> 5 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/conditional_operator/if_short_circuit.c b/infer/tests/codetoanalyze/c/frontend/conditional_operator/if_short_circuit.c new file mode 100644 index 000000000..ff9c71dde --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/conditional_operator/if_short_circuit.c @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +#include + +void shortcircuit_or(int *x) { + //x = 0; + if (x == 0 || *x == 2){ x=17;} else {x=32; }; +} + +void shortcircuit_and(int *x) { + if (!x && !(x =getenv ("BLOCK"))) { + x=17; + } + else { + *x=32; + }; +} + + + +void test_loop () { + + char* spec; + char* block_size; + + spec= getenv ("BLOCK"); + +while((! spec + && ! (spec = getenv ("BLOCK_SIZE")) + && ! (spec = getenv ("BLOCKSIZE")))) { + block_size =0; + + } + + +} + + +int main() { + + char* spec; + char* block_size; + + spec= getenv ("BLOCK"); + + + + if (! spec + && ! (spec = getenv ("BLOCK_SIZE")) + && ! (spec = getenv ("BLOCKSIZE"))) + block_size = 0; + else + { + if (*spec == '\'') + block_size=0; } + + + return 0; +} diff --git a/infer/tests/codetoanalyze/c/frontend/conditional_operator/if_short_circuit.dot b/infer/tests/codetoanalyze/c/frontend/conditional_operator/if_short_circuit.dot new file mode 100644 index 000000000..c2650da71 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/conditional_operator/if_short_circuit.dot @@ -0,0 +1,389 @@ +digraph iCFG { +93 [label="93: BinaryOperatorStmt: Assign \n n$28=_fun_getenv(\"BLOCK\":char *) [line 47]\n *&spec:char *=n$28 [line 47]\n REMOVE_TEMPS(n$28); [line 47]\n " shape="box"] + + + 93 -> 65 ; + 93 -> 66 ; +92 [label="92: BinaryOperatorStmt: Assign \n *&block_size:char *=0 [line 58]\n NULLIFY(&block_size,false); [line 58]\n APPLY_ABSTRACTION; [line 58]\n " shape="box"] + + + 92 -> 88 ; +91 [label="91: Prune (false branch) \n PRUNE(((n$27 == 39) == 0), false); [line 57]\n REMOVE_TEMPS(n$26,n$27); [line 57]\n APPLY_ABSTRACTION; [line 57]\n " shape="invhouse"] + + + 91 -> 88 ; +90 [label="90: Prune (true branch) \n PRUNE(((n$27 == 39) != 0), true); [line 57]\n REMOVE_TEMPS(n$26,n$27); [line 57]\n " shape="invhouse"] + + + 90 -> 92 ; +89 [label="89: BinaryOperatorStmt: EQ \n n$26=*&spec:char * [line 57]\n n$27=*n$26:char [line 57]\n NULLIFY(&spec,false); [line 57]\n " shape="box"] + + + 89 -> 90 ; + 89 -> 91 ; +88 [label="88: + \n " ] + + + 88 -> 63 ; +87 [label="87: BinaryOperatorStmt: Assign \n *&block_size:char *=0 [line 54]\n NULLIFY(&block_size,false); [line 54]\n APPLY_ABSTRACTION; [line 54]\n " shape="box"] + + + 87 -> 63 ; +86 [label="86: Prune (false branch) \n n$25=*&SIL_temp_conditional___79:int [line 53]\n NULLIFY(&SIL_temp_conditional___79,true); [line 53]\n PRUNE((n$25 == 0), false); [line 53]\n REMOVE_TEMPS(n$25); [line 53]\n APPLY_ABSTRACTION; [line 53]\n " shape="invhouse"] + + + 86 -> 89 ; +85 [label="85: Prune (true branch) \n n$25=*&SIL_temp_conditional___79:int [line 53]\n NULLIFY(&SIL_temp_conditional___79,true); [line 53]\n PRUNE((n$25 != 0), true); [line 53]\n REMOVE_TEMPS(n$25); [line 53]\n NULLIFY(&spec,false); [line 53]\n " shape="invhouse"] + + + 85 -> 87 ; +84 [label="84: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___79); [line 53]\n *&SIL_temp_conditional___79:int =1 [line 53]\n APPLY_ABSTRACTION; [line 53]\n " shape="box"] + + + 84 -> 79 ; +83 [label="83: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___79); [line 53]\n *&SIL_temp_conditional___79:int =0 [line 53]\n APPLY_ABSTRACTION; [line 53]\n " shape="box"] + + + 83 -> 79 ; +82 [label="82: Prune (false branch) \n PRUNE((n$24 == 0), false); [line 53]\n REMOVE_TEMPS(n$23,n$24); [line 53]\n " shape="invhouse"] + + + 82 -> 84 ; +81 [label="81: Prune (true branch) \n PRUNE((n$24 != 0), true); [line 53]\n REMOVE_TEMPS(n$23,n$24); [line 53]\n " shape="invhouse"] + + + 81 -> 83 ; +80 [label="80: BinaryOperatorStmt: Assign \n n$23=_fun_getenv(\"BLOCKSIZE\":char *) [line 53]\n *&spec:char *=n$23 [line 53]\n n$24=*&spec:char * [line 53]\n " shape="box"] + + + 80 -> 81 ; + 80 -> 82 ; +79 [label="79: + \n " ] + + + 79 -> 85 ; + 79 -> 86 ; +78 [label="78: Prune (false branch) \n n$22=*&SIL_temp_conditional___71:int [line 52]\n NULLIFY(&SIL_temp_conditional___71,true); [line 52]\n PRUNE((n$22 == 0), false); [line 52]\n REMOVE_TEMPS(n$22); [line 52]\n APPLY_ABSTRACTION; [line 52]\n " shape="invhouse"] + + + 78 -> 89 ; +77 [label="77: Prune (true branch) \n n$22=*&SIL_temp_conditional___71:int [line 52]\n NULLIFY(&SIL_temp_conditional___71,true); [line 52]\n PRUNE((n$22 != 0), true); [line 52]\n REMOVE_TEMPS(n$22); [line 52]\n NULLIFY(&spec,false); [line 52]\n " shape="invhouse"] + + + 77 -> 80 ; +76 [label="76: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___71); [line 52]\n *&SIL_temp_conditional___71:int =1 [line 52]\n APPLY_ABSTRACTION; [line 52]\n " shape="box"] + + + 76 -> 71 ; +75 [label="75: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___71); [line 52]\n *&SIL_temp_conditional___71:int =0 [line 52]\n APPLY_ABSTRACTION; [line 52]\n " shape="box"] + + + 75 -> 71 ; +74 [label="74: Prune (false branch) \n PRUNE((n$21 == 0), false); [line 52]\n REMOVE_TEMPS(n$20,n$21); [line 52]\n " shape="invhouse"] + + + 74 -> 76 ; +73 [label="73: Prune (true branch) \n PRUNE((n$21 != 0), true); [line 52]\n REMOVE_TEMPS(n$20,n$21); [line 52]\n " shape="invhouse"] + + + 73 -> 75 ; +72 [label="72: BinaryOperatorStmt: Assign \n n$20=_fun_getenv(\"BLOCK_SIZE\":char *) [line 52]\n *&spec:char *=n$20 [line 52]\n n$21=*&spec:char * [line 52]\n " shape="box"] + + + 72 -> 73 ; + 72 -> 74 ; +71 [label="71: + \n " ] + + + 71 -> 77 ; + 71 -> 78 ; +70 [label="70: Prune (false branch) \n n$19=*&SIL_temp_conditional___64:int [line 51]\n NULLIFY(&SIL_temp_conditional___64,true); [line 51]\n PRUNE((n$19 == 0), false); [line 51]\n REMOVE_TEMPS(n$19); [line 51]\n APPLY_ABSTRACTION; [line 51]\n " shape="invhouse"] + + + 70 -> 89 ; +69 [label="69: Prune (true branch) \n n$19=*&SIL_temp_conditional___64:int [line 51]\n NULLIFY(&SIL_temp_conditional___64,true); [line 51]\n PRUNE((n$19 != 0), true); [line 51]\n REMOVE_TEMPS(n$19); [line 51]\n NULLIFY(&spec,false); [line 51]\n " shape="invhouse"] + + + 69 -> 72 ; +68 [label="68: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___64); [line 51]\n *&SIL_temp_conditional___64:int =1 [line 51]\n APPLY_ABSTRACTION; [line 51]\n " shape="box"] + + + 68 -> 64 ; +67 [label="67: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___64); [line 51]\n *&SIL_temp_conditional___64:int =0 [line 51]\n APPLY_ABSTRACTION; [line 51]\n " shape="box"] + + + 67 -> 64 ; +66 [label="66: Prune (false branch) \n n$18=*&spec:char * [line 51]\n PRUNE((n$18 == 0), false); [line 51]\n REMOVE_TEMPS(n$18); [line 51]\n " shape="invhouse"] + + + 66 -> 68 ; +65 [label="65: Prune (true branch) \n n$18=*&spec:char * [line 51]\n PRUNE((n$18 != 0), true); [line 51]\n REMOVE_TEMPS(n$18); [line 51]\n " shape="invhouse"] + + + 65 -> 67 ; +64 [label="64: + \n " ] + + + 64 -> 69 ; + 64 -> 70 ; +63 [label="63: + \n " ] + + + 63 -> 62 ; +62 [label="62: Return Stmt \n *&return:int =0 [line 61]\n APPLY_ABSTRACTION; [line 61]\n " shape="box"] + + + 62 -> 61 ; +61 [label="61: Exit main \n " color=yellow style=filled] + + +60 [label="60: Start main\nFormals: \nLocals: spec:char * block_size:char * \n DECLARE_LOCALS(&return,&spec,&block_size); [line 42]\n NULLIFY(&block_size,false); [line 42]\n NULLIFY(&spec,false); [line 42]\n " color=yellow style=filled] + + + 60 -> 93 ; +59 [label="59: BinaryOperatorStmt: Assign \n n$17=_fun_getenv(\"BLOCK\":char *) [line 29]\n *&spec:char *=n$17 [line 29]\n REMOVE_TEMPS(n$17); [line 29]\n APPLY_ABSTRACTION; [line 29]\n " shape="box"] + + + 59 -> 34 ; +58 [label="58: BinaryOperatorStmt: Assign \n *&block_size:char *=0 [line 34]\n NULLIFY(&block_size,false); [line 34]\n APPLY_ABSTRACTION; [line 34]\n " shape="box"] + + + 58 -> 34 ; +57 [label="57: Prune (false branch) \n n$16=*&SIL_temp_conditional___50:int [line 33]\n NULLIFY(&SIL_temp_conditional___50,true); [line 33]\n PRUNE((n$16 == 0), false); [line 33]\n REMOVE_TEMPS(n$16); [line 33]\n NULLIFY(&spec,false); [line 33]\n APPLY_ABSTRACTION; [line 33]\n " shape="invhouse"] + + + 57 -> 33 ; +56 [label="56: Prune (true branch) \n n$16=*&SIL_temp_conditional___50:int [line 33]\n NULLIFY(&SIL_temp_conditional___50,true); [line 33]\n PRUNE((n$16 != 0), true); [line 33]\n REMOVE_TEMPS(n$16); [line 33]\n " shape="invhouse"] + + + 56 -> 58 ; +55 [label="55: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___50); [line 33]\n *&SIL_temp_conditional___50:int =1 [line 33]\n APPLY_ABSTRACTION; [line 33]\n " shape="box"] + + + 55 -> 50 ; +54 [label="54: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___50); [line 33]\n *&SIL_temp_conditional___50:int =0 [line 33]\n APPLY_ABSTRACTION; [line 33]\n " shape="box"] + + + 54 -> 50 ; +53 [label="53: Prune (false branch) \n PRUNE((n$15 == 0), false); [line 33]\n REMOVE_TEMPS(n$14,n$15); [line 33]\n " shape="invhouse"] + + + 53 -> 55 ; +52 [label="52: Prune (true branch) \n PRUNE((n$15 != 0), true); [line 33]\n REMOVE_TEMPS(n$14,n$15); [line 33]\n " shape="invhouse"] + + + 52 -> 54 ; +51 [label="51: BinaryOperatorStmt: Assign \n n$14=_fun_getenv(\"BLOCKSIZE\":char *) [line 33]\n *&spec:char *=n$14 [line 33]\n n$15=*&spec:char * [line 33]\n " shape="box"] + + + 51 -> 52 ; + 51 -> 53 ; +50 [label="50: + \n " ] + + + 50 -> 56 ; + 50 -> 57 ; +49 [label="49: Prune (false branch) \n n$13=*&SIL_temp_conditional___42:int [line 32]\n NULLIFY(&SIL_temp_conditional___42,true); [line 32]\n PRUNE((n$13 == 0), false); [line 32]\n REMOVE_TEMPS(n$13); [line 32]\n APPLY_ABSTRACTION; [line 32]\n " shape="invhouse"] + + + 49 -> 33 ; +48 [label="48: Prune (true branch) \n n$13=*&SIL_temp_conditional___42:int [line 32]\n NULLIFY(&SIL_temp_conditional___42,true); [line 32]\n PRUNE((n$13 != 0), true); [line 32]\n REMOVE_TEMPS(n$13); [line 32]\n " shape="invhouse"] + + + 48 -> 51 ; +47 [label="47: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___42); [line 32]\n *&SIL_temp_conditional___42:int =1 [line 32]\n APPLY_ABSTRACTION; [line 32]\n " shape="box"] + + + 47 -> 42 ; +46 [label="46: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___42); [line 32]\n *&SIL_temp_conditional___42:int =0 [line 32]\n APPLY_ABSTRACTION; [line 32]\n " shape="box"] + + + 46 -> 42 ; +45 [label="45: Prune (false branch) \n PRUNE((n$12 == 0), false); [line 32]\n REMOVE_TEMPS(n$11,n$12); [line 32]\n " shape="invhouse"] + + + 45 -> 47 ; +44 [label="44: Prune (true branch) \n PRUNE((n$12 != 0), true); [line 32]\n REMOVE_TEMPS(n$11,n$12); [line 32]\n " shape="invhouse"] + + + 44 -> 46 ; +43 [label="43: BinaryOperatorStmt: Assign \n n$11=_fun_getenv(\"BLOCK_SIZE\":char *) [line 32]\n *&spec:char *=n$11 [line 32]\n n$12=*&spec:char * [line 32]\n NULLIFY(&spec,false); [line 32]\n " shape="box"] + + + 43 -> 44 ; + 43 -> 45 ; +42 [label="42: + \n " ] + + + 42 -> 48 ; + 42 -> 49 ; +41 [label="41: Prune (false branch) \n n$10=*&SIL_temp_conditional___35:int [line 31]\n NULLIFY(&SIL_temp_conditional___35,true); [line 31]\n PRUNE((n$10 == 0), false); [line 31]\n REMOVE_TEMPS(n$10); [line 31]\n APPLY_ABSTRACTION; [line 31]\n " shape="invhouse"] + + + 41 -> 33 ; +40 [label="40: Prune (true branch) \n n$10=*&SIL_temp_conditional___35:int [line 31]\n NULLIFY(&SIL_temp_conditional___35,true); [line 31]\n PRUNE((n$10 != 0), true); [line 31]\n REMOVE_TEMPS(n$10); [line 31]\n " shape="invhouse"] + + + 40 -> 43 ; +39 [label="39: ConditinalStmt Branch \n NULLIFY(&spec,false); [line 31]\n DECLARE_LOCALS(&SIL_temp_conditional___35); [line 31]\n *&SIL_temp_conditional___35:int =1 [line 31]\n APPLY_ABSTRACTION; [line 31]\n " shape="box"] + + + 39 -> 35 ; +38 [label="38: ConditinalStmt Branch \n NULLIFY(&spec,false); [line 31]\n DECLARE_LOCALS(&SIL_temp_conditional___35); [line 31]\n *&SIL_temp_conditional___35:int =0 [line 31]\n APPLY_ABSTRACTION; [line 31]\n " shape="box"] + + + 38 -> 35 ; +37 [label="37: Prune (false branch) \n n$9=*&spec:char * [line 31]\n PRUNE((n$9 == 0), false); [line 31]\n REMOVE_TEMPS(n$9); [line 31]\n " shape="invhouse"] + + + 37 -> 39 ; +36 [label="36: Prune (true branch) \n n$9=*&spec:char * [line 31]\n PRUNE((n$9 != 0), true); [line 31]\n REMOVE_TEMPS(n$9); [line 31]\n " shape="invhouse"] + + + 36 -> 38 ; +35 [label="35: + \n " ] + + + 35 -> 40 ; + 35 -> 41 ; +34 [label="34: + \n " ] + + + 34 -> 36 ; + 34 -> 37 ; +33 [label="33: Exit test_loop \n " color=yellow style=filled] + + +32 [label="32: Start test_loop\nFormals: \nLocals: spec:char * block_size:char * \n DECLARE_LOCALS(&return,&spec,&block_size); [line 24]\n NULLIFY(&block_size,false); [line 24]\n NULLIFY(&spec,false); [line 24]\n " color=yellow style=filled] + + + 32 -> 59 ; +31 [label="31: BinaryOperatorStmt: Assign \n n$8=*&x:int * [line 18]\n *n$8:int =32 [line 18]\n REMOVE_TEMPS(n$8); [line 18]\n NULLIFY(&x,false); [line 18]\n APPLY_ABSTRACTION; [line 18]\n " shape="box"] + + + 31 -> 14 ; +30 [label="30: BinaryOperatorStmt: Assign \n *&x:int *=17 [line 15]\n NULLIFY(&x,false); [line 15]\n APPLY_ABSTRACTION; [line 15]\n " shape="box"] + + + 30 -> 14 ; +29 [label="29: Prune (false branch) \n n$7=*&SIL_temp_conditional___22:int [line 14]\n NULLIFY(&SIL_temp_conditional___22,true); [line 14]\n PRUNE((n$7 == 0), false); [line 14]\n REMOVE_TEMPS(n$7); [line 14]\n APPLY_ABSTRACTION; [line 14]\n " shape="invhouse"] + + + 29 -> 31 ; +28 [label="28: Prune (true branch) \n n$7=*&SIL_temp_conditional___22:int [line 14]\n NULLIFY(&SIL_temp_conditional___22,true); [line 14]\n PRUNE((n$7 != 0), true); [line 14]\n REMOVE_TEMPS(n$7); [line 14]\n NULLIFY(&x,false); [line 14]\n " shape="invhouse"] + + + 28 -> 30 ; +27 [label="27: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___22); [line 14]\n *&SIL_temp_conditional___22:int =1 [line 14]\n APPLY_ABSTRACTION; [line 14]\n " shape="box"] + + + 27 -> 22 ; +26 [label="26: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___22); [line 14]\n *&SIL_temp_conditional___22:int =0 [line 14]\n APPLY_ABSTRACTION; [line 14]\n " shape="box"] + + + 26 -> 22 ; +25 [label="25: Prune (false branch) \n PRUNE((n$6 == 0), false); [line 14]\n REMOVE_TEMPS(n$5,n$6); [line 14]\n " shape="invhouse"] + + + 25 -> 27 ; +24 [label="24: Prune (true branch) \n PRUNE((n$6 != 0), true); [line 14]\n REMOVE_TEMPS(n$5,n$6); [line 14]\n " shape="invhouse"] + + + 24 -> 26 ; +23 [label="23: BinaryOperatorStmt: Assign \n n$5=_fun_getenv(\"BLOCK\":char *) [line 14]\n *&x:int *=n$5 [line 14]\n n$6=*&x:int * [line 14]\n " shape="box"] + + + 23 -> 24 ; + 23 -> 25 ; +22 [label="22: + \n " ] + + + 22 -> 28 ; + 22 -> 29 ; +21 [label="21: Prune (false branch) \n n$4=*&SIL_temp_conditional___15:int [line 14]\n NULLIFY(&SIL_temp_conditional___15,true); [line 14]\n PRUNE((n$4 == 0), false); [line 14]\n REMOVE_TEMPS(n$4); [line 14]\n APPLY_ABSTRACTION; [line 14]\n " shape="invhouse"] + + + 21 -> 31 ; +20 [label="20: Prune (true branch) \n n$4=*&SIL_temp_conditional___15:int [line 14]\n NULLIFY(&SIL_temp_conditional___15,true); [line 14]\n PRUNE((n$4 != 0), true); [line 14]\n REMOVE_TEMPS(n$4); [line 14]\n NULLIFY(&x,false); [line 14]\n " shape="invhouse"] + + + 20 -> 23 ; +19 [label="19: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___15); [line 14]\n *&SIL_temp_conditional___15:int =1 [line 14]\n APPLY_ABSTRACTION; [line 14]\n " shape="box"] + + + 19 -> 15 ; +18 [label="18: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___15); [line 14]\n *&SIL_temp_conditional___15:int =0 [line 14]\n APPLY_ABSTRACTION; [line 14]\n " shape="box"] + + + 18 -> 15 ; +17 [label="17: Prune (false branch) \n n$3=*&x:int * [line 14]\n PRUNE((n$3 == 0), false); [line 14]\n REMOVE_TEMPS(n$3); [line 14]\n " shape="invhouse"] + + + 17 -> 19 ; +16 [label="16: Prune (true branch) \n n$3=*&x:int * [line 14]\n PRUNE((n$3 != 0), true); [line 14]\n REMOVE_TEMPS(n$3); [line 14]\n " shape="invhouse"] + + + 16 -> 18 ; +15 [label="15: + \n " ] + + + 15 -> 20 ; + 15 -> 21 ; +14 [label="14: + \n " ] + + + 14 -> 13 ; +13 [label="13: Exit shortcircuit_and \n " color=yellow style=filled] + + +12 [label="12: Start shortcircuit_and\nFormals: x:int *\nLocals: \n DECLARE_LOCALS(&return); [line 13]\n " color=yellow style=filled] + + + 12 -> 16 ; + 12 -> 17 ; +11 [label="11: BinaryOperatorStmt: Assign \n *&x:int *=32 [line 10]\n NULLIFY(&x,false); [line 10]\n APPLY_ABSTRACTION; [line 10]\n " shape="box"] + + + 11 -> 3 ; +10 [label="10: BinaryOperatorStmt: Assign \n NULLIFY(&x,false); [line 10]\n *&x:int *=17 [line 10]\n NULLIFY(&x,false); [line 10]\n APPLY_ABSTRACTION; [line 10]\n " shape="box"] + + + 10 -> 3 ; +9 [label="9: Prune (false branch) \n PRUNE(((n$2 == 2) == 0), false); [line 10]\n REMOVE_TEMPS(n$1,n$2); [line 10]\n " shape="invhouse"] + + + 9 -> 11 ; +8 [label="8: Prune (true branch) \n PRUNE(((n$2 == 2) != 0), true); [line 10]\n REMOVE_TEMPS(n$1,n$2); [line 10]\n APPLY_ABSTRACTION; [line 10]\n " shape="invhouse"] + + + 8 -> 10 ; +7 [label="7: BinaryOperatorStmt: EQ \n n$1=*&x:int * [line 10]\n n$2=*n$1:int [line 10]\n NULLIFY(&x,false); [line 10]\n " shape="box"] + + + 7 -> 8 ; + 7 -> 9 ; +6 [label="6: Prune (false branch) \n PRUNE(((n$0 == 0) == 0), false); [line 10]\n REMOVE_TEMPS(n$0); [line 10]\n " shape="invhouse"] + + + 6 -> 7 ; +5 [label="5: Prune (true branch) \n PRUNE(((n$0 == 0) != 0), true); [line 10]\n REMOVE_TEMPS(n$0); [line 10]\n APPLY_ABSTRACTION; [line 10]\n " shape="invhouse"] + + + 5 -> 10 ; +4 [label="4: BinaryOperatorStmt: EQ \n n$0=*&x:int * [line 10]\n " shape="box"] + + + 4 -> 5 ; + 4 -> 6 ; +3 [label="3: + \n " ] + + + 3 -> 2 ; +2 [label="2: Exit shortcircuit_or \n " color=yellow style=filled] + + +1 [label="1: Start shortcircuit_or\nFormals: x:int *\nLocals: \n DECLARE_LOCALS(&return); [line 8]\n " color=yellow style=filled] + + + 1 -> 4 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/conditional_operator/int_negation.c b/infer/tests/codetoanalyze/c/frontend/conditional_operator/int_negation.c new file mode 100644 index 000000000..35b08ec00 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/conditional_operator/int_negation.c @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +int identity(int x) { + return x; +} + + +int bar(int x) { + if (identity(x)) { + return 1; + } else { + return 0; + } +} + +int baz(int x) { + + if (identity(!x)) { + return 1; + } else { + return 0; + } +} + +int neg(int x) { + return !x; +} diff --git a/infer/tests/codetoanalyze/c/frontend/conditional_operator/int_negation.dot b/infer/tests/codetoanalyze/c/frontend/conditional_operator/int_negation.dot new file mode 100644 index 000000000..496c8ef54 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/conditional_operator/int_negation.dot @@ -0,0 +1,130 @@ +digraph iCFG { +32 [label="32: ConditinalStmt Branch \n NULLIFY(&x,false); [line 29]\n DECLARE_LOCALS(&SIL_temp_conditional___28); [line 29]\n *&SIL_temp_conditional___28:int =1 [line 29]\n APPLY_ABSTRACTION; [line 29]\n " shape="box"] + + + 32 -> 28 ; +31 [label="31: ConditinalStmt Branch \n NULLIFY(&x,false); [line 29]\n DECLARE_LOCALS(&SIL_temp_conditional___28); [line 29]\n *&SIL_temp_conditional___28:int =0 [line 29]\n APPLY_ABSTRACTION; [line 29]\n " shape="box"] + + + 31 -> 28 ; +30 [label="30: Prune (false branch) \n n$6=*&x:int [line 29]\n PRUNE((n$6 == 0), false); [line 29]\n REMOVE_TEMPS(n$6); [line 29]\n " shape="invhouse"] + + + 30 -> 32 ; +29 [label="29: Prune (true branch) \n n$6=*&x:int [line 29]\n PRUNE((n$6 != 0), true); [line 29]\n REMOVE_TEMPS(n$6); [line 29]\n " shape="invhouse"] + + + 29 -> 31 ; +28 [label="28: + \n " ] + + + 28 -> 27 ; +27 [label="27: Return Stmt \n n$7=*&SIL_temp_conditional___28:int [line 29]\n NULLIFY(&SIL_temp_conditional___28,true); [line 29]\n *&return:int =n$7 [line 29]\n REMOVE_TEMPS(n$7); [line 29]\n APPLY_ABSTRACTION; [line 29]\n " shape="box"] + + + 27 -> 26 ; +26 [label="26: Exit neg \n " color=yellow style=filled] + + +25 [label="25: Start neg\nFormals: x:int \nLocals: \n DECLARE_LOCALS(&return); [line 28]\n " color=yellow style=filled] + + + 25 -> 29 ; + 25 -> 30 ; +24 [label="24: Return Stmt \n *&return:int =0 [line 24]\n APPLY_ABSTRACTION; [line 24]\n " shape="box"] + + + 24 -> 13 ; +23 [label="23: Return Stmt \n *&return:int =1 [line 22]\n APPLY_ABSTRACTION; [line 22]\n " shape="box"] + + + 23 -> 13 ; +22 [label="22: Prune (false branch) \n PRUNE((n$5 == 0), false); [line 21]\n REMOVE_TEMPS(n$4,n$5); [line 21]\n " shape="invhouse"] + + + 22 -> 24 ; +21 [label="21: Prune (true branch) \n PRUNE((n$5 != 0), true); [line 21]\n REMOVE_TEMPS(n$4,n$5); [line 21]\n " shape="invhouse"] + + + 21 -> 23 ; +20 [label="20: Call _fun_identity \n n$4=*&SIL_temp_conditional___15:int [line 21]\n NULLIFY(&SIL_temp_conditional___15,true); [line 21]\n n$5=_fun_identity(n$4:int ) [line 21]\n " shape="box"] + + + 20 -> 21 ; + 20 -> 22 ; +19 [label="19: ConditinalStmt Branch \n NULLIFY(&x,false); [line 21]\n DECLARE_LOCALS(&SIL_temp_conditional___15); [line 21]\n *&SIL_temp_conditional___15:int =1 [line 21]\n APPLY_ABSTRACTION; [line 21]\n " shape="box"] + + + 19 -> 15 ; +18 [label="18: ConditinalStmt Branch \n NULLIFY(&x,false); [line 21]\n DECLARE_LOCALS(&SIL_temp_conditional___15); [line 21]\n *&SIL_temp_conditional___15:int =0 [line 21]\n APPLY_ABSTRACTION; [line 21]\n " shape="box"] + + + 18 -> 15 ; +17 [label="17: Prune (false branch) \n n$3=*&x:int [line 21]\n PRUNE((n$3 == 0), false); [line 21]\n REMOVE_TEMPS(n$3); [line 21]\n " shape="invhouse"] + + + 17 -> 19 ; +16 [label="16: Prune (true branch) \n n$3=*&x:int [line 21]\n PRUNE((n$3 != 0), true); [line 21]\n REMOVE_TEMPS(n$3); [line 21]\n " shape="invhouse"] + + + 16 -> 18 ; +15 [label="15: + \n " ] + + + 15 -> 20 ; +14 [label="14: + \n NULLIFY(&x,false); [line 21]\n " ] + + + 14 -> 13 ; +13 [label="13: Exit baz \n " color=yellow style=filled] + + +12 [label="12: Start baz\nFormals: x:int \nLocals: \n DECLARE_LOCALS(&return); [line 19]\n " color=yellow style=filled] + + + 12 -> 16 ; + 12 -> 17 ; +11 [label="11: Return Stmt \n *&return:int =0 [line 15]\n APPLY_ABSTRACTION; [line 15]\n " shape="box"] + + + 11 -> 5 ; +10 [label="10: Return Stmt \n *&return:int =1 [line 13]\n APPLY_ABSTRACTION; [line 13]\n " shape="box"] + + + 10 -> 5 ; +9 [label="9: Prune (false branch) \n PRUNE((n$2 == 0), false); [line 12]\n REMOVE_TEMPS(n$1,n$2); [line 12]\n " shape="invhouse"] + + + 9 -> 11 ; +8 [label="8: Prune (true branch) \n PRUNE((n$2 != 0), true); [line 12]\n REMOVE_TEMPS(n$1,n$2); [line 12]\n " shape="invhouse"] + + + 8 -> 10 ; +7 [label="7: Call _fun_identity \n n$1=*&x:int [line 12]\n n$2=_fun_identity(n$1:int ) [line 12]\n NULLIFY(&x,false); [line 12]\n " shape="box"] + + + 7 -> 8 ; + 7 -> 9 ; +6 [label="6: + \n NULLIFY(&x,false); [line 12]\n " ] + + + 6 -> 5 ; +5 [label="5: Exit bar \n " color=yellow style=filled] + + +4 [label="4: Start bar\nFormals: x:int \nLocals: \n DECLARE_LOCALS(&return); [line 11]\n " color=yellow style=filled] + + + 4 -> 7 ; +3 [label="3: Return Stmt \n n$0=*&x:int [line 7]\n *&return:int =n$0 [line 7]\n REMOVE_TEMPS(n$0); [line 7]\n NULLIFY(&x,false); [line 7]\n APPLY_ABSTRACTION; [line 7]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit identity \n " color=yellow style=filled] + + +1 [label="1: Start identity\nFormals: x:int \nLocals: \n DECLARE_LOCALS(&return); [line 6]\n " color=yellow style=filled] + + + 1 -> 3 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/conditional_operator/member_access.c b/infer/tests/codetoanalyze/c/frontend/conditional_operator/member_access.c new file mode 100644 index 000000000..d28d81957 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/conditional_operator/member_access.c @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +struct s { + int field; +}; + +struct s *ret_ptr(int); + +void ife_then_access_field(struct s *p, struct s *q) { + int z = (1 ? p : q)->field; +} + +void call_ife_then_access_field() { + int z = (ret_ptr(1 ? 2 : 3))->field; +} + +void access_field_in_ife_branch() { + int z = 1 ? (ret_ptr(4))->field : 0; +} diff --git a/infer/tests/codetoanalyze/c/frontend/conditional_operator/member_access.dot b/infer/tests/codetoanalyze/c/frontend/conditional_operator/member_access.dot new file mode 100644 index 000000000..879bb9b85 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/conditional_operator/member_access.dot @@ -0,0 +1,98 @@ +digraph iCFG { +24 [label="24: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___20); [line 21]\n *&SIL_temp_conditional___20:int =0 [line 21]\n APPLY_ABSTRACTION; [line 21]\n " shape="box"] + + + 24 -> 20 ; +23 [label="23: Call _fun_ret_ptr \n n$7=_fun_ret_ptr(4:int ) [line 21]\n n$8=*n$7.field:int [line 21]\n DECLARE_LOCALS(&SIL_temp_conditional___20); [line 21]\n *&SIL_temp_conditional___20:int =n$8 [line 21]\n REMOVE_TEMPS(n$7,n$8); [line 21]\n APPLY_ABSTRACTION; [line 21]\n " shape="box"] + + + 23 -> 20 ; +22 [label="22: Prune (false branch) \n PRUNE((1 == 0), false); [line 21]\n " shape="invhouse"] + + + 22 -> 24 ; +21 [label="21: Prune (true branch) \n PRUNE((1 != 0), true); [line 21]\n " shape="invhouse"] + + + 21 -> 23 ; +20 [label="20: + \n " ] + + + 20 -> 19 ; +19 [label="19: DeclStmt \n n$9=*&SIL_temp_conditional___20:int [line 21]\n NULLIFY(&SIL_temp_conditional___20,true); [line 21]\n *&z:int =n$9 [line 21]\n REMOVE_TEMPS(n$9); [line 21]\n NULLIFY(&z,false); [line 21]\n APPLY_ABSTRACTION; [line 21]\n " shape="box"] + + + 19 -> 18 ; +18 [label="18: Exit access_field_in_ife_branch \n " color=yellow style=filled] + + +17 [label="17: Start access_field_in_ife_branch\nFormals: \nLocals: z:int \n DECLARE_LOCALS(&return,&z); [line 20]\n NULLIFY(&z,false); [line 20]\n " color=yellow style=filled] + + + 17 -> 21 ; + 17 -> 22 ; +16 [label="16: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___12); [line 17]\n *&SIL_temp_conditional___12:int =3 [line 17]\n APPLY_ABSTRACTION; [line 17]\n " shape="box"] + + + 16 -> 12 ; +15 [label="15: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___12); [line 17]\n *&SIL_temp_conditional___12:int =2 [line 17]\n APPLY_ABSTRACTION; [line 17]\n " shape="box"] + + + 15 -> 12 ; +14 [label="14: Prune (false branch) \n PRUNE((1 == 0), false); [line 17]\n " shape="invhouse"] + + + 14 -> 16 ; +13 [label="13: Prune (true branch) \n PRUNE((1 != 0), true); [line 17]\n " shape="invhouse"] + + + 13 -> 15 ; +12 [label="12: + \n " ] + + + 12 -> 11 ; +11 [label="11: DeclStmt \n n$4=*&SIL_temp_conditional___12:int [line 17]\n NULLIFY(&SIL_temp_conditional___12,true); [line 17]\n n$5=_fun_ret_ptr(n$4:int ) [line 17]\n n$6=*n$5.field:int [line 17]\n *&z:int =n$6 [line 17]\n REMOVE_TEMPS(n$4,n$5,n$6); [line 17]\n NULLIFY(&z,false); [line 17]\n APPLY_ABSTRACTION; [line 17]\n " shape="box"] + + + 11 -> 10 ; +10 [label="10: Exit call_ife_then_access_field \n " color=yellow style=filled] + + +9 [label="9: Start call_ife_then_access_field\nFormals: \nLocals: z:int \n DECLARE_LOCALS(&return,&z); [line 16]\n NULLIFY(&z,false); [line 16]\n " color=yellow style=filled] + + + 9 -> 13 ; + 9 -> 14 ; +8 [label="8: ConditinalStmt Branch \n NULLIFY(&p,false); [line 13]\n n$1=*&q:struct s * [line 13]\n DECLARE_LOCALS(&SIL_temp_conditional___4); [line 13]\n *&SIL_temp_conditional___4:struct s *=n$1 [line 13]\n REMOVE_TEMPS(n$1); [line 13]\n NULLIFY(&q,false); [line 13]\n APPLY_ABSTRACTION; [line 13]\n " shape="box"] + + + 8 -> 4 ; +7 [label="7: ConditinalStmt Branch \n NULLIFY(&q,false); [line 13]\n n$0=*&p:struct s * [line 13]\n DECLARE_LOCALS(&SIL_temp_conditional___4); [line 13]\n *&SIL_temp_conditional___4:struct s *=n$0 [line 13]\n REMOVE_TEMPS(n$0); [line 13]\n NULLIFY(&p,false); [line 13]\n APPLY_ABSTRACTION; [line 13]\n " shape="box"] + + + 7 -> 4 ; +6 [label="6: Prune (false branch) \n PRUNE((1 == 0), false); [line 13]\n " shape="invhouse"] + + + 6 -> 8 ; +5 [label="5: Prune (true branch) \n PRUNE((1 != 0), true); [line 13]\n " shape="invhouse"] + + + 5 -> 7 ; +4 [label="4: + \n " ] + + + 4 -> 3 ; +3 [label="3: DeclStmt \n n$2=*&SIL_temp_conditional___4:struct s * [line 13]\n NULLIFY(&SIL_temp_conditional___4,true); [line 13]\n n$3=*n$2.field:int [line 13]\n *&z:int =n$3 [line 13]\n REMOVE_TEMPS(n$2,n$3); [line 13]\n NULLIFY(&z,false); [line 13]\n APPLY_ABSTRACTION; [line 13]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit ife_then_access_field \n " color=yellow style=filled] + + +1 [label="1: Start ife_then_access_field\nFormals: p:struct s * q:struct s *\nLocals: z:int \n DECLARE_LOCALS(&return,&z); [line 12]\n NULLIFY(&z,false); [line 12]\n " color=yellow style=filled] + + + 1 -> 5 ; + 1 -> 6 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/conditional_operator/preincrement.c b/infer/tests/codetoanalyze/c/frontend/conditional_operator/preincrement.c new file mode 100644 index 000000000..1b0e36716 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/conditional_operator/preincrement.c @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +struct s { + int x; +}; + +void preincrement(struct s *p) { + p->x += 1; + (1 ? p : p)->x += 1; + p->x += 1 ? 3 : 7; + (1 ? p : p)->x += 1 ? 3 : 7; +} diff --git a/infer/tests/codetoanalyze/c/frontend/conditional_operator/preincrement.dot b/infer/tests/codetoanalyze/c/frontend/conditional_operator/preincrement.dot new file mode 100644 index 000000000..27e035229 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/conditional_operator/preincrement.dot @@ -0,0 +1,109 @@ +digraph iCFG { +26 [label="26: ComppoundAssignStmt \n n$12=*&p:struct s * [line 11]\n n$13=*n$12.x:int [line 11]\n *n$12.x:int =(n$13 + 1) [line 11]\n REMOVE_TEMPS(n$12,n$13); [line 11]\n " shape="box"] + + + 26 -> 21 ; + 26 -> 22 ; +25 [label="25: ComppoundAssignStmt \n n$10=*&SIL_temp_conditional___20:struct s * [line 12]\n NULLIFY(&SIL_temp_conditional___20,true); [line 12]\n n$11=*n$10.x:int [line 12]\n *n$10.x:int =(n$11 + 1) [line 12]\n REMOVE_TEMPS(n$10,n$11); [line 12]\n " shape="box"] + + + 25 -> 15 ; + 25 -> 16 ; +24 [label="24: ConditinalStmt Branch \n n$9=*&p:struct s * [line 12]\n DECLARE_LOCALS(&SIL_temp_conditional___20); [line 12]\n *&SIL_temp_conditional___20:struct s *=n$9 [line 12]\n REMOVE_TEMPS(n$9); [line 12]\n APPLY_ABSTRACTION; [line 12]\n " shape="box"] + + + 24 -> 20 ; +23 [label="23: ConditinalStmt Branch \n n$8=*&p:struct s * [line 12]\n DECLARE_LOCALS(&SIL_temp_conditional___20); [line 12]\n *&SIL_temp_conditional___20:struct s *=n$8 [line 12]\n REMOVE_TEMPS(n$8); [line 12]\n APPLY_ABSTRACTION; [line 12]\n " shape="box"] + + + 23 -> 20 ; +22 [label="22: Prune (false branch) \n PRUNE((1 == 0), false); [line 12]\n " shape="invhouse"] + + + 22 -> 24 ; +21 [label="21: Prune (true branch) \n PRUNE((1 != 0), true); [line 12]\n " shape="invhouse"] + + + 21 -> 23 ; +20 [label="20: + \n " ] + + + 20 -> 25 ; +19 [label="19: ComppoundAssignStmt \n n$5=*&p:struct s * [line 13]\n n$6=*&SIL_temp_conditional___14:int [line 13]\n NULLIFY(&SIL_temp_conditional___14,true); [line 13]\n n$7=*n$5.x:int [line 13]\n *n$5.x:int =(n$7 + n$6) [line 13]\n REMOVE_TEMPS(n$5,n$6,n$7); [line 13]\n " shape="box"] + + + 19 -> 4 ; + 19 -> 5 ; +18 [label="18: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___14); [line 13]\n *&SIL_temp_conditional___14:int =7 [line 13]\n APPLY_ABSTRACTION; [line 13]\n " shape="box"] + + + 18 -> 14 ; +17 [label="17: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___14); [line 13]\n *&SIL_temp_conditional___14:int =3 [line 13]\n APPLY_ABSTRACTION; [line 13]\n " shape="box"] + + + 17 -> 14 ; +16 [label="16: Prune (false branch) \n PRUNE((1 == 0), false); [line 13]\n " shape="invhouse"] + + + 16 -> 18 ; +15 [label="15: Prune (true branch) \n PRUNE((1 != 0), true); [line 13]\n " shape="invhouse"] + + + 15 -> 17 ; +14 [label="14: + \n " ] + + + 14 -> 19 ; +13 [label="13: ComppoundAssignStmt \n n$2=*&SIL_temp_conditional___3:struct s * [line 14]\n NULLIFY(&SIL_temp_conditional___3,true); [line 14]\n n$3=*&SIL_temp_conditional___8:int [line 14]\n NULLIFY(&SIL_temp_conditional___8,true); [line 14]\n n$4=*n$2.x:int [line 14]\n *n$2.x:int =(n$4 + n$3) [line 14]\n REMOVE_TEMPS(n$2,n$3,n$4); [line 14]\n APPLY_ABSTRACTION; [line 14]\n " shape="box"] + + + 13 -> 2 ; +12 [label="12: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___8); [line 14]\n *&SIL_temp_conditional___8:int =7 [line 14]\n APPLY_ABSTRACTION; [line 14]\n " shape="box"] + + + 12 -> 8 ; +11 [label="11: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___8); [line 14]\n *&SIL_temp_conditional___8:int =3 [line 14]\n APPLY_ABSTRACTION; [line 14]\n " shape="box"] + + + 11 -> 8 ; +10 [label="10: Prune (false branch) \n PRUNE((1 == 0), false); [line 14]\n " shape="invhouse"] + + + 10 -> 12 ; +9 [label="9: Prune (true branch) \n PRUNE((1 != 0), true); [line 14]\n " shape="invhouse"] + + + 9 -> 11 ; +8 [label="8: + \n " ] + + + 8 -> 13 ; +7 [label="7: ConditinalStmt Branch \n n$1=*&p:struct s * [line 14]\n DECLARE_LOCALS(&SIL_temp_conditional___3); [line 14]\n *&SIL_temp_conditional___3:struct s *=n$1 [line 14]\n REMOVE_TEMPS(n$1); [line 14]\n NULLIFY(&p,false); [line 14]\n APPLY_ABSTRACTION; [line 14]\n " shape="box"] + + + 7 -> 3 ; +6 [label="6: ConditinalStmt Branch \n n$0=*&p:struct s * [line 14]\n DECLARE_LOCALS(&SIL_temp_conditional___3); [line 14]\n *&SIL_temp_conditional___3:struct s *=n$0 [line 14]\n REMOVE_TEMPS(n$0); [line 14]\n NULLIFY(&p,false); [line 14]\n APPLY_ABSTRACTION; [line 14]\n " shape="box"] + + + 6 -> 3 ; +5 [label="5: Prune (false branch) \n PRUNE((1 == 0), false); [line 14]\n " shape="invhouse"] + + + 5 -> 7 ; +4 [label="4: Prune (true branch) \n PRUNE((1 != 0), true); [line 14]\n " shape="invhouse"] + + + 4 -> 6 ; +3 [label="3: + \n " ] + + + 3 -> 9 ; + 3 -> 10 ; +2 [label="2: Exit preincrement \n " color=yellow style=filled] + + +1 [label="1: Start preincrement\nFormals: p:struct s *\nLocals: \n DECLARE_LOCALS(&return); [line 10]\n " color=yellow style=filled] + + + 1 -> 26 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/conditional_operator/unary_operator.c b/infer/tests/codetoanalyze/c/frontend/conditional_operator/unary_operator.c new file mode 100644 index 000000000..ec6ffa27c --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/conditional_operator/unary_operator.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +void dereference_ifthenelse(int *p) { + int x; + x = * (1 ? p : p); + + int y = * (1 ? p : p); + + * (1 ? p : p); +} diff --git a/infer/tests/codetoanalyze/c/frontend/conditional_operator/unary_operator.dot b/infer/tests/codetoanalyze/c/frontend/conditional_operator/unary_operator.dot new file mode 100644 index 000000000..642179dc6 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/conditional_operator/unary_operator.dot @@ -0,0 +1,84 @@ +digraph iCFG { +20 [label="20: BinaryOperatorStmt: Assign \n n$10=*&SIL_temp_conditional___15:int * [line 8]\n NULLIFY(&SIL_temp_conditional___15,true); [line 8]\n n$11=*n$10:int [line 8]\n *&x:int =n$11 [line 8]\n REMOVE_TEMPS(n$10,n$11); [line 8]\n NULLIFY(&x,false); [line 8]\n " shape="box"] + + + 20 -> 11 ; + 20 -> 12 ; +19 [label="19: ConditinalStmt Branch \n n$9=*&p:int * [line 8]\n DECLARE_LOCALS(&SIL_temp_conditional___15); [line 8]\n *&SIL_temp_conditional___15:int *=n$9 [line 8]\n REMOVE_TEMPS(n$9); [line 8]\n APPLY_ABSTRACTION; [line 8]\n " shape="box"] + + + 19 -> 15 ; +18 [label="18: ConditinalStmt Branch \n n$8=*&p:int * [line 8]\n DECLARE_LOCALS(&SIL_temp_conditional___15); [line 8]\n *&SIL_temp_conditional___15:int *=n$8 [line 8]\n REMOVE_TEMPS(n$8); [line 8]\n APPLY_ABSTRACTION; [line 8]\n " shape="box"] + + + 18 -> 15 ; +17 [label="17: Prune (false branch) \n PRUNE((1 == 0), false); [line 8]\n " shape="invhouse"] + + + 17 -> 19 ; +16 [label="16: Prune (true branch) \n PRUNE((1 != 0), true); [line 8]\n " shape="invhouse"] + + + 16 -> 18 ; +15 [label="15: + \n " ] + + + 15 -> 20 ; +14 [label="14: ConditinalStmt Branch \n n$5=*&p:int * [line 10]\n DECLARE_LOCALS(&SIL_temp_conditional___10); [line 10]\n *&SIL_temp_conditional___10:int *=n$5 [line 10]\n REMOVE_TEMPS(n$5); [line 10]\n APPLY_ABSTRACTION; [line 10]\n " shape="box"] + + + 14 -> 10 ; +13 [label="13: ConditinalStmt Branch \n n$4=*&p:int * [line 10]\n DECLARE_LOCALS(&SIL_temp_conditional___10); [line 10]\n *&SIL_temp_conditional___10:int *=n$4 [line 10]\n REMOVE_TEMPS(n$4); [line 10]\n APPLY_ABSTRACTION; [line 10]\n " shape="box"] + + + 13 -> 10 ; +12 [label="12: Prune (false branch) \n PRUNE((1 == 0), false); [line 10]\n " shape="invhouse"] + + + 12 -> 14 ; +11 [label="11: Prune (true branch) \n PRUNE((1 != 0), true); [line 10]\n " shape="invhouse"] + + + 11 -> 13 ; +10 [label="10: + \n " ] + + + 10 -> 9 ; +9 [label="9: DeclStmt \n n$6=*&SIL_temp_conditional___10:int * [line 10]\n NULLIFY(&SIL_temp_conditional___10,true); [line 10]\n n$7=*n$6:int [line 10]\n *&y:int =n$7 [line 10]\n REMOVE_TEMPS(n$6,n$7); [line 10]\n NULLIFY(&y,false); [line 10]\n " shape="box"] + + + 9 -> 4 ; + 9 -> 5 ; +8 [label="8: UnaryOperator \n n$2=*&SIL_temp_conditional___3:int * [line 12]\n NULLIFY(&SIL_temp_conditional___3,true); [line 12]\n REMOVE_TEMPS(n$2); [line 12]\n APPLY_ABSTRACTION; [line 12]\n " shape="box"] + + + 8 -> 2 ; +7 [label="7: ConditinalStmt Branch \n n$1=*&p:int * [line 12]\n DECLARE_LOCALS(&SIL_temp_conditional___3); [line 12]\n *&SIL_temp_conditional___3:int *=n$1 [line 12]\n REMOVE_TEMPS(n$1); [line 12]\n NULLIFY(&p,false); [line 12]\n APPLY_ABSTRACTION; [line 12]\n " shape="box"] + + + 7 -> 3 ; +6 [label="6: ConditinalStmt Branch \n n$0=*&p:int * [line 12]\n DECLARE_LOCALS(&SIL_temp_conditional___3); [line 12]\n *&SIL_temp_conditional___3:int *=n$0 [line 12]\n REMOVE_TEMPS(n$0); [line 12]\n NULLIFY(&p,false); [line 12]\n APPLY_ABSTRACTION; [line 12]\n " shape="box"] + + + 6 -> 3 ; +5 [label="5: Prune (false branch) \n PRUNE((1 == 0), false); [line 12]\n " shape="invhouse"] + + + 5 -> 7 ; +4 [label="4: Prune (true branch) \n PRUNE((1 != 0), true); [line 12]\n " shape="invhouse"] + + + 4 -> 6 ; +3 [label="3: + \n " ] + + + 3 -> 8 ; +2 [label="2: Exit dereference_ifthenelse \n " color=yellow style=filled] + + +1 [label="1: Start dereference_ifthenelse\nFormals: p:int *\nLocals: x:int y:int \n DECLARE_LOCALS(&return,&x,&y); [line 6]\n NULLIFY(&x,false); [line 6]\n NULLIFY(&y,false); [line 6]\n " color=yellow style=filled] + + + 1 -> 16 ; + 1 -> 17 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/enumeration/enum.c b/infer/tests/codetoanalyze/c/frontend/enumeration/enum.c new file mode 100644 index 000000000..5939f9766 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/enumeration/enum.c @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +enum week{ sunday, monday, tuesday, wednesday=0, thursday, friday, saturday}; + +int main(){ + enum week today; + today=wednesday; + today=monday; + today=today+4; + today=(enum week) tuesday+1; + int i = tuesday+(friday-sunday); + return 0; +} diff --git a/infer/tests/codetoanalyze/c/frontend/enumeration/enum.dot b/infer/tests/codetoanalyze/c/frontend/enumeration/enum.dot new file mode 100644 index 000000000..ec0ba6684 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/enumeration/enum.dot @@ -0,0 +1,33 @@ +digraph iCFG { +8 [label="8: BinaryOperatorStmt: Assign \n *&today:enum { (sunday, 0) (monday, 1) (tuesday, 2) (wednesday, 0) (thursday, 3) (friday, 4) (saturday, 5) }=0 [line 10]\n NULLIFY(&today,false); [line 10]\n " shape="box"] + + + 8 -> 7 ; +7 [label="7: BinaryOperatorStmt: Assign \n *&today:enum { (sunday, 0) (monday, 1) (tuesday, 2) (wednesday, 0) (thursday, 3) (friday, 4) (saturday, 5) }=1 [line 11]\n " shape="box"] + + + 7 -> 6 ; +6 [label="6: BinaryOperatorStmt: Assign \n n$0=*&today:enum { (sunday, 0) (monday, 1) (tuesday, 2) (wednesday, 0) (thursday, 3) (friday, 4) (saturday, 5) } [line 12]\n *&today:enum { (sunday, 0) (monday, 1) (tuesday, 2) (wednesday, 0) (thursday, 3) (friday, 4) (saturday, 5) }=(n$0 + 4) [line 12]\n REMOVE_TEMPS(n$0); [line 12]\n NULLIFY(&today,false); [line 12]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: BinaryOperatorStmt: Assign \n *&today:enum { (sunday, 0) (monday, 1) (tuesday, 2) (wednesday, 0) (thursday, 3) (friday, 4) (saturday, 5) }=(2 + 1) [line 13]\n NULLIFY(&today,false); [line 13]\n " shape="box"] + + + 5 -> 4 ; +4 [label="4: DeclStmt \n *&i:int =(2 + (4 - 0)) [line 14]\n NULLIFY(&i,false); [line 14]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n *&return:int =0 [line 15]\n APPLY_ABSTRACTION; [line 15]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: today:enum { (sunday, 0) (monday, 1) (tuesday, 2) (wednesday, 0) (thursday, 3) (friday, 4) (saturday, 5) } i:int \n DECLARE_LOCALS(&return,&today,&i); [line 8]\n NULLIFY(&i,false); [line 8]\n NULLIFY(&today,false); [line 8]\n " color=yellow style=filled] + + + 1 -> 8 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/gotostmt/goto_ex.c b/infer/tests/codetoanalyze/c/frontend/gotostmt/goto_ex.c new file mode 100644 index 000000000..5233670d3 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/gotostmt/goto_ex.c @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +#import + +int getValue() { + return 2; +} + +int g0() +{ + int a = 0; + if (getValue() > 1) + goto stepC; + + stepB: + stepC: + stepD: + a = 1; + return 1; +} + +int g1() +{ + int a = 0; + if (getValue() > 1) + goto stepB; + return 0; + + stepB: + a = 1; + return 1; +} + +int g2() +{ + int a = 0; + stepB: + a = 1; + + if (!getValue()) + goto exit_step; + if (!getValue()) + goto stepA; + if (getValue() > 1) + goto stepB; + return 0; + +stepA: { + a = 2; + return 2; +} +exit_step: + a = 3; + return 1; +} + +int g3() +{ + stepB: + printf("B\n"); + + if (!getValue()) + goto exit_step; + if (!getValue()) + goto stepA; + if (getValue() > 1) + goto stepB; + printf("g3\n"); + return 0; + +stepA: { + int a = 2; + printf("A\n"); +} +exit_step: + printf("exit\n"); + return 1; +} + +int g4() +{ + stepB: + printf("B\n"); + + if (!getValue()) + goto exit_step; + if (!getValue()) + goto stepA; + if (getValue() > 1) + goto stepB; + printf("g4\n"); + +stepA: { + int a = 2; + printf("A\n"); +} +exit_step: + printf("exit\n"); + return 1; +} + +int g5() +{ + stepB: + printf("B\n"); + + if (!getValue()) + goto exit_step; + if (!getValue()) + goto stepA; + if (getValue() > 1) + goto stepB; + goto exit_step; + +stepA: { + int a = 2; + printf("A\n"); + return 1; +} +exit_step: + printf("exit\n"); + goto stepA; +} + +int g6() +{ + stepB: + printf("B\n"); + + if (!getValue()) + goto exit_step; + if (!getValue()) + goto stepA; + if (getValue() > 1) + goto stepB; + goto exit_step; + +stepA: { + int a = 2; + printf("A\n"); +} +return 1; +exit_step: + printf("exit\n"); + goto stepA; +} + + +int g7() +{ + int i = 0, j = 0, k = 0; + while (i < 10) { + while (j < 10) { + while (k < 10) { + int v = i + j + k; + if (v >= 15) { + goto out; + print: + printf("wow\n"); + goto terminate; + } + } + } + } + out: + printf("out!\n"); + goto print; + terminate: + printf("terminating!\n"); + return 2; +} + +int g8(int q) +{ + int i = 0, j = 0, k = 0; + if(q) goto print; + while (i < 10) { + while (j < 10) { + while (k < 10) { + int v = i + j + k; + if (v >= 15) { + print: + printf("wow\n"); + //goto terminate; + } + } + } + } + out: + printf("out!\n"); + terminate: + printf("terminating!\n"); + return 2; +} diff --git a/infer/tests/codetoanalyze/c/frontend/gotostmt/goto_ex.dot b/infer/tests/codetoanalyze/c/frontend/gotostmt/goto_ex.dot new file mode 100644 index 000000000..f78dcfc1d --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/gotostmt/goto_ex.dot @@ -0,0 +1,1036 @@ +digraph iCFG { +252 [label="252: DeclStmt \n *&i:int =0 [line 178]\n " shape="box"] + + + 252 -> 251 ; +251 [label="251: DeclStmt \n *&j:int =0 [line 178]\n " shape="box"] + + + 251 -> 250 ; +250 [label="250: DeclStmt \n *&k:int =0 [line 178]\n " shape="box"] + + + 250 -> 248 ; + 250 -> 249 ; +249 [label="249: Prune (false branch) \n n$61=*&q:int [line 179]\n PRUNE((n$61 == 0), false); [line 179]\n REMOVE_TEMPS(n$61); [line 179]\n " shape="invhouse"] + + + 249 -> 247 ; +248 [label="248: Prune (true branch) \n n$61=*&q:int [line 179]\n PRUNE((n$61 != 0), true); [line 179]\n REMOVE_TEMPS(n$61); [line 179]\n APPLY_ABSTRACTION; [line 179]\n " shape="invhouse"] + + + 248 -> 245 ; +247 [label="247: + \n " ] + + + 247 -> 228 ; +246 [label="246: DeclStmt \n n$58=*&i:int [line 183]\n n$59=*&j:int [line 183]\n n$60=*&k:int [line 183]\n *&v:int =((n$58 + n$59) + n$60) [line 183]\n REMOVE_TEMPS(n$58,n$59,n$60); [line 183]\n " shape="box"] + + + 246 -> 241 ; +245 [label="245: Skip GotoLabel_print \n NULLIFY(&q,false); [line 185]\n " color="gray"] + + + 245 -> 244 ; +244 [label="244: Call _fun_printf \n n$57=_fun_printf(\"wow\\n\":char *) [line 184]\n REMOVE_TEMPS(n$57); [line 184]\n APPLY_ABSTRACTION; [line 184]\n " shape="box"] + + + 244 -> 240 ; +243 [label="243: Prune (false branch) \n PRUNE(((n$56 >= 15) == 0), false); [line 184]\n REMOVE_TEMPS(n$56); [line 184]\n APPLY_ABSTRACTION; [line 184]\n " shape="invhouse"] + + + 243 -> 240 ; +242 [label="242: Prune (true branch) \n PRUNE(((n$56 >= 15) != 0), true); [line 184]\n REMOVE_TEMPS(n$56); [line 184]\n APPLY_ABSTRACTION; [line 184]\n " shape="invhouse"] + + + 242 -> 245 ; +241 [label="241: BinaryOperatorStmt: GE \n n$56=*&v:int [line 184]\n NULLIFY(&v,false); [line 184]\n " shape="box"] + + + 241 -> 242 ; + 241 -> 243 ; +240 [label="240: + \n " ] + + + 240 -> 236 ; +239 [label="239: Prune (false branch) \n PRUNE(((n$55 < 10) == 0), false); [line 182]\n REMOVE_TEMPS(n$55); [line 182]\n APPLY_ABSTRACTION; [line 182]\n " shape="invhouse"] + + + 239 -> 232 ; +238 [label="238: Prune (true branch) \n PRUNE(((n$55 < 10) != 0), true); [line 182]\n REMOVE_TEMPS(n$55); [line 182]\n " shape="invhouse"] + + + 238 -> 246 ; +237 [label="237: BinaryOperatorStmt: LT \n n$55=*&k:int [line 182]\n " shape="box"] + + + 237 -> 238 ; + 237 -> 239 ; +236 [label="236: + \n " ] + + + 236 -> 237 ; +235 [label="235: Prune (false branch) \n PRUNE(((n$54 < 10) == 0), false); [line 181]\n REMOVE_TEMPS(n$54); [line 181]\n APPLY_ABSTRACTION; [line 181]\n " shape="invhouse"] + + + 235 -> 228 ; +234 [label="234: Prune (true branch) \n PRUNE(((n$54 < 10) != 0), true); [line 181]\n REMOVE_TEMPS(n$54); [line 181]\n APPLY_ABSTRACTION; [line 181]\n " shape="invhouse"] + + + 234 -> 236 ; +233 [label="233: BinaryOperatorStmt: LT \n n$54=*&j:int [line 181]\n " shape="box"] + + + 233 -> 234 ; + 233 -> 235 ; +232 [label="232: + \n " ] + + + 232 -> 233 ; +231 [label="231: Prune (false branch) \n PRUNE(((n$53 < 10) == 0), false); [line 180]\n REMOVE_TEMPS(n$53); [line 180]\n " shape="invhouse"] + + + 231 -> 227 ; +230 [label="230: Prune (true branch) \n PRUNE(((n$53 < 10) != 0), true); [line 180]\n REMOVE_TEMPS(n$53); [line 180]\n APPLY_ABSTRACTION; [line 180]\n " shape="invhouse"] + + + 230 -> 232 ; +229 [label="229: BinaryOperatorStmt: LT \n NULLIFY(&q,false); [line 180]\n n$53=*&i:int [line 180]\n " shape="box"] + + + 229 -> 230 ; + 229 -> 231 ; +228 [label="228: + \n " ] + + + 228 -> 229 ; +227 [label="227: Skip GotoLabel_out \n NULLIFY(&i,false); [line 192]\n NULLIFY(&j,false); [line 192]\n NULLIFY(&k,false); [line 192]\n " color="gray"] + + + 227 -> 226 ; +226 [label="226: Call _fun_printf \n n$52=_fun_printf(\"out!\\n\":char *) [line 177]\n REMOVE_TEMPS(n$52); [line 177]\n " shape="box"] + + + 226 -> 225 ; +225 [label="225: Skip GotoLabel_terminate \n " color="gray"] + + + 225 -> 224 ; +224 [label="224: Call _fun_printf \n n$51=_fun_printf(\"terminating!\\n\":char *) [line 177]\n REMOVE_TEMPS(n$51); [line 177]\n " shape="box"] + + + 224 -> 223 ; +223 [label="223: Return Stmt \n *&return:int =2 [line 196]\n APPLY_ABSTRACTION; [line 196]\n " shape="box"] + + + 223 -> 222 ; +222 [label="222: Exit g8 \n " color=yellow style=filled] + + +221 [label="221: Start g8\nFormals: q:int \nLocals: i:int j:int k:int v:int \n DECLARE_LOCALS(&return,&i,&j,&k,&v); [line 176]\n NULLIFY(&i,false); [line 176]\n NULLIFY(&j,false); [line 176]\n NULLIFY(&k,false); [line 176]\n NULLIFY(&v,false); [line 176]\n " color=yellow style=filled] + + + 221 -> 252 ; +220 [label="220: DeclStmt \n *&i:int =0 [line 154]\n " shape="box"] + + + 220 -> 219 ; +219 [label="219: DeclStmt \n *&j:int =0 [line 154]\n " shape="box"] + + + 219 -> 218 ; +218 [label="218: DeclStmt \n *&k:int =0 [line 154]\n APPLY_ABSTRACTION; [line 154]\n " shape="box"] + + + 218 -> 200 ; +217 [label="217: DeclStmt \n n$48=*&i:int [line 158]\n n$49=*&j:int [line 158]\n n$50=*&k:int [line 158]\n *&v:int =((n$48 + n$49) + n$50) [line 158]\n REMOVE_TEMPS(n$48,n$49,n$50); [line 158]\n " shape="box"] + + + 217 -> 213 ; +216 [label="216: Call _fun_printf \n n$47=_fun_printf(\"wow\\n\":char *) [line 159]\n REMOVE_TEMPS(n$47); [line 159]\n " shape="box"] + + + 216 -> 196 ; +215 [label="215: Prune (false branch) \n PRUNE(((n$46 >= 15) == 0), false); [line 159]\n REMOVE_TEMPS(n$46); [line 159]\n " shape="invhouse"] + + + 215 -> 212 ; +214 [label="214: Prune (true branch) \n PRUNE(((n$46 >= 15) != 0), true); [line 159]\n REMOVE_TEMPS(n$46); [line 159]\n APPLY_ABSTRACTION; [line 159]\n " shape="invhouse"] + + + 214 -> 199 ; +213 [label="213: BinaryOperatorStmt: GE \n n$46=*&v:int [line 159]\n NULLIFY(&v,false); [line 159]\n " shape="box"] + + + 213 -> 214 ; + 213 -> 215 ; +212 [label="212: + \n " ] + + + 212 -> 208 ; +211 [label="211: Prune (false branch) \n PRUNE(((n$45 < 10) == 0), false); [line 157]\n REMOVE_TEMPS(n$45); [line 157]\n APPLY_ABSTRACTION; [line 157]\n " shape="invhouse"] + + + 211 -> 204 ; +210 [label="210: Prune (true branch) \n PRUNE(((n$45 < 10) != 0), true); [line 157]\n REMOVE_TEMPS(n$45); [line 157]\n " shape="invhouse"] + + + 210 -> 217 ; +209 [label="209: BinaryOperatorStmt: LT \n n$45=*&k:int [line 157]\n " shape="box"] + + + 209 -> 210 ; + 209 -> 211 ; +208 [label="208: + \n " ] + + + 208 -> 209 ; +207 [label="207: Prune (false branch) \n PRUNE(((n$44 < 10) == 0), false); [line 156]\n REMOVE_TEMPS(n$44); [line 156]\n APPLY_ABSTRACTION; [line 156]\n " shape="invhouse"] + + + 207 -> 200 ; +206 [label="206: Prune (true branch) \n PRUNE(((n$44 < 10) != 0), true); [line 156]\n REMOVE_TEMPS(n$44); [line 156]\n APPLY_ABSTRACTION; [line 156]\n " shape="invhouse"] + + + 206 -> 208 ; +205 [label="205: BinaryOperatorStmt: LT \n n$44=*&j:int [line 156]\n " shape="box"] + + + 205 -> 206 ; + 205 -> 207 ; +204 [label="204: + \n " ] + + + 204 -> 205 ; +203 [label="203: Prune (false branch) \n PRUNE(((n$43 < 10) == 0), false); [line 155]\n REMOVE_TEMPS(n$43); [line 155]\n APPLY_ABSTRACTION; [line 155]\n " shape="invhouse"] + + + 203 -> 199 ; +202 [label="202: Prune (true branch) \n PRUNE(((n$43 < 10) != 0), true); [line 155]\n REMOVE_TEMPS(n$43); [line 155]\n APPLY_ABSTRACTION; [line 155]\n " shape="invhouse"] + + + 202 -> 204 ; +201 [label="201: BinaryOperatorStmt: LT \n n$43=*&i:int [line 155]\n " shape="box"] + + + 201 -> 202 ; + 201 -> 203 ; +200 [label="200: + \n " ] + + + 200 -> 201 ; +199 [label="199: Skip GotoLabel_out \n NULLIFY(&i,false); [line 168]\n NULLIFY(&j,false); [line 168]\n NULLIFY(&k,false); [line 168]\n " color="gray"] + + + 199 -> 198 ; +198 [label="198: Call _fun_printf \n n$42=_fun_printf(\"out!\\n\":char *) [line 153]\n REMOVE_TEMPS(n$42); [line 153]\n " shape="box"] + + + 198 -> 197 ; +197 [label="197: Skip GotoLabel_print \n " color="gray"] + + + 197 -> 216 ; +196 [label="196: Skip GotoLabel_terminate \n " color="gray"] + + + 196 -> 195 ; +195 [label="195: Call _fun_printf \n n$41=_fun_printf(\"terminating!\\n\":char *) [line 153]\n REMOVE_TEMPS(n$41); [line 153]\n " shape="box"] + + + 195 -> 194 ; +194 [label="194: Return Stmt \n *&return:int =2 [line 173]\n APPLY_ABSTRACTION; [line 173]\n " shape="box"] + + + 194 -> 193 ; +193 [label="193: Exit g7 \n " color=yellow style=filled] + + +192 [label="192: Start g7\nFormals: \nLocals: i:int j:int k:int v:int \n DECLARE_LOCALS(&return,&i,&j,&k,&v); [line 152]\n NULLIFY(&i,false); [line 152]\n NULLIFY(&j,false); [line 152]\n NULLIFY(&k,false); [line 152]\n NULLIFY(&v,false); [line 152]\n " color=yellow style=filled] + + + 192 -> 220 ; +191 [label="191: Call _fun_printf \n n$40=_fun_printf(\"B\\n\":char *) [line 129]\n REMOVE_TEMPS(n$40); [line 129]\n " shape="box"] + + + 191 -> 184 ; +190 [label="190: Prune (false branch) \n n$39=*&SIL_temp_conditional___183:int [line 133]\n NULLIFY(&SIL_temp_conditional___183,true); [line 133]\n PRUNE((n$39 == 0), false); [line 133]\n REMOVE_TEMPS(n$39); [line 133]\n " shape="invhouse"] + + + 190 -> 182 ; +189 [label="189: Prune (true branch) \n n$39=*&SIL_temp_conditional___183:int [line 133]\n NULLIFY(&SIL_temp_conditional___183,true); [line 133]\n PRUNE((n$39 != 0), true); [line 133]\n REMOVE_TEMPS(n$39); [line 133]\n APPLY_ABSTRACTION; [line 133]\n " shape="invhouse"] + + + 189 -> 164 ; +188 [label="188: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___183); [line 133]\n *&SIL_temp_conditional___183:int =1 [line 133]\n APPLY_ABSTRACTION; [line 133]\n " shape="box"] + + + 188 -> 183 ; +187 [label="187: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___183); [line 133]\n *&SIL_temp_conditional___183:int =0 [line 133]\n APPLY_ABSTRACTION; [line 133]\n " shape="box"] + + + 187 -> 183 ; +186 [label="186: Prune (false branch) \n PRUNE((n$38 == 0), false); [line 133]\n REMOVE_TEMPS(n$38); [line 133]\n " shape="invhouse"] + + + 186 -> 188 ; +185 [label="185: Prune (true branch) \n PRUNE((n$38 != 0), true); [line 133]\n REMOVE_TEMPS(n$38); [line 133]\n " shape="invhouse"] + + + 185 -> 187 ; +184 [label="184: Call _fun_getValue \n n$38=_fun_getValue() [line 133]\n " shape="box"] + + + 184 -> 185 ; + 184 -> 186 ; +183 [label="183: + \n " ] + + + 183 -> 189 ; + 183 -> 190 ; +182 [label="182: + \n " ] + + + 182 -> 175 ; +181 [label="181: Prune (false branch) \n n$37=*&SIL_temp_conditional___174:int [line 135]\n NULLIFY(&SIL_temp_conditional___174,true); [line 135]\n PRUNE((n$37 == 0), false); [line 135]\n REMOVE_TEMPS(n$37); [line 135]\n " shape="invhouse"] + + + 181 -> 173 ; +180 [label="180: Prune (true branch) \n n$37=*&SIL_temp_conditional___174:int [line 135]\n NULLIFY(&SIL_temp_conditional___174,true); [line 135]\n PRUNE((n$37 != 0), true); [line 135]\n REMOVE_TEMPS(n$37); [line 135]\n APPLY_ABSTRACTION; [line 135]\n " shape="invhouse"] + + + 180 -> 162 ; +179 [label="179: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___174); [line 135]\n *&SIL_temp_conditional___174:int =1 [line 135]\n APPLY_ABSTRACTION; [line 135]\n " shape="box"] + + + 179 -> 174 ; +178 [label="178: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___174); [line 135]\n *&SIL_temp_conditional___174:int =0 [line 135]\n APPLY_ABSTRACTION; [line 135]\n " shape="box"] + + + 178 -> 174 ; +177 [label="177: Prune (false branch) \n PRUNE((n$36 == 0), false); [line 135]\n REMOVE_TEMPS(n$36); [line 135]\n " shape="invhouse"] + + + 177 -> 179 ; +176 [label="176: Prune (true branch) \n PRUNE((n$36 != 0), true); [line 135]\n REMOVE_TEMPS(n$36); [line 135]\n " shape="invhouse"] + + + 176 -> 178 ; +175 [label="175: Call _fun_getValue \n n$36=_fun_getValue() [line 135]\n " shape="box"] + + + 175 -> 176 ; + 175 -> 177 ; +174 [label="174: + \n " ] + + + 174 -> 180 ; + 174 -> 181 ; +173 [label="173: + \n " ] + + + 173 -> 169 ; +172 [label="172: Skip GotoLabel_stepB \n " color="gray"] + + + 172 -> 191 ; +171 [label="171: Prune (false branch) \n PRUNE(((n$35 > 1) == 0), false); [line 137]\n REMOVE_TEMPS(n$35); [line 137]\n " shape="invhouse"] + + + 171 -> 168 ; +170 [label="170: Prune (true branch) \n PRUNE(((n$35 > 1) != 0), true); [line 137]\n REMOVE_TEMPS(n$35); [line 137]\n APPLY_ABSTRACTION; [line 137]\n " shape="invhouse"] + + + 170 -> 172 ; +169 [label="169: BinaryOperatorStmt: GT \n n$35=_fun_getValue() [line 137]\n " shape="box"] + + + 169 -> 170 ; + 169 -> 171 ; +168 [label="168: + \n " ] + + + 168 -> 164 ; +167 [label="167: DeclStmt \n *&a:int =2 [line 142]\n NULLIFY(&a,false); [line 142]\n " shape="box"] + + + 167 -> 166 ; +166 [label="166: Call _fun_printf \n n$34=_fun_printf(\"A\\n\":char *) [line 143]\n REMOVE_TEMPS(n$34); [line 143]\n " shape="box"] + + + 166 -> 165 ; +165 [label="165: Return Stmt \n *&return:int =1 [line 145]\n APPLY_ABSTRACTION; [line 145]\n " shape="box"] + + + 165 -> 161 ; +164 [label="164: Skip GotoLabel_exit_step \n " color="gray"] + + + 164 -> 163 ; +163 [label="163: Call _fun_printf \n n$33=_fun_printf(\"exit\\n\":char *) [line 129]\n REMOVE_TEMPS(n$33); [line 129]\n APPLY_ABSTRACTION; [line 129]\n " shape="box"] + + + 163 -> 162 ; +162 [label="162: Skip GotoLabel_stepA \n " color="gray"] + + + 162 -> 167 ; +161 [label="161: Exit g6 \n " color=yellow style=filled] + + +160 [label="160: Start g6\nFormals: \nLocals: a:int \n DECLARE_LOCALS(&return,&a); [line 128]\n NULLIFY(&a,false); [line 128]\n " color=yellow style=filled] + + + 160 -> 172 ; +159 [label="159: Call _fun_printf \n n$32=_fun_printf(\"B\\n\":char *) [line 106]\n REMOVE_TEMPS(n$32); [line 106]\n " shape="box"] + + + 159 -> 152 ; +158 [label="158: Prune (false branch) \n n$31=*&SIL_temp_conditional___151:int [line 110]\n NULLIFY(&SIL_temp_conditional___151,true); [line 110]\n PRUNE((n$31 == 0), false); [line 110]\n REMOVE_TEMPS(n$31); [line 110]\n " shape="invhouse"] + + + 158 -> 150 ; +157 [label="157: Prune (true branch) \n n$31=*&SIL_temp_conditional___151:int [line 110]\n NULLIFY(&SIL_temp_conditional___151,true); [line 110]\n PRUNE((n$31 != 0), true); [line 110]\n REMOVE_TEMPS(n$31); [line 110]\n APPLY_ABSTRACTION; [line 110]\n " shape="invhouse"] + + + 157 -> 132 ; +156 [label="156: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___151); [line 110]\n *&SIL_temp_conditional___151:int =1 [line 110]\n APPLY_ABSTRACTION; [line 110]\n " shape="box"] + + + 156 -> 151 ; +155 [label="155: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___151); [line 110]\n *&SIL_temp_conditional___151:int =0 [line 110]\n APPLY_ABSTRACTION; [line 110]\n " shape="box"] + + + 155 -> 151 ; +154 [label="154: Prune (false branch) \n PRUNE((n$30 == 0), false); [line 110]\n REMOVE_TEMPS(n$30); [line 110]\n " shape="invhouse"] + + + 154 -> 156 ; +153 [label="153: Prune (true branch) \n PRUNE((n$30 != 0), true); [line 110]\n REMOVE_TEMPS(n$30); [line 110]\n " shape="invhouse"] + + + 153 -> 155 ; +152 [label="152: Call _fun_getValue \n n$30=_fun_getValue() [line 110]\n " shape="box"] + + + 152 -> 153 ; + 152 -> 154 ; +151 [label="151: + \n " ] + + + 151 -> 157 ; + 151 -> 158 ; +150 [label="150: + \n " ] + + + 150 -> 143 ; +149 [label="149: Prune (false branch) \n n$29=*&SIL_temp_conditional___142:int [line 112]\n NULLIFY(&SIL_temp_conditional___142,true); [line 112]\n PRUNE((n$29 == 0), false); [line 112]\n REMOVE_TEMPS(n$29); [line 112]\n " shape="invhouse"] + + + 149 -> 141 ; +148 [label="148: Prune (true branch) \n n$29=*&SIL_temp_conditional___142:int [line 112]\n NULLIFY(&SIL_temp_conditional___142,true); [line 112]\n PRUNE((n$29 != 0), true); [line 112]\n REMOVE_TEMPS(n$29); [line 112]\n APPLY_ABSTRACTION; [line 112]\n " shape="invhouse"] + + + 148 -> 130 ; +147 [label="147: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___142); [line 112]\n *&SIL_temp_conditional___142:int =1 [line 112]\n APPLY_ABSTRACTION; [line 112]\n " shape="box"] + + + 147 -> 142 ; +146 [label="146: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___142); [line 112]\n *&SIL_temp_conditional___142:int =0 [line 112]\n APPLY_ABSTRACTION; [line 112]\n " shape="box"] + + + 146 -> 142 ; +145 [label="145: Prune (false branch) \n PRUNE((n$28 == 0), false); [line 112]\n REMOVE_TEMPS(n$28); [line 112]\n " shape="invhouse"] + + + 145 -> 147 ; +144 [label="144: Prune (true branch) \n PRUNE((n$28 != 0), true); [line 112]\n REMOVE_TEMPS(n$28); [line 112]\n " shape="invhouse"] + + + 144 -> 146 ; +143 [label="143: Call _fun_getValue \n n$28=_fun_getValue() [line 112]\n " shape="box"] + + + 143 -> 144 ; + 143 -> 145 ; +142 [label="142: + \n " ] + + + 142 -> 148 ; + 142 -> 149 ; +141 [label="141: + \n " ] + + + 141 -> 137 ; +140 [label="140: Skip GotoLabel_stepB \n " color="gray"] + + + 140 -> 159 ; +139 [label="139: Prune (false branch) \n PRUNE(((n$27 > 1) == 0), false); [line 114]\n REMOVE_TEMPS(n$27); [line 114]\n " shape="invhouse"] + + + 139 -> 136 ; +138 [label="138: Prune (true branch) \n PRUNE(((n$27 > 1) != 0), true); [line 114]\n REMOVE_TEMPS(n$27); [line 114]\n APPLY_ABSTRACTION; [line 114]\n " shape="invhouse"] + + + 138 -> 140 ; +137 [label="137: BinaryOperatorStmt: GT \n n$27=_fun_getValue() [line 114]\n " shape="box"] + + + 137 -> 138 ; + 137 -> 139 ; +136 [label="136: + \n " ] + + + 136 -> 132 ; +135 [label="135: DeclStmt \n *&a:int =2 [line 119]\n NULLIFY(&a,false); [line 119]\n " shape="box"] + + + 135 -> 134 ; +134 [label="134: Call _fun_printf \n n$26=_fun_printf(\"A\\n\":char *) [line 120]\n REMOVE_TEMPS(n$26); [line 120]\n " shape="box"] + + + 134 -> 133 ; +133 [label="133: Return Stmt \n *&return:int =1 [line 121]\n APPLY_ABSTRACTION; [line 121]\n " shape="box"] + + + 133 -> 129 ; +132 [label="132: Skip GotoLabel_exit_step \n " color="gray"] + + + 132 -> 131 ; +131 [label="131: Call _fun_printf \n n$25=_fun_printf(\"exit\\n\":char *) [line 106]\n REMOVE_TEMPS(n$25); [line 106]\n APPLY_ABSTRACTION; [line 106]\n " shape="box"] + + + 131 -> 130 ; +130 [label="130: Skip GotoLabel_stepA \n " color="gray"] + + + 130 -> 135 ; +129 [label="129: Exit g5 \n " color=yellow style=filled] + + +128 [label="128: Start g5\nFormals: \nLocals: a:int \n DECLARE_LOCALS(&return,&a); [line 105]\n NULLIFY(&a,false); [line 105]\n " color=yellow style=filled] + + + 128 -> 140 ; +127 [label="127: Call _fun_printf \n n$24=_fun_printf(\"B\\n\":char *) [line 84]\n REMOVE_TEMPS(n$24); [line 84]\n " shape="box"] + + + 127 -> 120 ; +126 [label="126: Prune (false branch) \n n$23=*&SIL_temp_conditional___119:int [line 88]\n NULLIFY(&SIL_temp_conditional___119,true); [line 88]\n PRUNE((n$23 == 0), false); [line 88]\n REMOVE_TEMPS(n$23); [line 88]\n " shape="invhouse"] + + + 126 -> 118 ; +125 [label="125: Prune (true branch) \n n$23=*&SIL_temp_conditional___119:int [line 88]\n NULLIFY(&SIL_temp_conditional___119,true); [line 88]\n PRUNE((n$23 != 0), true); [line 88]\n REMOVE_TEMPS(n$23); [line 88]\n APPLY_ABSTRACTION; [line 88]\n " shape="invhouse"] + + + 125 -> 99 ; +124 [label="124: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___119); [line 88]\n *&SIL_temp_conditional___119:int =1 [line 88]\n APPLY_ABSTRACTION; [line 88]\n " shape="box"] + + + 124 -> 119 ; +123 [label="123: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___119); [line 88]\n *&SIL_temp_conditional___119:int =0 [line 88]\n APPLY_ABSTRACTION; [line 88]\n " shape="box"] + + + 123 -> 119 ; +122 [label="122: Prune (false branch) \n PRUNE((n$22 == 0), false); [line 88]\n REMOVE_TEMPS(n$22); [line 88]\n " shape="invhouse"] + + + 122 -> 124 ; +121 [label="121: Prune (true branch) \n PRUNE((n$22 != 0), true); [line 88]\n REMOVE_TEMPS(n$22); [line 88]\n " shape="invhouse"] + + + 121 -> 123 ; +120 [label="120: Call _fun_getValue \n n$22=_fun_getValue() [line 88]\n " shape="box"] + + + 120 -> 121 ; + 120 -> 122 ; +119 [label="119: + \n " ] + + + 119 -> 125 ; + 119 -> 126 ; +118 [label="118: + \n " ] + + + 118 -> 111 ; +117 [label="117: Prune (false branch) \n n$21=*&SIL_temp_conditional___110:int [line 90]\n NULLIFY(&SIL_temp_conditional___110,true); [line 90]\n PRUNE((n$21 == 0), false); [line 90]\n REMOVE_TEMPS(n$21); [line 90]\n " shape="invhouse"] + + + 117 -> 109 ; +116 [label="116: Prune (true branch) \n n$21=*&SIL_temp_conditional___110:int [line 90]\n NULLIFY(&SIL_temp_conditional___110,true); [line 90]\n PRUNE((n$21 != 0), true); [line 90]\n REMOVE_TEMPS(n$21); [line 90]\n APPLY_ABSTRACTION; [line 90]\n " shape="invhouse"] + + + 116 -> 102 ; +115 [label="115: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___110); [line 90]\n *&SIL_temp_conditional___110:int =1 [line 90]\n APPLY_ABSTRACTION; [line 90]\n " shape="box"] + + + 115 -> 110 ; +114 [label="114: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___110); [line 90]\n *&SIL_temp_conditional___110:int =0 [line 90]\n APPLY_ABSTRACTION; [line 90]\n " shape="box"] + + + 114 -> 110 ; +113 [label="113: Prune (false branch) \n PRUNE((n$20 == 0), false); [line 90]\n REMOVE_TEMPS(n$20); [line 90]\n " shape="invhouse"] + + + 113 -> 115 ; +112 [label="112: Prune (true branch) \n PRUNE((n$20 != 0), true); [line 90]\n REMOVE_TEMPS(n$20); [line 90]\n " shape="invhouse"] + + + 112 -> 114 ; +111 [label="111: Call _fun_getValue \n n$20=_fun_getValue() [line 90]\n " shape="box"] + + + 111 -> 112 ; + 111 -> 113 ; +110 [label="110: + \n " ] + + + 110 -> 116 ; + 110 -> 117 ; +109 [label="109: + \n " ] + + + 109 -> 105 ; +108 [label="108: Skip GotoLabel_stepB \n " color="gray"] + + + 108 -> 127 ; +107 [label="107: Prune (false branch) \n PRUNE(((n$19 > 1) == 0), false); [line 92]\n REMOVE_TEMPS(n$19); [line 92]\n " shape="invhouse"] + + + 107 -> 104 ; +106 [label="106: Prune (true branch) \n PRUNE(((n$19 > 1) != 0), true); [line 92]\n REMOVE_TEMPS(n$19); [line 92]\n APPLY_ABSTRACTION; [line 92]\n " shape="invhouse"] + + + 106 -> 108 ; +105 [label="105: BinaryOperatorStmt: GT \n n$19=_fun_getValue() [line 92]\n " shape="box"] + + + 105 -> 106 ; + 105 -> 107 ; +104 [label="104: + \n " ] + + + 104 -> 103 ; +103 [label="103: Call _fun_printf \n n$18=_fun_printf(\"g4\\n\":char *) [line 94]\n REMOVE_TEMPS(n$18); [line 94]\n APPLY_ABSTRACTION; [line 94]\n " shape="box"] + + + 103 -> 102 ; +102 [label="102: Skip GotoLabel_stepA \n " color="gray"] + + + 102 -> 101 ; +101 [label="101: DeclStmt \n *&a:int =2 [line 97]\n NULLIFY(&a,false); [line 97]\n " shape="box"] + + + 101 -> 100 ; +100 [label="100: Call _fun_printf \n n$17=_fun_printf(\"A\\n\":char *) [line 98]\n REMOVE_TEMPS(n$17); [line 98]\n APPLY_ABSTRACTION; [line 98]\n " shape="box"] + + + 100 -> 99 ; +99 [label="99: Skip GotoLabel_exit_step \n " color="gray"] + + + 99 -> 98 ; +98 [label="98: Call _fun_printf \n n$16=_fun_printf(\"exit\\n\":char *) [line 84]\n REMOVE_TEMPS(n$16); [line 84]\n " shape="box"] + + + 98 -> 97 ; +97 [label="97: Return Stmt \n *&return:int =1 [line 102]\n APPLY_ABSTRACTION; [line 102]\n " shape="box"] + + + 97 -> 96 ; +96 [label="96: Exit g4 \n " color=yellow style=filled] + + +95 [label="95: Start g4\nFormals: \nLocals: a:int \n DECLARE_LOCALS(&return,&a); [line 83]\n NULLIFY(&a,false); [line 83]\n " color=yellow style=filled] + + + 95 -> 108 ; +94 [label="94: Call _fun_printf \n n$15=_fun_printf(\"B\\n\":char *) [line 61]\n REMOVE_TEMPS(n$15); [line 61]\n " shape="box"] + + + 94 -> 87 ; +93 [label="93: Prune (false branch) \n n$14=*&SIL_temp_conditional___86:int [line 65]\n NULLIFY(&SIL_temp_conditional___86,true); [line 65]\n PRUNE((n$14 == 0), false); [line 65]\n REMOVE_TEMPS(n$14); [line 65]\n " shape="invhouse"] + + + 93 -> 85 ; +92 [label="92: Prune (true branch) \n n$14=*&SIL_temp_conditional___86:int [line 65]\n NULLIFY(&SIL_temp_conditional___86,true); [line 65]\n PRUNE((n$14 != 0), true); [line 65]\n REMOVE_TEMPS(n$14); [line 65]\n APPLY_ABSTRACTION; [line 65]\n " shape="invhouse"] + + + 92 -> 65 ; +91 [label="91: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___86); [line 65]\n *&SIL_temp_conditional___86:int =1 [line 65]\n APPLY_ABSTRACTION; [line 65]\n " shape="box"] + + + 91 -> 86 ; +90 [label="90: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___86); [line 65]\n *&SIL_temp_conditional___86:int =0 [line 65]\n APPLY_ABSTRACTION; [line 65]\n " shape="box"] + + + 90 -> 86 ; +89 [label="89: Prune (false branch) \n PRUNE((n$13 == 0), false); [line 65]\n REMOVE_TEMPS(n$13); [line 65]\n " shape="invhouse"] + + + 89 -> 91 ; +88 [label="88: Prune (true branch) \n PRUNE((n$13 != 0), true); [line 65]\n REMOVE_TEMPS(n$13); [line 65]\n " shape="invhouse"] + + + 88 -> 90 ; +87 [label="87: Call _fun_getValue \n n$13=_fun_getValue() [line 65]\n " shape="box"] + + + 87 -> 88 ; + 87 -> 89 ; +86 [label="86: + \n " ] + + + 86 -> 92 ; + 86 -> 93 ; +85 [label="85: + \n " ] + + + 85 -> 78 ; +84 [label="84: Prune (false branch) \n n$12=*&SIL_temp_conditional___77:int [line 67]\n NULLIFY(&SIL_temp_conditional___77,true); [line 67]\n PRUNE((n$12 == 0), false); [line 67]\n REMOVE_TEMPS(n$12); [line 67]\n " shape="invhouse"] + + + 84 -> 76 ; +83 [label="83: Prune (true branch) \n n$12=*&SIL_temp_conditional___77:int [line 67]\n NULLIFY(&SIL_temp_conditional___77,true); [line 67]\n PRUNE((n$12 != 0), true); [line 67]\n REMOVE_TEMPS(n$12); [line 67]\n " shape="invhouse"] + + + 83 -> 68 ; +82 [label="82: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___77); [line 67]\n *&SIL_temp_conditional___77:int =1 [line 67]\n APPLY_ABSTRACTION; [line 67]\n " shape="box"] + + + 82 -> 77 ; +81 [label="81: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___77); [line 67]\n *&SIL_temp_conditional___77:int =0 [line 67]\n APPLY_ABSTRACTION; [line 67]\n " shape="box"] + + + 81 -> 77 ; +80 [label="80: Prune (false branch) \n PRUNE((n$11 == 0), false); [line 67]\n REMOVE_TEMPS(n$11); [line 67]\n " shape="invhouse"] + + + 80 -> 82 ; +79 [label="79: Prune (true branch) \n PRUNE((n$11 != 0), true); [line 67]\n REMOVE_TEMPS(n$11); [line 67]\n " shape="invhouse"] + + + 79 -> 81 ; +78 [label="78: Call _fun_getValue \n n$11=_fun_getValue() [line 67]\n " shape="box"] + + + 78 -> 79 ; + 78 -> 80 ; +77 [label="77: + \n " ] + + + 77 -> 83 ; + 77 -> 84 ; +76 [label="76: + \n " ] + + + 76 -> 72 ; +75 [label="75: Skip GotoLabel_stepB \n " color="gray"] + + + 75 -> 94 ; +74 [label="74: Prune (false branch) \n PRUNE(((n$10 > 1) == 0), false); [line 69]\n REMOVE_TEMPS(n$10); [line 69]\n " shape="invhouse"] + + + 74 -> 71 ; +73 [label="73: Prune (true branch) \n PRUNE(((n$10 > 1) != 0), true); [line 69]\n REMOVE_TEMPS(n$10); [line 69]\n APPLY_ABSTRACTION; [line 69]\n " shape="invhouse"] + + + 73 -> 75 ; +72 [label="72: BinaryOperatorStmt: GT \n n$10=_fun_getValue() [line 69]\n " shape="box"] + + + 72 -> 73 ; + 72 -> 74 ; +71 [label="71: + \n " ] + + + 71 -> 70 ; +70 [label="70: Call _fun_printf \n n$9=_fun_printf(\"g3\\n\":char *) [line 71]\n REMOVE_TEMPS(n$9); [line 71]\n " shape="box"] + + + 70 -> 69 ; +69 [label="69: Return Stmt \n *&return:int =0 [line 72]\n APPLY_ABSTRACTION; [line 72]\n " shape="box"] + + + 69 -> 62 ; +68 [label="68: Skip GotoLabel_stepA \n " color="gray"] + + + 68 -> 67 ; +67 [label="67: DeclStmt \n *&a:int =2 [line 75]\n NULLIFY(&a,false); [line 75]\n " shape="box"] + + + 67 -> 66 ; +66 [label="66: Call _fun_printf \n n$8=_fun_printf(\"A\\n\":char *) [line 76]\n REMOVE_TEMPS(n$8); [line 76]\n APPLY_ABSTRACTION; [line 76]\n " shape="box"] + + + 66 -> 65 ; +65 [label="65: Skip GotoLabel_exit_step \n " color="gray"] + + + 65 -> 64 ; +64 [label="64: Call _fun_printf \n n$7=_fun_printf(\"exit\\n\":char *) [line 61]\n REMOVE_TEMPS(n$7); [line 61]\n " shape="box"] + + + 64 -> 63 ; +63 [label="63: Return Stmt \n *&return:int =1 [line 80]\n APPLY_ABSTRACTION; [line 80]\n " shape="box"] + + + 63 -> 62 ; +62 [label="62: Exit g3 \n " color=yellow style=filled] + + +61 [label="61: Start g3\nFormals: \nLocals: a:int \n DECLARE_LOCALS(&return,&a); [line 60]\n NULLIFY(&a,false); [line 60]\n " color=yellow style=filled] + + + 61 -> 75 ; +60 [label="60: DeclStmt \n *&a:int =0 [line 39]\n NULLIFY(&a,false); [line 39]\n APPLY_ABSTRACTION; [line 39]\n " shape="box"] + + + 60 -> 40 ; +59 [label="59: BinaryOperatorStmt: Assign \n *&a:int =1 [line 38]\n NULLIFY(&a,false); [line 38]\n " shape="box"] + + + 59 -> 52 ; +58 [label="58: Prune (false branch) \n n$6=*&SIL_temp_conditional___51:int [line 43]\n NULLIFY(&SIL_temp_conditional___51,true); [line 43]\n PRUNE((n$6 == 0), false); [line 43]\n REMOVE_TEMPS(n$6); [line 43]\n " shape="invhouse"] + + + 58 -> 50 ; +57 [label="57: Prune (true branch) \n n$6=*&SIL_temp_conditional___51:int [line 43]\n NULLIFY(&SIL_temp_conditional___51,true); [line 43]\n PRUNE((n$6 != 0), true); [line 43]\n REMOVE_TEMPS(n$6); [line 43]\n " shape="invhouse"] + + + 57 -> 31 ; +56 [label="56: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___51); [line 43]\n *&SIL_temp_conditional___51:int =1 [line 43]\n APPLY_ABSTRACTION; [line 43]\n " shape="box"] + + + 56 -> 51 ; +55 [label="55: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___51); [line 43]\n *&SIL_temp_conditional___51:int =0 [line 43]\n APPLY_ABSTRACTION; [line 43]\n " shape="box"] + + + 55 -> 51 ; +54 [label="54: Prune (false branch) \n PRUNE((n$5 == 0), false); [line 43]\n REMOVE_TEMPS(n$5); [line 43]\n " shape="invhouse"] + + + 54 -> 56 ; +53 [label="53: Prune (true branch) \n PRUNE((n$5 != 0), true); [line 43]\n REMOVE_TEMPS(n$5); [line 43]\n " shape="invhouse"] + + + 53 -> 55 ; +52 [label="52: Call _fun_getValue \n n$5=_fun_getValue() [line 43]\n " shape="box"] + + + 52 -> 53 ; + 52 -> 54 ; +51 [label="51: + \n " ] + + + 51 -> 57 ; + 51 -> 58 ; +50 [label="50: + \n " ] + + + 50 -> 43 ; +49 [label="49: Prune (false branch) \n n$4=*&SIL_temp_conditional___42:int [line 45]\n NULLIFY(&SIL_temp_conditional___42,true); [line 45]\n PRUNE((n$4 == 0), false); [line 45]\n REMOVE_TEMPS(n$4); [line 45]\n " shape="invhouse"] + + + 49 -> 41 ; +48 [label="48: Prune (true branch) \n n$4=*&SIL_temp_conditional___42:int [line 45]\n NULLIFY(&SIL_temp_conditional___42,true); [line 45]\n PRUNE((n$4 != 0), true); [line 45]\n REMOVE_TEMPS(n$4); [line 45]\n " shape="invhouse"] + + + 48 -> 34 ; +47 [label="47: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___42); [line 45]\n *&SIL_temp_conditional___42:int =1 [line 45]\n APPLY_ABSTRACTION; [line 45]\n " shape="box"] + + + 47 -> 42 ; +46 [label="46: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___42); [line 45]\n *&SIL_temp_conditional___42:int =0 [line 45]\n APPLY_ABSTRACTION; [line 45]\n " shape="box"] + + + 46 -> 42 ; +45 [label="45: Prune (false branch) \n PRUNE((n$3 == 0), false); [line 45]\n REMOVE_TEMPS(n$3); [line 45]\n " shape="invhouse"] + + + 45 -> 47 ; +44 [label="44: Prune (true branch) \n PRUNE((n$3 != 0), true); [line 45]\n REMOVE_TEMPS(n$3); [line 45]\n " shape="invhouse"] + + + 44 -> 46 ; +43 [label="43: Call _fun_getValue \n n$3=_fun_getValue() [line 45]\n " shape="box"] + + + 43 -> 44 ; + 43 -> 45 ; +42 [label="42: + \n " ] + + + 42 -> 48 ; + 42 -> 49 ; +41 [label="41: + \n " ] + + + 41 -> 37 ; +40 [label="40: Skip GotoLabel_stepB \n " color="gray"] + + + 40 -> 59 ; +39 [label="39: Prune (false branch) \n PRUNE(((n$2 > 1) == 0), false); [line 47]\n REMOVE_TEMPS(n$2); [line 47]\n " shape="invhouse"] + + + 39 -> 36 ; +38 [label="38: Prune (true branch) \n PRUNE(((n$2 > 1) != 0), true); [line 47]\n REMOVE_TEMPS(n$2); [line 47]\n APPLY_ABSTRACTION; [line 47]\n " shape="invhouse"] + + + 38 -> 40 ; +37 [label="37: BinaryOperatorStmt: GT \n n$2=_fun_getValue() [line 47]\n " shape="box"] + + + 37 -> 38 ; + 37 -> 39 ; +36 [label="36: + \n " ] + + + 36 -> 35 ; +35 [label="35: Return Stmt \n *&return:int =0 [line 49]\n APPLY_ABSTRACTION; [line 49]\n " shape="box"] + + + 35 -> 28 ; +34 [label="34: Skip GotoLabel_stepA \n " color="gray"] + + + 34 -> 33 ; +33 [label="33: BinaryOperatorStmt: Assign \n *&a:int =2 [line 52]\n NULLIFY(&a,false); [line 52]\n " shape="box"] + + + 33 -> 32 ; +32 [label="32: Return Stmt \n *&return:int =2 [line 53]\n APPLY_ABSTRACTION; [line 53]\n " shape="box"] + + + 32 -> 28 ; +31 [label="31: Skip GotoLabel_exit_step \n " color="gray"] + + + 31 -> 30 ; +30 [label="30: BinaryOperatorStmt: Assign \n *&a:int =3 [line 38]\n NULLIFY(&a,false); [line 38]\n " shape="box"] + + + 30 -> 29 ; +29 [label="29: Return Stmt \n *&return:int =1 [line 57]\n APPLY_ABSTRACTION; [line 57]\n " shape="box"] + + + 29 -> 28 ; +28 [label="28: Exit g2 \n " color=yellow style=filled] + + +27 [label="27: Start g2\nFormals: \nLocals: a:int \n DECLARE_LOCALS(&return,&a); [line 37]\n NULLIFY(&a,false); [line 37]\n " color=yellow style=filled] + + + 27 -> 60 ; +26 [label="26: DeclStmt \n *&a:int =0 [line 27]\n NULLIFY(&a,false); [line 27]\n " shape="box"] + + + 26 -> 23 ; +25 [label="25: Prune (false branch) \n PRUNE(((n$1 > 1) == 0), false); [line 28]\n REMOVE_TEMPS(n$1); [line 28]\n " shape="invhouse"] + + + 25 -> 22 ; +24 [label="24: Prune (true branch) \n PRUNE(((n$1 > 1) != 0), true); [line 28]\n REMOVE_TEMPS(n$1); [line 28]\n " shape="invhouse"] + + + 24 -> 20 ; +23 [label="23: BinaryOperatorStmt: GT \n n$1=_fun_getValue() [line 28]\n " shape="box"] + + + 23 -> 24 ; + 23 -> 25 ; +22 [label="22: + \n " ] + + + 22 -> 21 ; +21 [label="21: Return Stmt \n *&return:int =0 [line 30]\n APPLY_ABSTRACTION; [line 30]\n " shape="box"] + + + 21 -> 17 ; +20 [label="20: Skip GotoLabel_stepB \n " color="gray"] + + + 20 -> 19 ; +19 [label="19: BinaryOperatorStmt: Assign \n *&a:int =1 [line 26]\n NULLIFY(&a,false); [line 26]\n " shape="box"] + + + 19 -> 18 ; +18 [label="18: Return Stmt \n *&return:int =1 [line 34]\n APPLY_ABSTRACTION; [line 34]\n " shape="box"] + + + 18 -> 17 ; +17 [label="17: Exit g1 \n " color=yellow style=filled] + + +16 [label="16: Start g1\nFormals: \nLocals: a:int \n DECLARE_LOCALS(&return,&a); [line 25]\n NULLIFY(&a,false); [line 25]\n " color=yellow style=filled] + + + 16 -> 26 ; +15 [label="15: DeclStmt \n *&a:int =0 [line 14]\n NULLIFY(&a,false); [line 14]\n " shape="box"] + + + 15 -> 12 ; +14 [label="14: Prune (false branch) \n PRUNE(((n$0 > 1) == 0), false); [line 15]\n REMOVE_TEMPS(n$0); [line 15]\n " shape="invhouse"] + + + 14 -> 11 ; +13 [label="13: Prune (true branch) \n PRUNE(((n$0 > 1) != 0), true); [line 15]\n REMOVE_TEMPS(n$0); [line 15]\n APPLY_ABSTRACTION; [line 15]\n " shape="invhouse"] + + + 13 -> 9 ; +12 [label="12: BinaryOperatorStmt: GT \n n$0=_fun_getValue() [line 15]\n " shape="box"] + + + 12 -> 13 ; + 12 -> 14 ; +11 [label="11: + \n " ] + + + 11 -> 10 ; +10 [label="10: Skip GotoLabel_stepB \n APPLY_ABSTRACTION; [line 18]\n " color="gray"] + + + 10 -> 9 ; +9 [label="9: Skip GotoLabel_stepC \n " color="gray"] + + + 9 -> 8 ; +8 [label="8: Skip GotoLabel_stepD \n " color="gray"] + + + 8 -> 7 ; +7 [label="7: BinaryOperatorStmt: Assign \n *&a:int =1 [line 13]\n NULLIFY(&a,false); [line 13]\n " shape="box"] + + + 7 -> 6 ; +6 [label="6: Return Stmt \n *&return:int =1 [line 22]\n APPLY_ABSTRACTION; [line 22]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: Exit g0 \n " color=yellow style=filled] + + +4 [label="4: Start g0\nFormals: \nLocals: a:int \n DECLARE_LOCALS(&return,&a); [line 12]\n NULLIFY(&a,false); [line 12]\n " color=yellow style=filled] + + + 4 -> 15 ; +3 [label="3: Return Stmt \n *&return:int =2 [line 9]\n APPLY_ABSTRACTION; [line 9]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit getValue \n " color=yellow style=filled] + + +1 [label="1: Start getValue\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 8]\n " color=yellow style=filled] + + + 1 -> 3 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/initialization/array_initlistexpr.c b/infer/tests/codetoanalyze/c/frontend/initialization/array_initlistexpr.c new file mode 100644 index 000000000..917157ab2 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/initialization/array_initlistexpr.c @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +int main() { + int z; + int a[2][3] = {{z+1, 2, 3}, {5,6,7}}; +} diff --git a/infer/tests/codetoanalyze/c/frontend/initialization/array_initlistexpr.dot b/infer/tests/codetoanalyze/c/frontend/initialization/array_initlistexpr.dot new file mode 100644 index 000000000..1ce9edd70 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/initialization/array_initlistexpr.dot @@ -0,0 +1,13 @@ +digraph iCFG { +3 [label="3: InitListExp \n n$0=*&z:int [line 8]\n *&a[0][0]:int =(n$0 + 1) [line 8]\n *&a[0][1]:int =2 [line 8]\n *&a[0][2]:int =3 [line 8]\n *&a[1][0]:int =5 [line 8]\n *&a[1][1]:int =6 [line 8]\n *&a[1][2]:int =7 [line 8]\n REMOVE_TEMPS(n$0); [line 8]\n NULLIFY(&z,false); [line 8]\n APPLY_ABSTRACTION; [line 8]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: z:int a:int [2][3] \n DECLARE_LOCALS(&return,&z,&a); [line 6]\n NULLIFY(&a,false); [line 6]\n " color=yellow style=filled] + + + 1 -> 3 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/initialization/struct_initlistexpr.c b/infer/tests/codetoanalyze/c/frontend/initialization/struct_initlistexpr.c new file mode 100644 index 000000000..13e497829 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/initialization/struct_initlistexpr.c @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +typedef struct Point { + int x; + int y; +} Point; + +int foo() { + return 5; +} + +int main() { + struct Point p = {1, foo() + 3}; +} + +int test(Point *p) { + *p = (Point){4, 5}; + return 0; +} + +struct Employee +{ + int ssn; + float salary; + struct date + { + int date; + int month; + int year; + }doj; +}emp1; + +int main2() { + struct Employee e = {12, 3000.50, 12, 12, 2010}; + return e.ssn; +} diff --git a/infer/tests/codetoanalyze/c/frontend/initialization/struct_initlistexpr.dot b/infer/tests/codetoanalyze/c/frontend/initialization/struct_initlistexpr.dot new file mode 100644 index 000000000..95b1b0928 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/initialization/struct_initlistexpr.dot @@ -0,0 +1,54 @@ +digraph iCFG { +14 [label="14: InitListExp \n *&e.ssn:int =12 [line 37]\n *&e.salary:float =3000.500000 [line 37]\n *&e.doj.date:int =12 [line 37]\n *&e.doj.month:int =12 [line 37]\n *&e.doj.year:int =2010 [line 37]\n " shape="box"] + + + 14 -> 13 ; +13 [label="13: Return Stmt \n n$1=*&e.ssn:int [line 38]\n *&return:int =n$1 [line 38]\n REMOVE_TEMPS(n$1); [line 38]\n NULLIFY(&e,false); [line 38]\n APPLY_ABSTRACTION; [line 38]\n " shape="box"] + + + 13 -> 12 ; +12 [label="12: Exit main2 \n " color=yellow style=filled] + + +11 [label="11: Start main2\nFormals: \nLocals: e:struct Employee \n DECLARE_LOCALS(&return,&e); [line 36]\n " color=yellow style=filled] + + + 11 -> 14 ; +10 [label="10: InitListExp \n *&p.x:int =4 [line 20]\n *&p.y:int =5 [line 20]\n " shape="box"] + + + 10 -> 9 ; +9 [label="9: Return Stmt \n *&return:int =0 [line 21]\n APPLY_ABSTRACTION; [line 21]\n " shape="box"] + + + 9 -> 8 ; +8 [label="8: Exit test \n " color=yellow style=filled] + + +7 [label="7: Start test\nFormals: p:Point *\nLocals: \n DECLARE_LOCALS(&return); [line 19]\n NULLIFY(&p,false); [line 19]\n " color=yellow style=filled] + + + 7 -> 10 ; +6 [label="6: InitListExp \n n$0=_fun_foo() [line 16]\n *&p.x:int =1 [line 16]\n *&p.y:int =(n$0 + 3) [line 16]\n REMOVE_TEMPS(n$0); [line 16]\n APPLY_ABSTRACTION; [line 16]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: Exit main \n " color=yellow style=filled] + + +4 [label="4: Start main\nFormals: \nLocals: p:struct Point \n DECLARE_LOCALS(&return,&p); [line 15]\n NULLIFY(&p,false); [line 15]\n " color=yellow style=filled] + + + 4 -> 6 ; +3 [label="3: Return Stmt \n *&return:int =5 [line 12]\n APPLY_ABSTRACTION; [line 12]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit foo \n " color=yellow style=filled] + + +1 [label="1: Start foo\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 11]\n " color=yellow style=filled] + + + 1 -> 3 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/loops/do_while.c b/infer/tests/codetoanalyze/c/frontend/loops/do_while.c new file mode 100644 index 000000000..68604376d --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/loops/do_while.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +int main () { + int a = 10; + int b = 0; + do { + a = 1; + }while( b < 20 ); + + return 0; +} diff --git a/infer/tests/codetoanalyze/c/frontend/loops/do_while.dot b/infer/tests/codetoanalyze/c/frontend/loops/do_while.dot new file mode 100644 index 000000000..21bc4f9b4 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/loops/do_while.dot @@ -0,0 +1,42 @@ +digraph iCFG { +10 [label="10: DeclStmt \n *&a:int =10 [line 7]\n NULLIFY(&a,false); [line 7]\n " shape="box"] + + + 10 -> 9 ; +9 [label="9: DeclStmt \n *&b:int =0 [line 8]\n APPLY_ABSTRACTION; [line 8]\n " shape="box"] + + + 9 -> 4 ; +8 [label="8: BinaryOperatorStmt: Assign \n *&a:int =1 [line 10]\n NULLIFY(&a,false); [line 10]\n " shape="box"] + + + 8 -> 5 ; +7 [label="7: Prune (false branch) \n PRUNE(((n$0 < 20) == 0), false); [line 11]\n REMOVE_TEMPS(n$0); [line 11]\n " shape="invhouse"] + + + 7 -> 3 ; +6 [label="6: Prune (true branch) \n PRUNE(((n$0 < 20) != 0), true); [line 11]\n REMOVE_TEMPS(n$0); [line 11]\n APPLY_ABSTRACTION; [line 11]\n " shape="invhouse"] + + + 6 -> 4 ; +5 [label="5: BinaryOperatorStmt: LT \n n$0=*&b:int [line 11]\n " shape="box"] + + + 5 -> 6 ; + 5 -> 7 ; +4 [label="4: + \n " ] + + + 4 -> 8 ; +3 [label="3: Return Stmt \n NULLIFY(&b,false); [line 13]\n *&return:int =0 [line 13]\n APPLY_ABSTRACTION; [line 13]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: a:int b:int \n DECLARE_LOCALS(&return,&a,&b); [line 6]\n NULLIFY(&a,false); [line 6]\n NULLIFY(&b,false); [line 6]\n " color=yellow style=filled] + + + 1 -> 10 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/loops/do_while_condition_side_effects.c b/infer/tests/codetoanalyze/c/frontend/loops/do_while_condition_side_effects.c new file mode 100644 index 000000000..28fec8360 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/loops/do_while_condition_side_effects.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +int main () { + int a = 10; + int b = 0; + do { + a = 1; + }while( (b = 40) ); + + return 0; +} diff --git a/infer/tests/codetoanalyze/c/frontend/loops/do_while_condition_side_effects.dot b/infer/tests/codetoanalyze/c/frontend/loops/do_while_condition_side_effects.dot new file mode 100644 index 000000000..0ecf88488 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/loops/do_while_condition_side_effects.dot @@ -0,0 +1,42 @@ +digraph iCFG { +10 [label="10: DeclStmt \n *&a:int =10 [line 7]\n NULLIFY(&a,false); [line 7]\n " shape="box"] + + + 10 -> 9 ; +9 [label="9: DeclStmt \n *&b:int =0 [line 8]\n NULLIFY(&b,false); [line 8]\n APPLY_ABSTRACTION; [line 8]\n " shape="box"] + + + 9 -> 4 ; +8 [label="8: BinaryOperatorStmt: Assign \n *&a:int =1 [line 10]\n NULLIFY(&a,false); [line 10]\n " shape="box"] + + + 8 -> 5 ; +7 [label="7: Prune (false branch) \n PRUNE((n$0 == 0), false); [line 11]\n REMOVE_TEMPS(n$0); [line 11]\n " shape="invhouse"] + + + 7 -> 3 ; +6 [label="6: Prune (true branch) \n PRUNE((n$0 != 0), true); [line 11]\n REMOVE_TEMPS(n$0); [line 11]\n APPLY_ABSTRACTION; [line 11]\n " shape="invhouse"] + + + 6 -> 4 ; +5 [label="5: BinaryOperatorStmt: Assign \n *&b:int =40 [line 11]\n n$0=*&b:int [line 11]\n NULLIFY(&b,false); [line 11]\n " shape="box"] + + + 5 -> 6 ; + 5 -> 7 ; +4 [label="4: + \n " ] + + + 4 -> 8 ; +3 [label="3: Return Stmt \n *&return:int =0 [line 13]\n APPLY_ABSTRACTION; [line 13]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: a:int b:int \n DECLARE_LOCALS(&return,&a,&b); [line 6]\n NULLIFY(&a,false); [line 6]\n NULLIFY(&b,false); [line 6]\n " color=yellow style=filled] + + + 1 -> 10 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/loops/do_while_nested.c b/infer/tests/codetoanalyze/c/frontend/loops/do_while_nested.c new file mode 100644 index 000000000..7dd758fc1 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/loops/do_while_nested.c @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +int main () { + int a = 10; + int b = 0; + do { + a = 1; + do { + a = 2; + }while( b < 30 ); + }while( b < 20 ); + + return 0; +} diff --git a/infer/tests/codetoanalyze/c/frontend/loops/do_while_nested.dot b/infer/tests/codetoanalyze/c/frontend/loops/do_while_nested.dot new file mode 100644 index 000000000..4619e7499 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/loops/do_while_nested.dot @@ -0,0 +1,63 @@ +digraph iCFG { +15 [label="15: DeclStmt \n *&a:int =10 [line 7]\n NULLIFY(&a,false); [line 7]\n " shape="box"] + + + 15 -> 14 ; +14 [label="14: DeclStmt \n *&b:int =0 [line 8]\n APPLY_ABSTRACTION; [line 8]\n " shape="box"] + + + 14 -> 4 ; +13 [label="13: BinaryOperatorStmt: Assign \n *&a:int =1 [line 10]\n NULLIFY(&a,false); [line 10]\n APPLY_ABSTRACTION; [line 10]\n " shape="box"] + + + 13 -> 8 ; +12 [label="12: BinaryOperatorStmt: Assign \n *&a:int =2 [line 12]\n NULLIFY(&a,false); [line 12]\n " shape="box"] + + + 12 -> 9 ; +11 [label="11: Prune (false branch) \n PRUNE(((n$1 < 30) == 0), false); [line 13]\n REMOVE_TEMPS(n$1); [line 13]\n " shape="invhouse"] + + + 11 -> 5 ; +10 [label="10: Prune (true branch) \n PRUNE(((n$1 < 30) != 0), true); [line 13]\n REMOVE_TEMPS(n$1); [line 13]\n APPLY_ABSTRACTION; [line 13]\n " shape="invhouse"] + + + 10 -> 8 ; +9 [label="9: BinaryOperatorStmt: LT \n n$1=*&b:int [line 13]\n " shape="box"] + + + 9 -> 10 ; + 9 -> 11 ; +8 [label="8: + \n " ] + + + 8 -> 12 ; +7 [label="7: Prune (false branch) \n PRUNE(((n$0 < 20) == 0), false); [line 14]\n REMOVE_TEMPS(n$0); [line 14]\n " shape="invhouse"] + + + 7 -> 3 ; +6 [label="6: Prune (true branch) \n PRUNE(((n$0 < 20) != 0), true); [line 14]\n REMOVE_TEMPS(n$0); [line 14]\n APPLY_ABSTRACTION; [line 14]\n " shape="invhouse"] + + + 6 -> 4 ; +5 [label="5: BinaryOperatorStmt: LT \n n$0=*&b:int [line 14]\n " shape="box"] + + + 5 -> 6 ; + 5 -> 7 ; +4 [label="4: + \n " ] + + + 4 -> 13 ; +3 [label="3: Return Stmt \n NULLIFY(&b,false); [line 16]\n *&return:int =0 [line 16]\n APPLY_ABSTRACTION; [line 16]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: a:int b:int \n DECLARE_LOCALS(&return,&a,&b); [line 6]\n NULLIFY(&a,false); [line 6]\n NULLIFY(&b,false); [line 6]\n " color=yellow style=filled] + + + 1 -> 15 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/loops/for_condition_side_effects.c b/infer/tests/codetoanalyze/c/frontend/loops/for_condition_side_effects.c new file mode 100644 index 000000000..965aff4d0 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/loops/for_condition_side_effects.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +int main() { + int j = 0; + int i = 0; + for (int b=3; (b=10); i++) { + j += j; + } + return 0; +} diff --git a/infer/tests/codetoanalyze/c/frontend/loops/for_condition_side_effects.dot b/infer/tests/codetoanalyze/c/frontend/loops/for_condition_side_effects.dot new file mode 100644 index 000000000..89912ac44 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/loops/for_condition_side_effects.dot @@ -0,0 +1,50 @@ +digraph iCFG { +12 [label="12: DeclStmt \n *&j:int =0 [line 7]\n " shape="box"] + + + 12 -> 11 ; +11 [label="11: DeclStmt \n *&i:int =0 [line 8]\n " shape="box"] + + + 11 -> 5 ; +10 [label="10: ComppoundAssignStmt \n n$2=*&j:int [line 10]\n n$3=*&j:int [line 10]\n *&j:int =(n$3 + n$2) [line 10]\n REMOVE_TEMPS(n$2,n$3); [line 10]\n " shape="box"] + + + 10 -> 6 ; +9 [label="9: Prune (false branch) \n PRUNE((n$1 == 0), false); [line 9]\n REMOVE_TEMPS(n$1); [line 9]\n " shape="invhouse"] + + + 9 -> 3 ; +8 [label="8: Prune (true branch) \n PRUNE((n$1 != 0), true); [line 9]\n REMOVE_TEMPS(n$1); [line 9]\n " shape="invhouse"] + + + 8 -> 10 ; +7 [label="7: BinaryOperatorStmt: Assign \n *&b:int =10 [line 9]\n n$1=*&b:int [line 9]\n NULLIFY(&b,false); [line 9]\n " shape="box"] + + + 7 -> 8 ; + 7 -> 9 ; +6 [label="6: UnaryOperator \n n$0=*&i:int [line 9]\n *&i:int =(n$0 + 1) [line 9]\n REMOVE_TEMPS(n$0); [line 9]\n APPLY_ABSTRACTION; [line 9]\n " shape="box"] + + + 6 -> 4 ; +5 [label="5: DeclStmt \n *&b:int =3 [line 9]\n NULLIFY(&b,false); [line 9]\n APPLY_ABSTRACTION; [line 9]\n " shape="box"] + + + 5 -> 4 ; +4 [label="4: + \n " ] + + + 4 -> 7 ; +3 [label="3: Return Stmt \n NULLIFY(&i,false); [line 12]\n NULLIFY(&j,false); [line 12]\n *&return:int =0 [line 12]\n APPLY_ABSTRACTION; [line 12]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: j:int i:int b:int \n DECLARE_LOCALS(&return,&j,&i,&b); [line 6]\n NULLIFY(&b,false); [line 6]\n NULLIFY(&i,false); [line 6]\n NULLIFY(&j,false); [line 6]\n " color=yellow style=filled] + + + 1 -> 12 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/loops/for_nested.c b/infer/tests/codetoanalyze/c/frontend/loops/for_nested.c new file mode 100644 index 000000000..c6d8085a1 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/loops/for_nested.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +int main() { + int k = 0; + for (int i=0; i<10; i++) { + for (int j=0; j<10; j++) { + k = k + i; + } + } + return k; +} diff --git a/infer/tests/codetoanalyze/c/frontend/loops/for_nested.dot b/infer/tests/codetoanalyze/c/frontend/loops/for_nested.dot new file mode 100644 index 000000000..cc71f45f1 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/loops/for_nested.dot @@ -0,0 +1,71 @@ +digraph iCFG { +17 [label="17: DeclStmt \n *&k:int =0 [line 7]\n " shape="box"] + + + 17 -> 5 ; +16 [label="16: BinaryOperatorStmt: Assign \n n$5=*&k:int [line 10]\n n$6=*&i:int [line 10]\n *&k:int =(n$5 + n$6) [line 10]\n REMOVE_TEMPS(n$5,n$6); [line 10]\n " shape="box"] + + + 16 -> 12 ; +15 [label="15: Prune (false branch) \n PRUNE(((n$4 < 10) == 0), false); [line 9]\n REMOVE_TEMPS(n$4); [line 9]\n " shape="invhouse"] + + + 15 -> 6 ; +14 [label="14: Prune (true branch) \n PRUNE(((n$4 < 10) != 0), true); [line 9]\n REMOVE_TEMPS(n$4); [line 9]\n " shape="invhouse"] + + + 14 -> 16 ; +13 [label="13: BinaryOperatorStmt: LT \n n$4=*&j:int [line 9]\n " shape="box"] + + + 13 -> 14 ; + 13 -> 15 ; +12 [label="12: UnaryOperator \n n$3=*&j:int [line 9]\n *&j:int =(n$3 + 1) [line 9]\n REMOVE_TEMPS(n$3); [line 9]\n APPLY_ABSTRACTION; [line 9]\n " shape="box"] + + + 12 -> 10 ; +11 [label="11: DeclStmt \n *&j:int =0 [line 9]\n APPLY_ABSTRACTION; [line 9]\n " shape="box"] + + + 11 -> 10 ; +10 [label="10: + \n " ] + + + 10 -> 13 ; +9 [label="9: Prune (false branch) \n PRUNE(((n$2 < 10) == 0), false); [line 8]\n REMOVE_TEMPS(n$2); [line 8]\n " shape="invhouse"] + + + 9 -> 3 ; +8 [label="8: Prune (true branch) \n PRUNE(((n$2 < 10) != 0), true); [line 8]\n REMOVE_TEMPS(n$2); [line 8]\n " shape="invhouse"] + + + 8 -> 11 ; +7 [label="7: BinaryOperatorStmt: LT \n n$2=*&i:int [line 8]\n " shape="box"] + + + 7 -> 8 ; + 7 -> 9 ; +6 [label="6: UnaryOperator \n NULLIFY(&j,false); [line 8]\n n$1=*&i:int [line 8]\n *&i:int =(n$1 + 1) [line 8]\n REMOVE_TEMPS(n$1); [line 8]\n APPLY_ABSTRACTION; [line 8]\n " shape="box"] + + + 6 -> 4 ; +5 [label="5: DeclStmt \n *&i:int =0 [line 8]\n APPLY_ABSTRACTION; [line 8]\n " shape="box"] + + + 5 -> 4 ; +4 [label="4: + \n " ] + + + 4 -> 7 ; +3 [label="3: Return Stmt \n NULLIFY(&i,false); [line 13]\n n$0=*&k:int [line 13]\n *&return:int =n$0 [line 13]\n REMOVE_TEMPS(n$0); [line 13]\n NULLIFY(&k,false); [line 13]\n APPLY_ABSTRACTION; [line 13]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: k:int i:int j:int \n DECLARE_LOCALS(&return,&k,&i,&j); [line 6]\n NULLIFY(&i,false); [line 6]\n NULLIFY(&j,false); [line 6]\n NULLIFY(&k,false); [line 6]\n " color=yellow style=filled] + + + 1 -> 17 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/loops/for_no_condition.c b/infer/tests/codetoanalyze/c/frontend/loops/for_no_condition.c new file mode 100644 index 000000000..f2cb58e17 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/loops/for_no_condition.c @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +int main() { + int j = 0; + for (int b=0;; b++) { + j += j; + } + return 0; +} diff --git a/infer/tests/codetoanalyze/c/frontend/loops/for_no_condition.dot b/infer/tests/codetoanalyze/c/frontend/loops/for_no_condition.dot new file mode 100644 index 000000000..d588d15c1 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/loops/for_no_condition.dot @@ -0,0 +1,42 @@ +digraph iCFG { +10 [label="10: DeclStmt \n *&j:int =0 [line 7]\n " shape="box"] + + + 10 -> 5 ; +9 [label="9: ComppoundAssignStmt \n n$1=*&j:int [line 9]\n n$2=*&j:int [line 9]\n *&j:int =(n$2 + n$1) [line 9]\n REMOVE_TEMPS(n$1,n$2); [line 9]\n " shape="box"] + + + 9 -> 6 ; +8 [label="8: Prune (false branch) \n PRUNE((1 == 0), false); [line 8]\n " shape="invhouse"] + + + 8 -> 3 ; +7 [label="7: Prune (true branch) \n PRUNE((1 != 0), true); [line 8]\n " shape="invhouse"] + + + 7 -> 9 ; +6 [label="6: UnaryOperator \n n$0=*&b:int [line 8]\n *&b:int =(n$0 + 1) [line 8]\n REMOVE_TEMPS(n$0); [line 8]\n APPLY_ABSTRACTION; [line 8]\n " shape="box"] + + + 6 -> 4 ; +5 [label="5: DeclStmt \n *&b:int =0 [line 8]\n APPLY_ABSTRACTION; [line 8]\n " shape="box"] + + + 5 -> 4 ; +4 [label="4: + \n " ] + + + 4 -> 7 ; + 4 -> 8 ; +3 [label="3: Return Stmt \n NULLIFY(&b,false); [line 11]\n NULLIFY(&j,false); [line 11]\n *&return:int =0 [line 11]\n APPLY_ABSTRACTION; [line 11]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: j:int b:int \n DECLARE_LOCALS(&return,&j,&b); [line 6]\n NULLIFY(&b,false); [line 6]\n NULLIFY(&j,false); [line 6]\n " color=yellow style=filled] + + + 1 -> 10 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/loops/for_no_condition_incr.c b/infer/tests/codetoanalyze/c/frontend/loops/for_no_condition_incr.c new file mode 100644 index 000000000..17beccc11 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/loops/for_no_condition_incr.c @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +int main() { + int j = 0; + for (int b=0;;) { + j += j; + } + return 0; +} diff --git a/infer/tests/codetoanalyze/c/frontend/loops/for_no_condition_incr.dot b/infer/tests/codetoanalyze/c/frontend/loops/for_no_condition_incr.dot new file mode 100644 index 000000000..10ce43081 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/loops/for_no_condition_incr.dot @@ -0,0 +1,38 @@ +digraph iCFG { +9 [label="9: DeclStmt \n *&j:int =0 [line 7]\n " shape="box"] + + + 9 -> 5 ; +8 [label="8: ComppoundAssignStmt \n n$0=*&j:int [line 9]\n n$1=*&j:int [line 9]\n *&j:int =(n$1 + n$0) [line 9]\n REMOVE_TEMPS(n$0,n$1); [line 9]\n APPLY_ABSTRACTION; [line 9]\n " shape="box"] + + + 8 -> 4 ; +7 [label="7: Prune (false branch) \n PRUNE((1 == 0), false); [line 8]\n " shape="invhouse"] + + + 7 -> 3 ; +6 [label="6: Prune (true branch) \n PRUNE((1 != 0), true); [line 8]\n " shape="invhouse"] + + + 6 -> 8 ; +5 [label="5: DeclStmt \n *&b:int =0 [line 8]\n NULLIFY(&b,false); [line 8]\n APPLY_ABSTRACTION; [line 8]\n " shape="box"] + + + 5 -> 4 ; +4 [label="4: + \n " ] + + + 4 -> 6 ; + 4 -> 7 ; +3 [label="3: Return Stmt \n NULLIFY(&j,false); [line 11]\n *&return:int =0 [line 11]\n APPLY_ABSTRACTION; [line 11]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: j:int b:int \n DECLARE_LOCALS(&return,&j,&b); [line 6]\n NULLIFY(&b,false); [line 6]\n NULLIFY(&j,false); [line 6]\n " color=yellow style=filled] + + + 1 -> 9 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/loops/for_no_condition_incr_body.c b/infer/tests/codetoanalyze/c/frontend/loops/for_no_condition_incr_body.c new file mode 100644 index 000000000..2d209699f --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/loops/for_no_condition_incr_body.c @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +int main() { + int d = 0; + for (d=0;;) { + } + return 0; +} diff --git a/infer/tests/codetoanalyze/c/frontend/loops/for_no_condition_incr_body.dot b/infer/tests/codetoanalyze/c/frontend/loops/for_no_condition_incr_body.dot new file mode 100644 index 000000000..31b987b28 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/loops/for_no_condition_incr_body.dot @@ -0,0 +1,34 @@ +digraph iCFG { +8 [label="8: DeclStmt \n *&d:int =0 [line 7]\n NULLIFY(&d,false); [line 7]\n " shape="box"] + + + 8 -> 5 ; +7 [label="7: Prune (false branch) \n PRUNE((1 == 0), false); [line 8]\n " shape="invhouse"] + + + 7 -> 3 ; +6 [label="6: Prune (true branch) \n PRUNE((1 != 0), true); [line 8]\n APPLY_ABSTRACTION; [line 8]\n " shape="invhouse"] + + + 6 -> 4 ; +5 [label="5: BinaryOperatorStmt: Assign \n *&d:int =0 [line 8]\n NULLIFY(&d,false); [line 8]\n APPLY_ABSTRACTION; [line 8]\n " shape="box"] + + + 5 -> 4 ; +4 [label="4: + \n " ] + + + 4 -> 6 ; + 4 -> 7 ; +3 [label="3: Return Stmt \n *&return:int =0 [line 10]\n APPLY_ABSTRACTION; [line 10]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: d:int \n DECLARE_LOCALS(&return,&d); [line 6]\n NULLIFY(&d,false); [line 6]\n " color=yellow style=filled] + + + 1 -> 8 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/loops/for_only_body.c b/infer/tests/codetoanalyze/c/frontend/loops/for_only_body.c new file mode 100644 index 000000000..f226df95a --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/loops/for_only_body.c @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +int main() { + int i = 0; + for (;;) { + i++; + } + return 0; +} diff --git a/infer/tests/codetoanalyze/c/frontend/loops/for_only_body.dot b/infer/tests/codetoanalyze/c/frontend/loops/for_only_body.dot new file mode 100644 index 000000000..dc29c9f85 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/loops/for_only_body.dot @@ -0,0 +1,34 @@ +digraph iCFG { +8 [label="8: DeclStmt \n *&i:int =0 [line 7]\n APPLY_ABSTRACTION; [line 7]\n " shape="box"] + + + 8 -> 4 ; +7 [label="7: UnaryOperator \n n$0=*&i:int [line 9]\n *&i:int =(n$0 + 1) [line 9]\n REMOVE_TEMPS(n$0); [line 9]\n APPLY_ABSTRACTION; [line 9]\n " shape="box"] + + + 7 -> 4 ; +6 [label="6: Prune (false branch) \n PRUNE((1 == 0), false); [line 8]\n " shape="invhouse"] + + + 6 -> 3 ; +5 [label="5: Prune (true branch) \n PRUNE((1 != 0), true); [line 8]\n " shape="invhouse"] + + + 5 -> 7 ; +4 [label="4: + \n " ] + + + 4 -> 5 ; + 4 -> 6 ; +3 [label="3: Return Stmt \n NULLIFY(&i,false); [line 11]\n *&return:int =0 [line 11]\n APPLY_ABSTRACTION; [line 11]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: i:int \n DECLARE_LOCALS(&return,&i); [line 6]\n NULLIFY(&i,false); [line 6]\n " color=yellow style=filled] + + + 1 -> 8 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/loops/for_simple.c b/infer/tests/codetoanalyze/c/frontend/loops/for_simple.c new file mode 100644 index 000000000..6afdd1b3e --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/loops/for_simple.c @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +int main() { + int j = 0; + for (int i=0; i<10; i++) { + j += j; + } + return 0; +} diff --git a/infer/tests/codetoanalyze/c/frontend/loops/for_simple.dot b/infer/tests/codetoanalyze/c/frontend/loops/for_simple.dot new file mode 100644 index 000000000..9d2fb9a46 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/loops/for_simple.dot @@ -0,0 +1,46 @@ +digraph iCFG { +11 [label="11: DeclStmt \n *&j:int =0 [line 7]\n " shape="box"] + + + 11 -> 5 ; +10 [label="10: ComppoundAssignStmt \n n$2=*&j:int [line 9]\n n$3=*&j:int [line 9]\n *&j:int =(n$3 + n$2) [line 9]\n REMOVE_TEMPS(n$2,n$3); [line 9]\n " shape="box"] + + + 10 -> 6 ; +9 [label="9: Prune (false branch) \n PRUNE(((n$1 < 10) == 0), false); [line 8]\n REMOVE_TEMPS(n$1); [line 8]\n " shape="invhouse"] + + + 9 -> 3 ; +8 [label="8: Prune (true branch) \n PRUNE(((n$1 < 10) != 0), true); [line 8]\n REMOVE_TEMPS(n$1); [line 8]\n " shape="invhouse"] + + + 8 -> 10 ; +7 [label="7: BinaryOperatorStmt: LT \n n$1=*&i:int [line 8]\n " shape="box"] + + + 7 -> 8 ; + 7 -> 9 ; +6 [label="6: UnaryOperator \n n$0=*&i:int [line 8]\n *&i:int =(n$0 + 1) [line 8]\n REMOVE_TEMPS(n$0); [line 8]\n APPLY_ABSTRACTION; [line 8]\n " shape="box"] + + + 6 -> 4 ; +5 [label="5: DeclStmt \n *&i:int =0 [line 8]\n APPLY_ABSTRACTION; [line 8]\n " shape="box"] + + + 5 -> 4 ; +4 [label="4: + \n " ] + + + 4 -> 7 ; +3 [label="3: Return Stmt \n NULLIFY(&i,false); [line 11]\n NULLIFY(&j,false); [line 11]\n *&return:int =0 [line 11]\n APPLY_ABSTRACTION; [line 11]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: j:int i:int \n DECLARE_LOCALS(&return,&j,&i); [line 6]\n NULLIFY(&i,false); [line 6]\n NULLIFY(&j,false); [line 6]\n " color=yellow style=filled] + + + 1 -> 11 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/loops/for_while_nested.c b/infer/tests/codetoanalyze/c/frontend/loops/for_while_nested.c new file mode 100644 index 000000000..3506e91af --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/loops/for_while_nested.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +int main() { + int k = 0; + for (int i=0; i<10; i++) { + while(k < 10) { + k++; + } + } + return k; +} diff --git a/infer/tests/codetoanalyze/c/frontend/loops/for_while_nested.dot b/infer/tests/codetoanalyze/c/frontend/loops/for_while_nested.dot new file mode 100644 index 000000000..ee2cb6b2e --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/loops/for_while_nested.dot @@ -0,0 +1,63 @@ +digraph iCFG { +15 [label="15: DeclStmt \n *&k:int =0 [line 7]\n " shape="box"] + + + 15 -> 5 ; +14 [label="14: UnaryOperator \n n$4=*&k:int [line 10]\n *&k:int =(n$4 + 1) [line 10]\n REMOVE_TEMPS(n$4); [line 10]\n APPLY_ABSTRACTION; [line 10]\n " shape="box"] + + + 14 -> 10 ; +13 [label="13: Prune (false branch) \n PRUNE(((n$3 < 10) == 0), false); [line 9]\n REMOVE_TEMPS(n$3); [line 9]\n " shape="invhouse"] + + + 13 -> 6 ; +12 [label="12: Prune (true branch) \n PRUNE(((n$3 < 10) != 0), true); [line 9]\n REMOVE_TEMPS(n$3); [line 9]\n " shape="invhouse"] + + + 12 -> 14 ; +11 [label="11: BinaryOperatorStmt: LT \n n$3=*&k:int [line 9]\n " shape="box"] + + + 11 -> 12 ; + 11 -> 13 ; +10 [label="10: + \n " ] + + + 10 -> 11 ; +9 [label="9: Prune (false branch) \n PRUNE(((n$2 < 10) == 0), false); [line 8]\n REMOVE_TEMPS(n$2); [line 8]\n " shape="invhouse"] + + + 9 -> 3 ; +8 [label="8: Prune (true branch) \n PRUNE(((n$2 < 10) != 0), true); [line 8]\n REMOVE_TEMPS(n$2); [line 8]\n APPLY_ABSTRACTION; [line 8]\n " shape="invhouse"] + + + 8 -> 10 ; +7 [label="7: BinaryOperatorStmt: LT \n n$2=*&i:int [line 8]\n " shape="box"] + + + 7 -> 8 ; + 7 -> 9 ; +6 [label="6: UnaryOperator \n n$1=*&i:int [line 8]\n *&i:int =(n$1 + 1) [line 8]\n REMOVE_TEMPS(n$1); [line 8]\n APPLY_ABSTRACTION; [line 8]\n " shape="box"] + + + 6 -> 4 ; +5 [label="5: DeclStmt \n *&i:int =0 [line 8]\n APPLY_ABSTRACTION; [line 8]\n " shape="box"] + + + 5 -> 4 ; +4 [label="4: + \n " ] + + + 4 -> 7 ; +3 [label="3: Return Stmt \n NULLIFY(&i,false); [line 13]\n n$0=*&k:int [line 13]\n *&return:int =n$0 [line 13]\n REMOVE_TEMPS(n$0); [line 13]\n NULLIFY(&k,false); [line 13]\n APPLY_ABSTRACTION; [line 13]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: k:int i:int \n DECLARE_LOCALS(&return,&k,&i); [line 6]\n NULLIFY(&i,false); [line 6]\n NULLIFY(&k,false); [line 6]\n " color=yellow style=filled] + + + 1 -> 15 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/loops/while.c b/infer/tests/codetoanalyze/c/frontend/loops/while.c new file mode 100644 index 000000000..59493ee60 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/loops/while.c @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +int main() { + int i = 0; + while (i <= 10) { + i++; + } + return 0; +} diff --git a/infer/tests/codetoanalyze/c/frontend/loops/while.dot b/infer/tests/codetoanalyze/c/frontend/loops/while.dot new file mode 100644 index 000000000..8b77cdc22 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/loops/while.dot @@ -0,0 +1,38 @@ +digraph iCFG { +9 [label="9: DeclStmt \n *&i:int =0 [line 7]\n APPLY_ABSTRACTION; [line 7]\n " shape="box"] + + + 9 -> 4 ; +8 [label="8: UnaryOperator \n n$1=*&i:int [line 9]\n *&i:int =(n$1 + 1) [line 9]\n REMOVE_TEMPS(n$1); [line 9]\n APPLY_ABSTRACTION; [line 9]\n " shape="box"] + + + 8 -> 4 ; +7 [label="7: Prune (false branch) \n PRUNE(((n$0 <= 10) == 0), false); [line 8]\n REMOVE_TEMPS(n$0); [line 8]\n " shape="invhouse"] + + + 7 -> 3 ; +6 [label="6: Prune (true branch) \n PRUNE(((n$0 <= 10) != 0), true); [line 8]\n REMOVE_TEMPS(n$0); [line 8]\n " shape="invhouse"] + + + 6 -> 8 ; +5 [label="5: BinaryOperatorStmt: LE \n n$0=*&i:int [line 8]\n " shape="box"] + + + 5 -> 6 ; + 5 -> 7 ; +4 [label="4: + \n " ] + + + 4 -> 5 ; +3 [label="3: Return Stmt \n NULLIFY(&i,false); [line 11]\n *&return:int =0 [line 11]\n APPLY_ABSTRACTION; [line 11]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: i:int \n DECLARE_LOCALS(&return,&i); [line 6]\n NULLIFY(&i,false); [line 6]\n " color=yellow style=filled] + + + 1 -> 9 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/loops/while_condition_side_effects.c b/infer/tests/codetoanalyze/c/frontend/loops/while_condition_side_effects.c new file mode 100644 index 000000000..6ca219edd --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/loops/while_condition_side_effects.c @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +int main() { + int i = 0; + while ((i = 10)) { + i++; + } + return 0; +} diff --git a/infer/tests/codetoanalyze/c/frontend/loops/while_condition_side_effects.dot b/infer/tests/codetoanalyze/c/frontend/loops/while_condition_side_effects.dot new file mode 100644 index 000000000..9a6df72e8 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/loops/while_condition_side_effects.dot @@ -0,0 +1,38 @@ +digraph iCFG { +9 [label="9: DeclStmt \n *&i:int =0 [line 7]\n NULLIFY(&i,false); [line 7]\n APPLY_ABSTRACTION; [line 7]\n " shape="box"] + + + 9 -> 4 ; +8 [label="8: UnaryOperator \n n$1=*&i:int [line 9]\n *&i:int =(n$1 + 1) [line 9]\n REMOVE_TEMPS(n$1); [line 9]\n NULLIFY(&i,false); [line 9]\n APPLY_ABSTRACTION; [line 9]\n " shape="box"] + + + 8 -> 4 ; +7 [label="7: Prune (false branch) \n PRUNE((n$0 == 0), false); [line 8]\n REMOVE_TEMPS(n$0); [line 8]\n " shape="invhouse"] + + + 7 -> 3 ; +6 [label="6: Prune (true branch) \n PRUNE((n$0 != 0), true); [line 8]\n REMOVE_TEMPS(n$0); [line 8]\n " shape="invhouse"] + + + 6 -> 8 ; +5 [label="5: BinaryOperatorStmt: Assign \n *&i:int =10 [line 8]\n n$0=*&i:int [line 8]\n " shape="box"] + + + 5 -> 6 ; + 5 -> 7 ; +4 [label="4: + \n " ] + + + 4 -> 5 ; +3 [label="3: Return Stmt \n NULLIFY(&i,false); [line 11]\n *&return:int =0 [line 11]\n APPLY_ABSTRACTION; [line 11]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: i:int \n DECLARE_LOCALS(&return,&i); [line 6]\n NULLIFY(&i,false); [line 6]\n " color=yellow style=filled] + + + 1 -> 9 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/loops/while_nested.c b/infer/tests/codetoanalyze/c/frontend/loops/while_nested.c new file mode 100644 index 000000000..9ece03a4b --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/loops/while_nested.c @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +int main() { + int i = 0; + int k = 0; + while (i <= 10) { + while (k <= 5) { + k++; + } + i++; + } + return 0; +} diff --git a/infer/tests/codetoanalyze/c/frontend/loops/while_nested.dot b/infer/tests/codetoanalyze/c/frontend/loops/while_nested.dot new file mode 100644 index 000000000..ab77b43f7 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/loops/while_nested.dot @@ -0,0 +1,63 @@ +digraph iCFG { +15 [label="15: DeclStmt \n *&i:int =0 [line 7]\n " shape="box"] + + + 15 -> 14 ; +14 [label="14: DeclStmt \n *&k:int =0 [line 8]\n APPLY_ABSTRACTION; [line 8]\n " shape="box"] + + + 14 -> 4 ; +13 [label="13: UnaryOperator \n n$3=*&k:int [line 11]\n *&k:int =(n$3 + 1) [line 11]\n REMOVE_TEMPS(n$3); [line 11]\n APPLY_ABSTRACTION; [line 11]\n " shape="box"] + + + 13 -> 9 ; +12 [label="12: Prune (false branch) \n PRUNE(((n$2 <= 5) == 0), false); [line 10]\n REMOVE_TEMPS(n$2); [line 10]\n " shape="invhouse"] + + + 12 -> 8 ; +11 [label="11: Prune (true branch) \n PRUNE(((n$2 <= 5) != 0), true); [line 10]\n REMOVE_TEMPS(n$2); [line 10]\n " shape="invhouse"] + + + 11 -> 13 ; +10 [label="10: BinaryOperatorStmt: LE \n n$2=*&k:int [line 10]\n " shape="box"] + + + 10 -> 11 ; + 10 -> 12 ; +9 [label="9: + \n " ] + + + 9 -> 10 ; +8 [label="8: UnaryOperator \n n$1=*&i:int [line 13]\n *&i:int =(n$1 + 1) [line 13]\n REMOVE_TEMPS(n$1); [line 13]\n APPLY_ABSTRACTION; [line 13]\n " shape="box"] + + + 8 -> 4 ; +7 [label="7: Prune (false branch) \n PRUNE(((n$0 <= 10) == 0), false); [line 9]\n REMOVE_TEMPS(n$0); [line 9]\n " shape="invhouse"] + + + 7 -> 3 ; +6 [label="6: Prune (true branch) \n PRUNE(((n$0 <= 10) != 0), true); [line 9]\n REMOVE_TEMPS(n$0); [line 9]\n APPLY_ABSTRACTION; [line 9]\n " shape="invhouse"] + + + 6 -> 9 ; +5 [label="5: BinaryOperatorStmt: LE \n n$0=*&i:int [line 9]\n " shape="box"] + + + 5 -> 6 ; + 5 -> 7 ; +4 [label="4: + \n " ] + + + 4 -> 5 ; +3 [label="3: Return Stmt \n NULLIFY(&i,false); [line 15]\n NULLIFY(&k,false); [line 15]\n *&return:int =0 [line 15]\n APPLY_ABSTRACTION; [line 15]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: i:int k:int \n DECLARE_LOCALS(&return,&i,&k); [line 6]\n NULLIFY(&i,false); [line 6]\n NULLIFY(&k,false); [line 6]\n " color=yellow style=filled] + + + 1 -> 15 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/loops/while_no_body.c b/infer/tests/codetoanalyze/c/frontend/loops/while_no_body.c new file mode 100644 index 000000000..074aaa404 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/loops/while_no_body.c @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +int main(){ + while(1) {} + return 0; +} diff --git a/infer/tests/codetoanalyze/c/frontend/loops/while_no_body.dot b/infer/tests/codetoanalyze/c/frontend/loops/while_no_body.dot new file mode 100644 index 000000000..8ac119b00 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/loops/while_no_body.dot @@ -0,0 +1,26 @@ +digraph iCFG { +6 [label="6: Prune (false branch) \n PRUNE((1 == 0), false); [line 7]\n " shape="invhouse"] + + + 6 -> 3 ; +5 [label="5: Prune (true branch) \n PRUNE((1 != 0), true); [line 7]\n APPLY_ABSTRACTION; [line 7]\n " shape="invhouse"] + + + 5 -> 4 ; +4 [label="4: + \n " ] + + + 4 -> 5 ; + 4 -> 6 ; +3 [label="3: Return Stmt \n *&return:int =0 [line 8]\n APPLY_ABSTRACTION; [line 8]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 6]\n " color=yellow style=filled] + + + 1 -> 4 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/loops/while_with_continue_and_break.c b/infer/tests/codetoanalyze/c/frontend/loops/while_with_continue_and_break.c new file mode 100644 index 000000000..cf089a33b --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/loops/while_with_continue_and_break.c @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +int main() { + int x = 0; + while(1) { + while(2) { + x += 1; + if (x > 5) { + break; + } + } + if (x == 2) { + continue; + } + } + return 0; +} diff --git a/infer/tests/codetoanalyze/c/frontend/loops/while_with_continue_and_break.dot b/infer/tests/codetoanalyze/c/frontend/loops/while_with_continue_and_break.dot new file mode 100644 index 000000000..6b4221944 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/loops/while_with_continue_and_break.dot @@ -0,0 +1,81 @@ +digraph iCFG { +19 [label="19: DeclStmt \n *&x:int =0 [line 7]\n APPLY_ABSTRACTION; [line 7]\n " shape="box"] + + + 19 -> 4 ; +18 [label="18: ComppoundAssignStmt \n n$2=*&x:int [line 10]\n *&x:int =(n$2 + 1) [line 10]\n REMOVE_TEMPS(n$2); [line 10]\n " shape="box"] + + + 18 -> 15 ; +17 [label="17: Prune (false branch) \n PRUNE(((n$1 > 5) == 0), false); [line 11]\n REMOVE_TEMPS(n$1); [line 11]\n " shape="invhouse"] + + + 17 -> 14 ; +16 [label="16: Prune (true branch) \n PRUNE(((n$1 > 5) != 0), true); [line 11]\n REMOVE_TEMPS(n$1); [line 11]\n APPLY_ABSTRACTION; [line 11]\n " shape="invhouse"] + + + 16 -> 8 ; +15 [label="15: BinaryOperatorStmt: GT \n n$1=*&x:int [line 11]\n " shape="box"] + + + 15 -> 16 ; + 15 -> 17 ; +14 [label="14: + \n " ] + + + 14 -> 11 ; +13 [label="13: Prune (false branch) \n PRUNE((2 == 0), false); [line 9]\n APPLY_ABSTRACTION; [line 9]\n " shape="invhouse"] + + + 13 -> 8 ; +12 [label="12: Prune (true branch) \n PRUNE((2 != 0), true); [line 9]\n " shape="invhouse"] + + + 12 -> 18 ; +11 [label="11: + \n " ] + + + 11 -> 12 ; + 11 -> 13 ; +10 [label="10: Prune (false branch) \n PRUNE(((n$0 == 2) == 0), false); [line 15]\n REMOVE_TEMPS(n$0); [line 15]\n " shape="invhouse"] + + + 10 -> 7 ; +9 [label="9: Prune (true branch) \n PRUNE(((n$0 == 2) != 0), true); [line 15]\n REMOVE_TEMPS(n$0); [line 15]\n APPLY_ABSTRACTION; [line 15]\n " shape="invhouse"] + + + 9 -> 4 ; +8 [label="8: BinaryOperatorStmt: EQ \n n$0=*&x:int [line 15]\n " shape="box"] + + + 8 -> 9 ; + 8 -> 10 ; +7 [label="7: + \n " ] + + + 7 -> 4 ; +6 [label="6: Prune (false branch) \n PRUNE((1 == 0), false); [line 8]\n " shape="invhouse"] + + + 6 -> 3 ; +5 [label="5: Prune (true branch) \n PRUNE((1 != 0), true); [line 8]\n APPLY_ABSTRACTION; [line 8]\n " shape="invhouse"] + + + 5 -> 11 ; +4 [label="4: + \n " ] + + + 4 -> 5 ; + 4 -> 6 ; +3 [label="3: Return Stmt \n NULLIFY(&x,false); [line 19]\n *&return:int =0 [line 19]\n APPLY_ABSTRACTION; [line 19]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: x:int \n DECLARE_LOCALS(&return,&x); [line 6]\n NULLIFY(&x,false); [line 6]\n " color=yellow style=filled] + + + 1 -> 19 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/nestedoperators/assign_in_condition.c b/infer/tests/codetoanalyze/c/frontend/nestedoperators/assign_in_condition.c new file mode 100644 index 000000000..97ecace57 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/nestedoperators/assign_in_condition.c @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +int foo(int *p) { + if((*p = 0)) { + return 32; + } + return 52; +} diff --git a/infer/tests/codetoanalyze/c/frontend/nestedoperators/assign_in_condition.dot b/infer/tests/codetoanalyze/c/frontend/nestedoperators/assign_in_condition.dot new file mode 100644 index 000000000..304dcfa1e --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/nestedoperators/assign_in_condition.dot @@ -0,0 +1,34 @@ +digraph iCFG { +8 [label="8: Return Stmt \n *&return:int =32 [line 8]\n APPLY_ABSTRACTION; [line 8]\n " shape="box"] + + + 8 -> 2 ; +7 [label="7: Prune (false branch) \n PRUNE((n$1 == 0), false); [line 7]\n REMOVE_TEMPS(n$0,n$1); [line 7]\n " shape="invhouse"] + + + 7 -> 4 ; +6 [label="6: Prune (true branch) \n PRUNE((n$1 != 0), true); [line 7]\n REMOVE_TEMPS(n$0,n$1); [line 7]\n " shape="invhouse"] + + + 6 -> 8 ; +5 [label="5: BinaryOperatorStmt: Assign \n n$0=*&p:int * [line 7]\n *n$0:int =0 [line 7]\n n$1=*n$0:int [line 7]\n NULLIFY(&p,false); [line 7]\n " shape="box"] + + + 5 -> 6 ; + 5 -> 7 ; +4 [label="4: + \n " ] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n *&return:int =52 [line 10]\n APPLY_ABSTRACTION; [line 10]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit foo \n " color=yellow style=filled] + + +1 [label="1: Start foo\nFormals: p:int *\nLocals: \n DECLARE_LOCALS(&return); [line 6]\n " color=yellow style=filled] + + + 1 -> 5 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/nestedoperators/gnuexpr.c b/infer/tests/codetoanalyze/c/frontend/nestedoperators/gnuexpr.c new file mode 100644 index 000000000..3882dab09 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/nestedoperators/gnuexpr.c @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +int main() { + int y = 3; + + y = ({int X = 4; X;}); + return 0; +} diff --git a/infer/tests/codetoanalyze/c/frontend/nestedoperators/gnuexpr.dot b/infer/tests/codetoanalyze/c/frontend/nestedoperators/gnuexpr.dot new file mode 100644 index 000000000..f27b56c73 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/nestedoperators/gnuexpr.dot @@ -0,0 +1,21 @@ +digraph iCFG { +5 [label="5: DeclStmt \n *&y:int =3 [line 7]\n NULLIFY(&y,false); [line 7]\n " shape="box"] + + + 5 -> 4 ; +4 [label="4: BinaryOperatorStmt: Assign \n n$0=*&X:int [line 9]\n *&X:int =4 [line 9]\n n$1=*&X:int [line 9]\n *&y:int =n$1 [line 9]\n REMOVE_TEMPS(n$1,n$0); [line 9]\n NULLIFY(&X,false); [line 9]\n NULLIFY(&y,false); [line 9]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n *&return:int =0 [line 10]\n APPLY_ABSTRACTION; [line 10]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: y:int X:int \n DECLARE_LOCALS(&return,&y,&X); [line 6]\n NULLIFY(&y,false); [line 6]\n " color=yellow style=filled] + + + 1 -> 5 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/nestedoperators/nestedassignment.c b/infer/tests/codetoanalyze/c/frontend/nestedoperators/nestedassignment.c new file mode 100644 index 000000000..97a3f0660 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/nestedoperators/nestedassignment.c @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +int main() { + double x = 1.0; + double q, r, s, t; + x = s; + q = (x = 3); + x += 7; + q = (x += 1.0); + q = (x += (r += (s += t))); + return 0; +} diff --git a/infer/tests/codetoanalyze/c/frontend/nestedoperators/nestedassignment.dot b/infer/tests/codetoanalyze/c/frontend/nestedoperators/nestedassignment.dot new file mode 100644 index 000000000..31bb95f58 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/nestedoperators/nestedassignment.dot @@ -0,0 +1,37 @@ +digraph iCFG { +9 [label="9: DeclStmt \n *&x:double =1.000000 [line 7]\n NULLIFY(&x,false); [line 7]\n " shape="box"] + + + 9 -> 8 ; +8 [label="8: BinaryOperatorStmt: Assign \n n$11=*&s:double [line 9]\n *&x:double =n$11 [line 9]\n REMOVE_TEMPS(n$11); [line 9]\n NULLIFY(&x,false); [line 9]\n " shape="box"] + + + 8 -> 7 ; +7 [label="7: BinaryOperatorStmt: Assign \n *&x:double =3 [line 10]\n n$10=*&x:double [line 10]\n *&q:double =n$10 [line 10]\n REMOVE_TEMPS(n$10); [line 10]\n NULLIFY(&q,false); [line 10]\n " shape="box"] + + + 7 -> 6 ; +6 [label="6: ComppoundAssignStmt \n n$9=*&x:double [line 11]\n *&x:double =(n$9 + 7) [line 11]\n REMOVE_TEMPS(n$9); [line 11]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: BinaryOperatorStmt: Assign \n n$7=*&x:double [line 12]\n *&x:double =(n$7 + 1.000000) [line 12]\n n$8=*&x:double [line 12]\n *&q:double =n$8 [line 12]\n REMOVE_TEMPS(n$7,n$8); [line 12]\n NULLIFY(&q,false); [line 12]\n " shape="box"] + + + 5 -> 4 ; +4 [label="4: BinaryOperatorStmt: Assign \n n$0=*&t:double [line 13]\n n$1=*&s:double [line 13]\n *&s:double =(n$1 + n$0) [line 13]\n n$2=*&s:double [line 13]\n n$3=*&r:double [line 13]\n *&r:double =(n$3 + n$2) [line 13]\n n$4=*&r:double [line 13]\n n$5=*&x:double [line 13]\n *&x:double =(n$5 + n$4) [line 13]\n n$6=*&x:double [line 13]\n *&q:double =n$6 [line 13]\n REMOVE_TEMPS(n$0,n$1,n$2,n$3,n$4,n$5,n$6); [line 13]\n NULLIFY(&q,false); [line 13]\n NULLIFY(&r,false); [line 13]\n NULLIFY(&s,false); [line 13]\n NULLIFY(&t,false); [line 13]\n NULLIFY(&x,false); [line 13]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n *&return:int =0 [line 14]\n APPLY_ABSTRACTION; [line 14]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: x:double q:double r:double s:double t:double \n DECLARE_LOCALS(&return,&x,&q,&r,&s,&t); [line 6]\n NULLIFY(&q,false); [line 6]\n NULLIFY(&x,false); [line 6]\n " color=yellow style=filled] + + + 1 -> 9 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/nestedoperators/union.c b/infer/tests/codetoanalyze/c/frontend/nestedoperators/union.c new file mode 100644 index 000000000..85bf56bfd --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/nestedoperators/union.c @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +#include + +struct { int a; int b; } *x; + +union { + int e; + int f; + + struct { int w; int u;} g; + + int h; +} y; + + +int main() { + int l; + + x->a=1; + y.f =7; + y.g.u=y.f; + + y.g.w = x->b; + return 0; +} diff --git a/infer/tests/codetoanalyze/c/frontend/nestedoperators/union.dot b/infer/tests/codetoanalyze/c/frontend/nestedoperators/union.dot new file mode 100644 index 000000000..aba19a48d --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/nestedoperators/union.dot @@ -0,0 +1,29 @@ +digraph iCFG { +7 [label="7: BinaryOperatorStmt: Assign \n n$3=*&#GB$x:struct (anonymous at infer/tests/codetoanalyze/c/frontend/nestedoperators/union.c:8:1) * [line 23]\n *n$3.a:int =1 [line 23]\n REMOVE_TEMPS(n$3); [line 23]\n " shape="box"] + + + 7 -> 6 ; +6 [label="6: BinaryOperatorStmt: Assign \n *&#GB$y.f:int =7 [line 24]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: BinaryOperatorStmt: Assign \n n$2=*&#GB$y.f:int [line 25]\n *&#GB$y.g.u:int =n$2 [line 25]\n REMOVE_TEMPS(n$2); [line 25]\n " shape="box"] + + + 5 -> 4 ; +4 [label="4: BinaryOperatorStmt: Assign \n n$0=*&#GB$x:struct (anonymous at infer/tests/codetoanalyze/c/frontend/nestedoperators/union.c:8:1) * [line 27]\n n$1=*n$0.b:int [line 27]\n *&#GB$y.g.w:int =n$1 [line 27]\n REMOVE_TEMPS(n$0,n$1); [line 27]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n *&return:int =0 [line 28]\n APPLY_ABSTRACTION; [line 28]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: l:int \n DECLARE_LOCALS(&return,&l); [line 20]\n NULLIFY(&l,false); [line 20]\n " color=yellow style=filled] + + + 1 -> 7 ; +} diff --git a/infer/tests/codetoanalyze/c/frontend/switchstmt/switch.c b/infer/tests/codetoanalyze/c/frontend/switchstmt/switch.c new file mode 100644 index 000000000..82dd19195 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/switchstmt/switch.c @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +#import + +int m1 () +{ + int value = 0; + while (value < 10) { + switch (value) { + int x = 1; + printf("(out)HELLO WORLD!"); + x = value + 1; + case 0: + printf("(0)HELLO WORLD!"); + break; + case 1: + printf("(1)HELLO WORLD!"); + continue; + case 2: + default: + printf("(2/def)HELLO WORLD!"); + continue; + } + printf("(after_switch)HELLO WORLD!"); + } + return 0; +} + + +int m2 () +{ + int value = 0; + switch (value) + { + int x = 1; + printf("(out)HELLO WORLD!"); + x = value + 1; + case 0: + printf("(0)HELLO WORLD!"); + break; + int z = 9; + default: + + case 1: { + int something = 1; + something++; + } + z = 42; + break; + case 2: + case 3: {} + } + return 0; +} + + +int m3 () +{ + int value = 0; + switch (value) + { + case 0: + printf("(0)HELLO WORLD!"); + break; + case 1: { + int something = 1; + something++; + } + break; + int z = 9; + case 2: + case 3: {} + } + return 0; +} + + +int m4 () +{ + int value = 0; + switch (value) + { + int x = 1; + printf("(out)HELLO WORLD!"); + x = value + 1; + case 0: + printf("(0)HELLO WORLD!"); + break; + int z = 9; + default: + + case 1: { + int something = 1; + something++; + } + z = 42; + break; + case 2: + case 3: {} + } + return 0; +} + + +int m5 () +{ + int value = 0; + while (value < 10) { + switch (value) { + int x = 1; + printf("(out)HELLO WORLD!"); + x = value + 1; + continue; + case 0: + printf("(0)HELLO WORLD!"); + break; + } + } + return 0; +} + + +int m6 () +{ + int value = 0; + switch (value > 0 ? 1 : 0) + { + case 0: + printf("(0)HELLO WORLD!"); + break; + case 1: { + int something = 1; + something++; + } + break; + int z = 9; + case 2: + case 3: {} + } + return 0; +} + + +int getValue () +{ + return 1; +} + +int m7 () +{ + int value = 0; + switch (getValue()) + { + case 0: + printf("(0)HELLO WORLD!"); + break; + case 1: { + int something = 1; + something++; + } + break; + int z = 9; + case 2: + case 3: {} + } + return 0; +} + +int m8 () +{ + int value = 0; + while (value < 10) + { + switch (getValue() == 0 ? 1 : 2) + { + case 0: + printf("(0)HELLO WORLD!"); + return 0; + case 1: + { + int something = 1; + something++; + continue; + } + break; + int z = 9; + case 2: + case 3: {} + } + int a = 0; + } + return 0; +} + +int m9 () +{ + int value = 0; + switch (value) + { + } + return 0; +} + +int m10 () +{ + int value = 0; + switch (value = 7) + { + } + return 0; +} + +int m11 () +{ + int value = 0; + switch (value = (value == 0 ? 7 : 9)) + { + case 0: + printf("(0)HELLO WORLD!"); + } + return 0; +} diff --git a/infer/tests/codetoanalyze/c/frontend/switchstmt/switch.dot b/infer/tests/codetoanalyze/c/frontend/switchstmt/switch.dot new file mode 100644 index 000000000..5726dbf20 --- /dev/null +++ b/infer/tests/codetoanalyze/c/frontend/switchstmt/switch.dot @@ -0,0 +1,805 @@ +digraph iCFG { +195 [label="195: DeclStmt \n *&value:int =0 [line 218]\n " shape="box"] + + + 195 -> 186 ; +194 [label="194: Prune (false branch) \n PRUNE(((n$42 == 0) == 0), false); [line 221]\n APPLY_ABSTRACTION; [line 221]\n " shape="invhouse"] + + + 194 -> 184 ; +193 [label="193: Prune (true branch) \n PRUNE(((n$42 == 0) != 0), true); [line 221]\n " shape="invhouse"] + + + 193 -> 192 ; +192 [label="192: Call _fun_printf \n n$43=_fun_printf(\"(0)HELLO WORLD!\":char *) [line 222]\n REMOVE_TEMPS(n$43); [line 222]\n APPLY_ABSTRACTION; [line 222]\n " shape="box"] + + + 192 -> 184 ; +191 [label="191: Switch_stmt \n n$41=*&SIL_temp_conditional___185:int [line 219]\n NULLIFY(&SIL_temp_conditional___185,true); [line 219]\n *&value:int =n$41 [line 219]\n n$42=*&value:int [line 219]\n NULLIFY(&value,false); [line 219]\n " shape="box"] + + + 191 -> 193 ; + 191 -> 194 ; +190 [label="190: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___185); [line 219]\n *&SIL_temp_conditional___185:int =9 [line 219]\n APPLY_ABSTRACTION; [line 219]\n " shape="box"] + + + 190 -> 185 ; +189 [label="189: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___185); [line 219]\n *&SIL_temp_conditional___185:int =7 [line 219]\n APPLY_ABSTRACTION; [line 219]\n " shape="box"] + + + 189 -> 185 ; +188 [label="188: Prune (false branch) \n PRUNE(((n$40 == 0) == 0), false); [line 219]\n REMOVE_TEMPS(n$40); [line 219]\n " shape="invhouse"] + + + 188 -> 190 ; +187 [label="187: Prune (true branch) \n PRUNE(((n$40 == 0) != 0), true); [line 219]\n REMOVE_TEMPS(n$40); [line 219]\n " shape="invhouse"] + + + 187 -> 189 ; +186 [label="186: BinaryOperatorStmt: EQ \n n$40=*&value:int [line 219]\n NULLIFY(&value,false); [line 219]\n " shape="box"] + + + 186 -> 187 ; + 186 -> 188 ; +185 [label="185: + \n " ] + + + 185 -> 191 ; +184 [label="184: Return Stmt \n *&return:int =0 [line 224]\n REMOVE_TEMPS(n$41,n$42); [line 224]\n APPLY_ABSTRACTION; [line 224]\n " shape="box"] + + + 184 -> 183 ; +183 [label="183: Exit m11 \n " color=yellow style=filled] + + +182 [label="182: Start m11\nFormals: \nLocals: value:int \n DECLARE_LOCALS(&return,&value); [line 216]\n NULLIFY(&value,false); [line 216]\n " color=yellow style=filled] + + + 182 -> 195 ; +181 [label="181: DeclStmt \n *&value:int =0 [line 209]\n NULLIFY(&value,false); [line 209]\n " shape="box"] + + + 181 -> 180 ; +180 [label="180: Switch_stmt \n *&value:int =7 [line 210]\n n$39=*&value:int [line 210]\n NULLIFY(&value,false); [line 210]\n " shape="box"] + + + 180 -> 179 ; +179 [label="179: Return Stmt \n *&return:int =0 [line 213]\n REMOVE_TEMPS(n$39); [line 213]\n APPLY_ABSTRACTION; [line 213]\n " shape="box"] + + + 179 -> 178 ; +178 [label="178: Exit m10 \n " color=yellow style=filled] + + +177 [label="177: Start m10\nFormals: \nLocals: value:int \n DECLARE_LOCALS(&return,&value); [line 207]\n NULLIFY(&value,false); [line 207]\n " color=yellow style=filled] + + + 177 -> 181 ; +176 [label="176: DeclStmt \n *&value:int =0 [line 200]\n " shape="box"] + + + 176 -> 175 ; +175 [label="175: Switch_stmt \n n$38=*&value:int [line 201]\n NULLIFY(&value,false); [line 201]\n " shape="box"] + + + 175 -> 174 ; +174 [label="174: Return Stmt \n *&return:int =0 [line 204]\n REMOVE_TEMPS(n$38); [line 204]\n APPLY_ABSTRACTION; [line 204]\n " shape="box"] + + + 174 -> 173 ; +173 [label="173: Exit m9 \n " color=yellow style=filled] + + +172 [label="172: Start m9\nFormals: \nLocals: value:int \n DECLARE_LOCALS(&return,&value); [line 198]\n NULLIFY(&value,false); [line 198]\n " color=yellow style=filled] + + + 172 -> 176 ; +171 [label="171: DeclStmt \n *&value:int =0 [line 174]\n APPLY_ABSTRACTION; [line 174]\n " shape="box"] + + + 171 -> 146 ; +170 [label="170: Prune (false branch) \n PRUNE(((n$35 == 0) == 0), false); [line 179]\n " shape="invhouse"] + + + 170 -> 165 ; + 170 -> 166 ; +169 [label="169: Prune (true branch) \n PRUNE(((n$35 == 0) != 0), true); [line 179]\n " shape="invhouse"] + + + 169 -> 168 ; +168 [label="168: Call _fun_printf \n NULLIFY(&value,false); [line 180]\n n$37=_fun_printf(\"(0)HELLO WORLD!\":char *) [line 180]\n REMOVE_TEMPS(n$37); [line 180]\n " shape="box"] + + + 168 -> 167 ; +167 [label="167: Return Stmt \n *&return:int =0 [line 181]\n APPLY_ABSTRACTION; [line 181]\n " shape="box"] + + + 167 -> 144 ; +166 [label="166: Prune (false branch) \n PRUNE(((n$35 == 1) == 0), false); [line 182]\n " shape="invhouse"] + + + 166 -> 160 ; + 166 -> 161 ; +165 [label="165: Prune (true branch) \n PRUNE(((n$35 == 1) != 0), true); [line 182]\n " shape="invhouse"] + + + 165 -> 164 ; +164 [label="164: DeclStmt \n *&something:int =1 [line 184]\n " shape="box"] + + + 164 -> 163 ; +163 [label="163: UnaryOperator \n n$36=*&something:int [line 185]\n *&something:int =(n$36 + 1) [line 185]\n REMOVE_TEMPS(n$36); [line 185]\n NULLIFY(&something,false); [line 185]\n APPLY_ABSTRACTION; [line 185]\n " shape="box"] + + + 163 -> 146 ; +162 [label="162: DeclStmt \n *&z:int =9 [line 189]\n NULLIFY(&a,false); [line 189]\n NULLIFY(&something,false); [line 189]\n NULLIFY(&z,false); [line 189]\n APPLY_ABSTRACTION; [line 189]\n " shape="box"] + + + 162 -> 150 ; +161 [label="161: Prune (false branch) \n PRUNE(((n$35 == 2) == 0), false); [line 190]\n " shape="invhouse"] + + + 161 -> 158 ; + 161 -> 159 ; +160 [label="160: Prune (true branch) \n PRUNE(((n$35 == 2) != 0), true); [line 190]\n APPLY_ABSTRACTION; [line 190]\n " shape="invhouse"] + + + 160 -> 150 ; +159 [label="159: Prune (false branch) \n PRUNE(((n$35 == 3) == 0), false); [line 191]\n APPLY_ABSTRACTION; [line 191]\n " shape="invhouse"] + + + 159 -> 150 ; +158 [label="158: Prune (true branch) \n PRUNE(((n$35 == 3) != 0), true); [line 191]\n APPLY_ABSTRACTION; [line 191]\n " shape="invhouse"] + + + 158 -> 150 ; +157 [label="157: Switch_stmt \n n$35=*&SIL_temp_conditional___151:int [line 177]\n NULLIFY(&SIL_temp_conditional___151,true); [line 177]\n " shape="box"] + + + 157 -> 169 ; + 157 -> 170 ; +156 [label="156: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___151); [line 177]\n *&SIL_temp_conditional___151:int =2 [line 177]\n APPLY_ABSTRACTION; [line 177]\n " shape="box"] + + + 156 -> 151 ; +155 [label="155: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___151); [line 177]\n *&SIL_temp_conditional___151:int =1 [line 177]\n APPLY_ABSTRACTION; [line 177]\n " shape="box"] + + + 155 -> 151 ; +154 [label="154: Prune (false branch) \n PRUNE(((n$34 == 0) == 0), false); [line 177]\n REMOVE_TEMPS(n$34); [line 177]\n " shape="invhouse"] + + + 154 -> 156 ; +153 [label="153: Prune (true branch) \n PRUNE(((n$34 == 0) != 0), true); [line 177]\n REMOVE_TEMPS(n$34); [line 177]\n " shape="invhouse"] + + + 153 -> 155 ; +152 [label="152: BinaryOperatorStmt: EQ \n n$34=_fun_getValue() [line 177]\n " shape="box"] + + + 152 -> 153 ; + 152 -> 154 ; +151 [label="151: + \n " ] + + + 151 -> 157 ; +150 [label="150: DeclStmt \n *&a:int =0 [line 193]\n REMOVE_TEMPS(n$35); [line 193]\n NULLIFY(&a,false); [line 193]\n APPLY_ABSTRACTION; [line 193]\n " shape="box"] + + + 150 -> 146 ; +149 [label="149: Prune (false branch) \n PRUNE(((n$33 < 10) == 0), false); [line 175]\n REMOVE_TEMPS(n$33); [line 175]\n " shape="invhouse"] + + + 149 -> 145 ; +148 [label="148: Prune (true branch) \n PRUNE(((n$33 < 10) != 0), true); [line 175]\n REMOVE_TEMPS(n$33); [line 175]\n " shape="invhouse"] + + + 148 -> 152 ; +147 [label="147: BinaryOperatorStmt: LT \n n$33=*&value:int [line 175]\n " shape="box"] + + + 147 -> 148 ; + 147 -> 149 ; +146 [label="146: + \n " ] + + + 146 -> 147 ; +145 [label="145: Return Stmt \n NULLIFY(&value,false); [line 195]\n *&return:int =0 [line 195]\n APPLY_ABSTRACTION; [line 195]\n " shape="box"] + + + 145 -> 144 ; +144 [label="144: Exit m8 \n " color=yellow style=filled] + + +143 [label="143: Start m8\nFormals: \nLocals: value:int something:int z:int a:int \n DECLARE_LOCALS(&return,&value,&something,&z,&a); [line 172]\n NULLIFY(&a,false); [line 172]\n NULLIFY(&something,false); [line 172]\n NULLIFY(&value,false); [line 172]\n NULLIFY(&z,false); [line 172]\n " color=yellow style=filled] + + + 143 -> 171 ; +142 [label="142: DeclStmt \n *&value:int =0 [line 154]\n NULLIFY(&value,false); [line 154]\n " shape="box"] + + + 142 -> 129 ; +141 [label="141: Prune (false branch) \n PRUNE(((n$30 == 0) == 0), false); [line 157]\n " shape="invhouse"] + + + 141 -> 137 ; + 141 -> 138 ; +140 [label="140: Prune (true branch) \n PRUNE(((n$30 == 0) != 0), true); [line 157]\n " shape="invhouse"] + + + 140 -> 139 ; +139 [label="139: Call _fun_printf \n n$32=_fun_printf(\"(0)HELLO WORLD!\":char *) [line 158]\n REMOVE_TEMPS(n$32); [line 158]\n APPLY_ABSTRACTION; [line 158]\n " shape="box"] + + + 139 -> 128 ; +138 [label="138: Prune (false branch) \n PRUNE(((n$30 == 1) == 0), false); [line 160]\n " shape="invhouse"] + + + 138 -> 132 ; + 138 -> 133 ; +137 [label="137: Prune (true branch) \n PRUNE(((n$30 == 1) != 0), true); [line 160]\n " shape="invhouse"] + + + 137 -> 136 ; +136 [label="136: DeclStmt \n *&something:int =1 [line 161]\n " shape="box"] + + + 136 -> 135 ; +135 [label="135: UnaryOperator \n n$31=*&something:int [line 162]\n *&something:int =(n$31 + 1) [line 162]\n REMOVE_TEMPS(n$31); [line 162]\n NULLIFY(&something,false); [line 162]\n APPLY_ABSTRACTION; [line 162]\n " shape="box"] + + + 135 -> 128 ; +134 [label="134: DeclStmt \n *&z:int =9 [line 165]\n NULLIFY(&something,false); [line 165]\n NULLIFY(&value,false); [line 165]\n NULLIFY(&z,false); [line 165]\n APPLY_ABSTRACTION; [line 165]\n " shape="box"] + + + 134 -> 128 ; +133 [label="133: Prune (false branch) \n PRUNE(((n$30 == 2) == 0), false); [line 166]\n " shape="invhouse"] + + + 133 -> 130 ; + 133 -> 131 ; +132 [label="132: Prune (true branch) \n PRUNE(((n$30 == 2) != 0), true); [line 166]\n APPLY_ABSTRACTION; [line 166]\n " shape="invhouse"] + + + 132 -> 128 ; +131 [label="131: Prune (false branch) \n PRUNE(((n$30 == 3) == 0), false); [line 167]\n APPLY_ABSTRACTION; [line 167]\n " shape="invhouse"] + + + 131 -> 128 ; +130 [label="130: Prune (true branch) \n PRUNE(((n$30 == 3) != 0), true); [line 167]\n APPLY_ABSTRACTION; [line 167]\n " shape="invhouse"] + + + 130 -> 128 ; +129 [label="129: Switch_stmt \n n$30=_fun_getValue() [line 155]\n " shape="box"] + + + 129 -> 140 ; + 129 -> 141 ; +128 [label="128: Return Stmt \n *&return:int =0 [line 169]\n REMOVE_TEMPS(n$30); [line 169]\n APPLY_ABSTRACTION; [line 169]\n " shape="box"] + + + 128 -> 127 ; +127 [label="127: Exit m7 \n " color=yellow style=filled] + + +126 [label="126: Start m7\nFormals: \nLocals: value:int something:int z:int \n DECLARE_LOCALS(&return,&value,&something,&z); [line 152]\n NULLIFY(&something,false); [line 152]\n NULLIFY(&value,false); [line 152]\n NULLIFY(&z,false); [line 152]\n " color=yellow style=filled] + + + 126 -> 142 ; +125 [label="125: Return Stmt \n *&return:int =1 [line 149]\n APPLY_ABSTRACTION; [line 149]\n " shape="box"] + + + 125 -> 124 ; +124 [label="124: Exit getValue \n " color=yellow style=filled] + + +123 [label="123: Start getValue\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 147]\n " color=yellow style=filled] + + + 123 -> 125 ; +122 [label="122: DeclStmt \n *&value:int =0 [line 128]\n " shape="box"] + + + 122 -> 104 ; +121 [label="121: Prune (false branch) \n PRUNE(((n$27 == 0) == 0), false); [line 131]\n " shape="invhouse"] + + + 121 -> 117 ; + 121 -> 118 ; +120 [label="120: Prune (true branch) \n PRUNE(((n$27 == 0) != 0), true); [line 131]\n " shape="invhouse"] + + + 120 -> 119 ; +119 [label="119: Call _fun_printf \n n$29=_fun_printf(\"(0)HELLO WORLD!\":char *) [line 132]\n REMOVE_TEMPS(n$29); [line 132]\n APPLY_ABSTRACTION; [line 132]\n " shape="box"] + + + 119 -> 102 ; +118 [label="118: Prune (false branch) \n PRUNE(((n$27 == 1) == 0), false); [line 134]\n " shape="invhouse"] + + + 118 -> 112 ; + 118 -> 113 ; +117 [label="117: Prune (true branch) \n PRUNE(((n$27 == 1) != 0), true); [line 134]\n " shape="invhouse"] + + + 117 -> 116 ; +116 [label="116: DeclStmt \n *&something:int =1 [line 135]\n " shape="box"] + + + 116 -> 115 ; +115 [label="115: UnaryOperator \n n$28=*&something:int [line 136]\n *&something:int =(n$28 + 1) [line 136]\n REMOVE_TEMPS(n$28); [line 136]\n NULLIFY(&something,false); [line 136]\n APPLY_ABSTRACTION; [line 136]\n " shape="box"] + + + 115 -> 102 ; +114 [label="114: DeclStmt \n *&z:int =9 [line 139]\n NULLIFY(&something,false); [line 139]\n NULLIFY(&value,false); [line 139]\n NULLIFY(&z,false); [line 139]\n APPLY_ABSTRACTION; [line 139]\n " shape="box"] + + + 114 -> 102 ; +113 [label="113: Prune (false branch) \n PRUNE(((n$27 == 2) == 0), false); [line 140]\n " shape="invhouse"] + + + 113 -> 110 ; + 113 -> 111 ; +112 [label="112: Prune (true branch) \n PRUNE(((n$27 == 2) != 0), true); [line 140]\n APPLY_ABSTRACTION; [line 140]\n " shape="invhouse"] + + + 112 -> 102 ; +111 [label="111: Prune (false branch) \n PRUNE(((n$27 == 3) == 0), false); [line 141]\n APPLY_ABSTRACTION; [line 141]\n " shape="invhouse"] + + + 111 -> 102 ; +110 [label="110: Prune (true branch) \n PRUNE(((n$27 == 3) != 0), true); [line 141]\n APPLY_ABSTRACTION; [line 141]\n " shape="invhouse"] + + + 110 -> 102 ; +109 [label="109: Switch_stmt \n n$27=*&SIL_temp_conditional___103:int [line 129]\n NULLIFY(&SIL_temp_conditional___103,true); [line 129]\n " shape="box"] + + + 109 -> 120 ; + 109 -> 121 ; +108 [label="108: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___103); [line 129]\n *&SIL_temp_conditional___103:int =0 [line 129]\n APPLY_ABSTRACTION; [line 129]\n " shape="box"] + + + 108 -> 103 ; +107 [label="107: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___103); [line 129]\n *&SIL_temp_conditional___103:int =1 [line 129]\n APPLY_ABSTRACTION; [line 129]\n " shape="box"] + + + 107 -> 103 ; +106 [label="106: Prune (false branch) \n PRUNE(((n$26 > 0) == 0), false); [line 129]\n REMOVE_TEMPS(n$26); [line 129]\n " shape="invhouse"] + + + 106 -> 108 ; +105 [label="105: Prune (true branch) \n PRUNE(((n$26 > 0) != 0), true); [line 129]\n REMOVE_TEMPS(n$26); [line 129]\n " shape="invhouse"] + + + 105 -> 107 ; +104 [label="104: BinaryOperatorStmt: GT \n n$26=*&value:int [line 129]\n NULLIFY(&value,false); [line 129]\n " shape="box"] + + + 104 -> 105 ; + 104 -> 106 ; +103 [label="103: + \n " ] + + + 103 -> 109 ; +102 [label="102: Return Stmt \n *&return:int =0 [line 143]\n REMOVE_TEMPS(n$27); [line 143]\n APPLY_ABSTRACTION; [line 143]\n " shape="box"] + + + 102 -> 101 ; +101 [label="101: Exit m6 \n " color=yellow style=filled] + + +100 [label="100: Start m6\nFormals: \nLocals: value:int something:int z:int \n DECLARE_LOCALS(&return,&value,&something,&z); [line 126]\n NULLIFY(&something,false); [line 126]\n NULLIFY(&value,false); [line 126]\n NULLIFY(&z,false); [line 126]\n " color=yellow style=filled] + + + 100 -> 122 ; +99 [label="99: DeclStmt \n *&value:int =0 [line 110]\n APPLY_ABSTRACTION; [line 110]\n " shape="box"] + + + 99 -> 88 ; +98 [label="98: DeclStmt \n *&x:int =1 [line 113]\n NULLIFY(&x,false); [line 113]\n " shape="box"] + + + 98 -> 97 ; +97 [label="97: Call _fun_printf \n n$25=_fun_printf(\"(out)HELLO WORLD!\":char *) [line 114]\n REMOVE_TEMPS(n$25); [line 114]\n " shape="box"] + + + 97 -> 96 ; +96 [label="96: BinaryOperatorStmt: Assign \n n$24=*&value:int [line 115]\n *&x:int =(n$24 + 1) [line 115]\n REMOVE_TEMPS(n$24); [line 115]\n NULLIFY(&x,false); [line 115]\n APPLY_ABSTRACTION; [line 115]\n " shape="box"] + + + 96 -> 88 ; +95 [label="95: Prune (false branch) \n PRUNE(((n$22 == 0) == 0), false); [line 117]\n APPLY_ABSTRACTION; [line 117]\n " shape="invhouse"] + + + 95 -> 88 ; +94 [label="94: Prune (true branch) \n PRUNE(((n$22 == 0) != 0), true); [line 117]\n " shape="invhouse"] + + + 94 -> 93 ; +93 [label="93: Call _fun_printf \n n$23=_fun_printf(\"(0)HELLO WORLD!\":char *) [line 118]\n REMOVE_TEMPS(n$23); [line 118]\n APPLY_ABSTRACTION; [line 118]\n " shape="box"] + + + 93 -> 88 ; +92 [label="92: Switch_stmt \n n$22=*&value:int [line 112]\n " shape="box"] + + + 92 -> 94 ; + 92 -> 95 ; +91 [label="91: Prune (false branch) \n PRUNE(((n$21 < 10) == 0), false); [line 111]\n REMOVE_TEMPS(n$21); [line 111]\n " shape="invhouse"] + + + 91 -> 87 ; +90 [label="90: Prune (true branch) \n PRUNE(((n$21 < 10) != 0), true); [line 111]\n REMOVE_TEMPS(n$21); [line 111]\n " shape="invhouse"] + + + 90 -> 92 ; +89 [label="89: BinaryOperatorStmt: LT \n n$21=*&value:int [line 111]\n " shape="box"] + + + 89 -> 90 ; + 89 -> 91 ; +88 [label="88: + \n REMOVE_TEMPS(n$22); [line 111]\n " ] + + + 88 -> 89 ; +87 [label="87: Return Stmt \n NULLIFY(&value,false); [line 122]\n *&return:int =0 [line 122]\n APPLY_ABSTRACTION; [line 122]\n " shape="box"] + + + 87 -> 86 ; +86 [label="86: Exit m5 \n " color=yellow style=filled] + + +85 [label="85: Start m5\nFormals: \nLocals: value:int x:int \n DECLARE_LOCALS(&return,&value,&x); [line 108]\n NULLIFY(&value,false); [line 108]\n NULLIFY(&x,false); [line 108]\n " color=yellow style=filled] + + + 85 -> 99 ; +84 [label="84: DeclStmt \n *&value:int =0 [line 83]\n " shape="box"] + + + 84 -> 66 ; +83 [label="83: DeclStmt \n *&x:int =1 [line 86]\n NULLIFY(&something,false); [line 86]\n NULLIFY(&x,false); [line 86]\n NULLIFY(&z,false); [line 86]\n " shape="box"] + + + 83 -> 82 ; +82 [label="82: Call _fun_printf \n n$20=_fun_printf(\"(out)HELLO WORLD!\":char *) [line 87]\n REMOVE_TEMPS(n$20); [line 87]\n " shape="box"] + + + 82 -> 81 ; +81 [label="81: BinaryOperatorStmt: Assign \n n$19=*&value:int [line 88]\n *&x:int =(n$19 + 1) [line 88]\n REMOVE_TEMPS(n$19); [line 88]\n NULLIFY(&value,false); [line 88]\n NULLIFY(&x,false); [line 88]\n APPLY_ABSTRACTION; [line 88]\n " shape="box"] + + + 81 -> 78 ; +80 [label="80: Prune (false branch) \n PRUNE(((n$16 == 0) == 0), false); [line 89]\n " shape="invhouse"] + + + 80 -> 75 ; + 80 -> 76 ; +79 [label="79: Prune (true branch) \n PRUNE(((n$16 == 0) != 0), true); [line 89]\n APPLY_ABSTRACTION; [line 89]\n " shape="invhouse"] + + + 79 -> 78 ; +78 [label="78: Call _fun_printf \n n$18=_fun_printf(\"(0)HELLO WORLD!\":char *) [line 90]\n REMOVE_TEMPS(n$18); [line 90]\n APPLY_ABSTRACTION; [line 90]\n " shape="box"] + + + 78 -> 65 ; +77 [label="77: DeclStmt \n *&z:int =9 [line 92]\n NULLIFY(&something,false); [line 92]\n NULLIFY(&value,false); [line 92]\n NULLIFY(&x,false); [line 92]\n NULLIFY(&z,false); [line 92]\n APPLY_ABSTRACTION; [line 92]\n " shape="box"] + + + 77 -> 74 ; +76 [label="76: Prune (false branch) \n PRUNE(((n$16 == 1) == 0), false); [line 95]\n " shape="invhouse"] + + + 76 -> 70 ; + 76 -> 71 ; +75 [label="75: Prune (true branch) \n PRUNE(((n$16 == 1) != 0), true); [line 95]\n APPLY_ABSTRACTION; [line 95]\n " shape="invhouse"] + + + 75 -> 74 ; +74 [label="74: DeclStmt \n *&something:int =1 [line 96]\n " shape="box"] + + + 74 -> 73 ; +73 [label="73: UnaryOperator \n n$17=*&something:int [line 97]\n *&something:int =(n$17 + 1) [line 97]\n REMOVE_TEMPS(n$17); [line 97]\n NULLIFY(&something,false); [line 97]\n " shape="box"] + + + 73 -> 72 ; +72 [label="72: BinaryOperatorStmt: Assign \n *&z:int =42 [line 99]\n NULLIFY(&z,false); [line 99]\n APPLY_ABSTRACTION; [line 99]\n " shape="box"] + + + 72 -> 65 ; +71 [label="71: Prune (false branch) \n PRUNE(((n$16 == 2) == 0), false); [line 101]\n " shape="invhouse"] + + + 71 -> 68 ; + 71 -> 69 ; +70 [label="70: Prune (true branch) \n PRUNE(((n$16 == 2) != 0), true); [line 101]\n APPLY_ABSTRACTION; [line 101]\n " shape="invhouse"] + + + 70 -> 65 ; +69 [label="69: Prune (false branch) \n PRUNE(((n$16 == 3) == 0), false); [line 102]\n " shape="invhouse"] + + + 69 -> 67 ; +68 [label="68: Prune (true branch) \n PRUNE(((n$16 == 3) != 0), true); [line 102]\n APPLY_ABSTRACTION; [line 102]\n " shape="invhouse"] + + + 68 -> 65 ; +67 [label="67: DefaultStmt_placeholder \n APPLY_ABSTRACTION; [line 93]\n " shape="box"] + + + 67 -> 74 ; +66 [label="66: Switch_stmt \n n$16=*&value:int [line 84]\n NULLIFY(&value,false); [line 84]\n " shape="box"] + + + 66 -> 79 ; + 66 -> 80 ; +65 [label="65: Return Stmt \n *&return:int =0 [line 104]\n REMOVE_TEMPS(n$16); [line 104]\n APPLY_ABSTRACTION; [line 104]\n " shape="box"] + + + 65 -> 64 ; +64 [label="64: Exit m4 \n " color=yellow style=filled] + + +63 [label="63: Start m4\nFormals: \nLocals: value:int x:int z:int something:int \n DECLARE_LOCALS(&return,&value,&x,&z,&something); [line 81]\n NULLIFY(&something,false); [line 81]\n NULLIFY(&value,false); [line 81]\n NULLIFY(&x,false); [line 81]\n NULLIFY(&z,false); [line 81]\n " color=yellow style=filled] + + + 63 -> 84 ; +62 [label="62: DeclStmt \n *&value:int =0 [line 62]\n " shape="box"] + + + 62 -> 49 ; +61 [label="61: Prune (false branch) \n PRUNE(((n$13 == 0) == 0), false); [line 65]\n " shape="invhouse"] + + + 61 -> 57 ; + 61 -> 58 ; +60 [label="60: Prune (true branch) \n PRUNE(((n$13 == 0) != 0), true); [line 65]\n " shape="invhouse"] + + + 60 -> 59 ; +59 [label="59: Call _fun_printf \n n$15=_fun_printf(\"(0)HELLO WORLD!\":char *) [line 66]\n REMOVE_TEMPS(n$15); [line 66]\n APPLY_ABSTRACTION; [line 66]\n " shape="box"] + + + 59 -> 48 ; +58 [label="58: Prune (false branch) \n PRUNE(((n$13 == 1) == 0), false); [line 68]\n " shape="invhouse"] + + + 58 -> 52 ; + 58 -> 53 ; +57 [label="57: Prune (true branch) \n PRUNE(((n$13 == 1) != 0), true); [line 68]\n " shape="invhouse"] + + + 57 -> 56 ; +56 [label="56: DeclStmt \n *&something:int =1 [line 69]\n " shape="box"] + + + 56 -> 55 ; +55 [label="55: UnaryOperator \n n$14=*&something:int [line 70]\n *&something:int =(n$14 + 1) [line 70]\n REMOVE_TEMPS(n$14); [line 70]\n NULLIFY(&something,false); [line 70]\n APPLY_ABSTRACTION; [line 70]\n " shape="box"] + + + 55 -> 48 ; +54 [label="54: DeclStmt \n *&z:int =9 [line 73]\n NULLIFY(&something,false); [line 73]\n NULLIFY(&value,false); [line 73]\n NULLIFY(&z,false); [line 73]\n APPLY_ABSTRACTION; [line 73]\n " shape="box"] + + + 54 -> 48 ; +53 [label="53: Prune (false branch) \n PRUNE(((n$13 == 2) == 0), false); [line 74]\n " shape="invhouse"] + + + 53 -> 50 ; + 53 -> 51 ; +52 [label="52: Prune (true branch) \n PRUNE(((n$13 == 2) != 0), true); [line 74]\n APPLY_ABSTRACTION; [line 74]\n " shape="invhouse"] + + + 52 -> 48 ; +51 [label="51: Prune (false branch) \n PRUNE(((n$13 == 3) == 0), false); [line 75]\n APPLY_ABSTRACTION; [line 75]\n " shape="invhouse"] + + + 51 -> 48 ; +50 [label="50: Prune (true branch) \n PRUNE(((n$13 == 3) != 0), true); [line 75]\n APPLY_ABSTRACTION; [line 75]\n " shape="invhouse"] + + + 50 -> 48 ; +49 [label="49: Switch_stmt \n n$13=*&value:int [line 63]\n NULLIFY(&value,false); [line 63]\n " shape="box"] + + + 49 -> 60 ; + 49 -> 61 ; +48 [label="48: Return Stmt \n *&return:int =0 [line 77]\n REMOVE_TEMPS(n$13); [line 77]\n APPLY_ABSTRACTION; [line 77]\n " shape="box"] + + + 48 -> 47 ; +47 [label="47: Exit m3 \n " color=yellow style=filled] + + +46 [label="46: Start m3\nFormals: \nLocals: value:int something:int z:int \n DECLARE_LOCALS(&return,&value,&something,&z); [line 60]\n NULLIFY(&something,false); [line 60]\n NULLIFY(&value,false); [line 60]\n NULLIFY(&z,false); [line 60]\n " color=yellow style=filled] + + + 46 -> 62 ; +45 [label="45: DeclStmt \n *&value:int =0 [line 35]\n " shape="box"] + + + 45 -> 27 ; +44 [label="44: DeclStmt \n *&x:int =1 [line 38]\n NULLIFY(&something,false); [line 38]\n NULLIFY(&x,false); [line 38]\n NULLIFY(&z,false); [line 38]\n " shape="box"] + + + 44 -> 43 ; +43 [label="43: Call _fun_printf \n n$12=_fun_printf(\"(out)HELLO WORLD!\":char *) [line 39]\n REMOVE_TEMPS(n$12); [line 39]\n " shape="box"] + + + 43 -> 42 ; +42 [label="42: BinaryOperatorStmt: Assign \n n$11=*&value:int [line 40]\n *&x:int =(n$11 + 1) [line 40]\n REMOVE_TEMPS(n$11); [line 40]\n NULLIFY(&value,false); [line 40]\n NULLIFY(&x,false); [line 40]\n APPLY_ABSTRACTION; [line 40]\n " shape="box"] + + + 42 -> 39 ; +41 [label="41: Prune (false branch) \n PRUNE(((n$8 == 0) == 0), false); [line 41]\n " shape="invhouse"] + + + 41 -> 36 ; + 41 -> 37 ; +40 [label="40: Prune (true branch) \n PRUNE(((n$8 == 0) != 0), true); [line 41]\n APPLY_ABSTRACTION; [line 41]\n " shape="invhouse"] + + + 40 -> 39 ; +39 [label="39: Call _fun_printf \n n$10=_fun_printf(\"(0)HELLO WORLD!\":char *) [line 42]\n REMOVE_TEMPS(n$10); [line 42]\n APPLY_ABSTRACTION; [line 42]\n " shape="box"] + + + 39 -> 26 ; +38 [label="38: DeclStmt \n *&z:int =9 [line 44]\n NULLIFY(&something,false); [line 44]\n NULLIFY(&value,false); [line 44]\n NULLIFY(&x,false); [line 44]\n NULLIFY(&z,false); [line 44]\n APPLY_ABSTRACTION; [line 44]\n " shape="box"] + + + 38 -> 35 ; +37 [label="37: Prune (false branch) \n PRUNE(((n$8 == 1) == 0), false); [line 47]\n " shape="invhouse"] + + + 37 -> 31 ; + 37 -> 32 ; +36 [label="36: Prune (true branch) \n PRUNE(((n$8 == 1) != 0), true); [line 47]\n APPLY_ABSTRACTION; [line 47]\n " shape="invhouse"] + + + 36 -> 35 ; +35 [label="35: DeclStmt \n *&something:int =1 [line 48]\n " shape="box"] + + + 35 -> 34 ; +34 [label="34: UnaryOperator \n n$9=*&something:int [line 49]\n *&something:int =(n$9 + 1) [line 49]\n REMOVE_TEMPS(n$9); [line 49]\n NULLIFY(&something,false); [line 49]\n " shape="box"] + + + 34 -> 33 ; +33 [label="33: BinaryOperatorStmt: Assign \n *&z:int =42 [line 51]\n NULLIFY(&z,false); [line 51]\n APPLY_ABSTRACTION; [line 51]\n " shape="box"] + + + 33 -> 26 ; +32 [label="32: Prune (false branch) \n PRUNE(((n$8 == 2) == 0), false); [line 53]\n " shape="invhouse"] + + + 32 -> 29 ; + 32 -> 30 ; +31 [label="31: Prune (true branch) \n PRUNE(((n$8 == 2) != 0), true); [line 53]\n APPLY_ABSTRACTION; [line 53]\n " shape="invhouse"] + + + 31 -> 26 ; +30 [label="30: Prune (false branch) \n PRUNE(((n$8 == 3) == 0), false); [line 54]\n " shape="invhouse"] + + + 30 -> 28 ; +29 [label="29: Prune (true branch) \n PRUNE(((n$8 == 3) != 0), true); [line 54]\n APPLY_ABSTRACTION; [line 54]\n " shape="invhouse"] + + + 29 -> 26 ; +28 [label="28: DefaultStmt_placeholder \n APPLY_ABSTRACTION; [line 45]\n " shape="box"] + + + 28 -> 35 ; +27 [label="27: Switch_stmt \n n$8=*&value:int [line 36]\n NULLIFY(&value,false); [line 36]\n " shape="box"] + + + 27 -> 40 ; + 27 -> 41 ; +26 [label="26: Return Stmt \n *&return:int =0 [line 56]\n REMOVE_TEMPS(n$8); [line 56]\n APPLY_ABSTRACTION; [line 56]\n " shape="box"] + + + 26 -> 25 ; +25 [label="25: Exit m2 \n " color=yellow style=filled] + + +24 [label="24: Start m2\nFormals: \nLocals: value:int x:int z:int something:int \n DECLARE_LOCALS(&return,&value,&x,&z,&something); [line 33]\n NULLIFY(&something,false); [line 33]\n NULLIFY(&value,false); [line 33]\n NULLIFY(&x,false); [line 33]\n NULLIFY(&z,false); [line 33]\n " color=yellow style=filled] + + + 24 -> 45 ; +23 [label="23: DeclStmt \n *&value:int =0 [line 10]\n APPLY_ABSTRACTION; [line 10]\n " shape="box"] + + + 23 -> 4 ; +22 [label="22: DeclStmt \n *&x:int =1 [line 13]\n NULLIFY(&x,false); [line 13]\n " shape="box"] + + + 22 -> 21 ; +21 [label="21: Call _fun_printf \n n$7=_fun_printf(\"(out)HELLO WORLD!\":char *) [line 14]\n REMOVE_TEMPS(n$7); [line 14]\n " shape="box"] + + + 21 -> 20 ; +20 [label="20: BinaryOperatorStmt: Assign \n n$6=*&value:int [line 15]\n *&x:int =(n$6 + 1) [line 15]\n REMOVE_TEMPS(n$6); [line 15]\n NULLIFY(&x,false); [line 15]\n APPLY_ABSTRACTION; [line 15]\n " shape="box"] + + + 20 -> 17 ; +19 [label="19: Prune (false branch) \n PRUNE(((n$2 == 0) == 0), false); [line 16]\n " shape="invhouse"] + + + 19 -> 15 ; + 19 -> 16 ; +18 [label="18: Prune (true branch) \n PRUNE(((n$2 == 0) != 0), true); [line 16]\n APPLY_ABSTRACTION; [line 16]\n " shape="invhouse"] + + + 18 -> 17 ; +17 [label="17: Call _fun_printf \n n$5=_fun_printf(\"(0)HELLO WORLD!\":char *) [line 17]\n REMOVE_TEMPS(n$5); [line 17]\n " shape="box"] + + + 17 -> 8 ; +16 [label="16: Prune (false branch) \n PRUNE(((n$2 == 1) == 0), false); [line 19]\n " shape="invhouse"] + + + 16 -> 12 ; + 16 -> 13 ; +15 [label="15: Prune (true branch) \n PRUNE(((n$2 == 1) != 0), true); [line 19]\n " shape="invhouse"] + + + 15 -> 14 ; +14 [label="14: Call _fun_printf \n n$4=_fun_printf(\"(1)HELLO WORLD!\":char *) [line 20]\n REMOVE_TEMPS(n$4); [line 20]\n APPLY_ABSTRACTION; [line 20]\n " shape="box"] + + + 14 -> 4 ; +13 [label="13: Prune (false branch) \n PRUNE(((n$2 == 2) == 0), false); [line 22]\n " shape="invhouse"] + + + 13 -> 10 ; +12 [label="12: Prune (true branch) \n PRUNE(((n$2 == 2) != 0), true); [line 22]\n APPLY_ABSTRACTION; [line 22]\n " shape="invhouse"] + + + 12 -> 11 ; +11 [label="11: Call _fun_printf \n n$3=_fun_printf(\"(2/def)HELLO WORLD!\":char *) [line 11]\n REMOVE_TEMPS(n$3); [line 11]\n APPLY_ABSTRACTION; [line 11]\n " shape="box"] + + + 11 -> 4 ; +10 [label="10: DefaultStmt_placeholder \n APPLY_ABSTRACTION; [line 23]\n " shape="box"] + + + 10 -> 11 ; +9 [label="9: Switch_stmt \n n$2=*&value:int [line 12]\n " shape="box"] + + + 9 -> 18 ; + 9 -> 19 ; +8 [label="8: Call _fun_printf \n n$1=_fun_printf(\"(after_switch)HELLO WORLD!\":char *) [line 27]\n REMOVE_TEMPS(n$1,n$2); [line 27]\n APPLY_ABSTRACTION; [line 27]\n " shape="box"] + + + 8 -> 4 ; +7 [label="7: Prune (false branch) \n PRUNE(((n$0 < 10) == 0), false); [line 11]\n REMOVE_TEMPS(n$0); [line 11]\n " shape="invhouse"] + + + 7 -> 3 ; +6 [label="6: Prune (true branch) \n PRUNE(((n$0 < 10) != 0), true); [line 11]\n REMOVE_TEMPS(n$0); [line 11]\n " shape="invhouse"] + + + 6 -> 9 ; +5 [label="5: BinaryOperatorStmt: LT \n n$0=*&value:int [line 11]\n " shape="box"] + + + 5 -> 6 ; + 5 -> 7 ; +4 [label="4: + \n " ] + + + 4 -> 5 ; +3 [label="3: Return Stmt \n NULLIFY(&value,false); [line 29]\n *&return:int =0 [line 29]\n APPLY_ABSTRACTION; [line 29]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit m1 \n " color=yellow style=filled] + + +1 [label="1: Start m1\nFormals: \nLocals: value:int x:int \n DECLARE_LOCALS(&return,&value,&x); [line 8]\n NULLIFY(&value,false); [line 8]\n NULLIFY(&x,false); [line 8]\n " color=yellow style=filled] + + + 1 -> 23 ; +} diff --git a/infer/tests/codetoanalyze/cpp/frontend/namespace/namespace.cpp b/infer/tests/codetoanalyze/cpp/frontend/namespace/namespace.cpp new file mode 100644 index 000000000..c42951621 --- /dev/null +++ b/infer/tests/codetoanalyze/cpp/frontend/namespace/namespace.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +#include +using namespace std; + +namespace foo +{ + typedef struct { int a; int b;} my_record; + int value() { return 5; } + + class Rectangle { + int width, height; + public: + void set_values (int,int); + int area (void); + } ; +} + +namespace bar +{ + const double pi = 3.1416; + double value() { return 2*pi; } + + class Rectangle { + int width, height; + public: + void set_values (int,int); + int area (void); + } rect; + +} + +int main () { + + int i; + double j; + + foo::my_record x; + + bar::Rectangle rect1; + rect1.set_values (3,4); + + foo::Rectangle rect2; + rect2.set_values (7,10); + + + x.a = 10; + i = foo::value(); + i = bar::value(); + j = bar::pi; + return 0; +} diff --git a/infer/tests/codetoanalyze/cpp/frontend/namespace/namespace.dot b/infer/tests/codetoanalyze/cpp/frontend/namespace/namespace.dot new file mode 100644 index 000000000..e694a6dff --- /dev/null +++ b/infer/tests/codetoanalyze/cpp/frontend/namespace/namespace.dot @@ -0,0 +1,24 @@ +digraph iCFG { +6 [label="6: Return Stmt \n n$0=*&#GB$pi:double [line -1]\n *&return:double =(2 * n$0) [line -1]\n REMOVE_TEMPS(n$0); [line -1]\n APPLY_ABSTRACTION; [line -1]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: Exit value \n " color=yellow style=filled] + + +4 [label="4: Start value\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 25]\n " color=yellow style=filled] + + + 4 -> 6 ; +3 [label="3: Return Stmt \n *&return:int =5 [line -1]\n APPLY_ABSTRACTION; [line -1]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit value \n " color=yellow style=filled] + + +1 [label="1: Start value\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 12]\n " color=yellow style=filled] + + + 1 -> 3 ; +} diff --git a/infer/tests/codetoanalyze/java/.inferconfig b/infer/tests/codetoanalyze/java/.inferconfig new file mode 100644 index 000000000..f5e679a23 --- /dev/null +++ b/infer/tests/codetoanalyze/java/.inferconfig @@ -0,0 +1,16 @@ +{ + "never_returning_null": [ + { + "language": "Java", + "source_contains": "_AUTOMATICALLY_GENERATED_" + }, + { + "language": "Java", + "class": "codetoanalyze.java.infer.SomeLibrary", + "method": "get" + } + ], + "infer_blacklist_files_containing": [ + "@generated" + ] +} diff --git a/infer/tests/codetoanalyze/java/BUCK b/infer/tests/codetoanalyze/java/BUCK new file mode 100644 index 000000000..d02ee457e --- /dev/null +++ b/infer/tests/codetoanalyze/java/BUCK @@ -0,0 +1,8 @@ +export_file( + name = 'inferconfig', + src = '.inferconfig', + out = '.inferconfig', + visibility = [ + 'PUBLIC' + ] +) diff --git a/infer/tests/codetoanalyze/java/checkers/BUCK b/infer/tests/codetoanalyze/java/checkers/BUCK new file mode 100644 index 000000000..869420dad --- /dev/null +++ b/infer/tests/codetoanalyze/java/checkers/BUCK @@ -0,0 +1,40 @@ +sources = glob(['**/*.java']) + +dependencies = [ + '//infer/lib/java/android:android', +] + +java_library( + name = 'checkers', + srcs = sources, + deps = dependencies, + visibility = [ + 'PUBLIC' + ] +) + +out = 'out' +clean_cmd = ' '.join(['rm', '-rf', out]) +classpath = ':'.join([('$(classpath ' + path + ')') for path in dependencies]) +infer_cmd = ' '.join([ + 'infer', + '-o', out, + '-a', 'checkers', + '--', + 'javac', + '-cp', classpath, + '$SRCS', +]) +copy_cmd = ' '.join(['cp', out + '/report.csv', '$OUT']) +command = ' && '.join([clean_cmd, infer_cmd, copy_cmd]) + +genrule( + name = 'analyze', + srcs = sources, + out = 'report.csv', + cmd = command, + deps = dependencies + [':checkers'], + visibility = [ + 'PUBLIC', + ] +) diff --git a/infer/tests/codetoanalyze/java/checkers/ImmutableCast.java b/infer/tests/codetoanalyze/java/checkers/ImmutableCast.java new file mode 100644 index 000000000..f4f57ec32 --- /dev/null +++ b/infer/tests/codetoanalyze/java/checkers/ImmutableCast.java @@ -0,0 +1,29 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package codetoanalyze.java.checkers; + +import com.google.common.collect.ImmutableList; + +import java.util.ArrayList; +import java.util.List; + +public class ImmutableCast { + + ImmutableList immutableList = ImmutableList.of("a", "b", "c"); + + List badCastFromField() { + return immutableList; + } + + List badCast(ImmutableList list) { + return list; + } + + List goodCast(ImmutableList list) { + return new ArrayList(list); + } + +} diff --git a/infer/tests/codetoanalyze/java/eradicate/ActivityFieldNotInitialized.java b/infer/tests/codetoanalyze/java/eradicate/ActivityFieldNotInitialized.java new file mode 100644 index 000000000..399598a50 --- /dev/null +++ b/infer/tests/codetoanalyze/java/eradicate/ActivityFieldNotInitialized.java @@ -0,0 +1,17 @@ +package codetoanalyze.java.eradicate; + +import android.app.Activity; +import android.os.Bundle; + +public class ActivityFieldNotInitialized { + + class BadActivityWithOnCreate extends Activity { + + private String field; + + protected void onCreate(Bundle bundle) { + } + + } + +} diff --git a/infer/tests/codetoanalyze/java/eradicate/BUCK b/infer/tests/codetoanalyze/java/eradicate/BUCK new file mode 100644 index 000000000..dc7b9a5e1 --- /dev/null +++ b/infer/tests/codetoanalyze/java/eradicate/BUCK @@ -0,0 +1,43 @@ +sources = glob(['**/*.java']) + +dependencies = [ + '//infer/annotations:annotations', + '//infer/lib/java/android:android', + '//dependencies/java/jsr-305:jsr-305', +] + +java_library( + name = 'eradicate', + srcs = sources, + deps = dependencies, + visibility = [ + 'PUBLIC' + ] +) + +out = 'out' +clean_cmd = ' '.join(['rm', '-rf', out]) +classpath = ':'.join([('$(classpath ' + path + ')') for path in dependencies]) +env_cmd = ' '.join(['export', 'ERADICATE_RETURN_OVER_ANNOTATED=1']) +infer_cmd = ' '.join([ + 'infer', + '-o', out, + '-a', 'eradicate', + '--', + 'javac', + '-cp', classpath, + '$SRCS', +]) +copy_cmd = ' '.join(['cp', out + '/report.csv', '$OUT']) +command = ' && '.join([clean_cmd, env_cmd, infer_cmd, copy_cmd]) + +genrule( + name = 'analyze', + srcs = sources, + out = 'report.csv', + cmd = command, + deps = dependencies + [':eradicate'], + visibility = [ + 'PUBLIC', + ] +) diff --git a/infer/tests/codetoanalyze/java/eradicate/FieldNotInitialized.java b/infer/tests/codetoanalyze/java/eradicate/FieldNotInitialized.java new file mode 100644 index 000000000..e4d4422f5 --- /dev/null +++ b/infer/tests/codetoanalyze/java/eradicate/FieldNotInitialized.java @@ -0,0 +1,21 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package codetoanalyze.java.eradicate; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class FieldNotInitialized { + + @Nullable + String x; + String y; + @Nonnull + String z; // Means: assume it's initialized to nonnull value somewhere else. + + FieldNotInitialized() { + } +} diff --git a/infer/tests/codetoanalyze/java/eradicate/FieldNotNullable.java b/infer/tests/codetoanalyze/java/eradicate/FieldNotNullable.java new file mode 100644 index 000000000..cac94b801 --- /dev/null +++ b/infer/tests/codetoanalyze/java/eradicate/FieldNotNullable.java @@ -0,0 +1,361 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package codetoanalyze.java.eradicate; + +import com.facebook.infer.annotation.Assertions; +import com.facebook.infer.annotation.Initializer; +import com.facebook.infer.annotation.Present; +import com.facebook.infer.annotation.SuppressFieldNotInitialized; +import com.google.common.base.Optional; + +import javax.annotation.Nullable; + +import android.app.Activity; +import android.os.Bundle; + +abstract class A { + final String fld; + + A(String s) { + this.fld = s; + } +} + +public class FieldNotNullable extends A { + + @Nullable + String x; + String y; + String fld; // Shadow the field defined in A + String static_s = null; // Static initializer error + + FieldNotNullable(String s) { + super(s); + x = null; + y = s; + this.fld = s; + } + + void setXNull() { + x = null; + } + + void setXNullable(@Nullable String s) { + x = s; + } + + void setYNull() { + y = null; + } + + void setYNullable(@Nullable String s) { + y = s; + } + + FieldNotNullable(Integer n) { + super(""); + this.fld = ""; + y = x == null ? "abc" : "def"; + } +} + +class MixedInitializers extends Activity { + + private String field1 = "1"; + private String field2; + private String field3; + + MixedInitializers() { + field2 = "2"; + } + + protected void onCreate(Bundle bundle) { + field3 = "3"; + } + +} + + +class TestInitializerBuilder { + String required1; + String required2; + @Nullable String optional; + + // No FIELD_NOT_INITIALIZED error should be reported, because of the @Initializer annotations. + TestInitializerBuilder() { + } + + // This is an initializer and must always be called before build(). + @Initializer TestInitializerBuilder setRequired1(String required1) { + this.required1 = required1; + return this; + } + + // This is an initializer and must always be called before build(). + @Initializer TestInitializerBuilder setRequired2(String required2) { + this.required2 = required2; + return this; + } + + TestInitializerBuilder setOptional(String optional) { + this.optional = optional; + return this; + } + + TestInitializer build() { + // Fail hard if the required fields are not initialzed + Assertions.assertCondition(required1 != null && required2 != null); + + return new TestInitializer(this); + } +} + +class TestInitializer { + String required1; // should always be set + String required2; // should always be set + @Nullable String optional; // optionally set + + TestInitializer (TestInitializerBuilder b) { + required1 = b.required1; + required2 = b.required2; + optional = b.optional; + } + + static void testInitializerClientA() { + TestInitializerBuilder b = new TestInitializerBuilder(); + b.setRequired1("hello"); + b.setRequired2("world"); + TestInitializer x = b.build(); + } + + static void testInitializerClientB() { + TestInitializerBuilder b = new TestInitializerBuilder(); + b.setRequired1("a"); + b.setRequired2("b"); + b.setOptional("c"); + TestInitializer x = b.build(); + } +} + + +class NestedFieldAccess { + + class C { + @Nullable + String s; + } + + class CC { + @Nullable + C c; + } + + class CCC { + @Nullable + CC cc; + } + + + public class Test { + @Nullable + String s; + C myc; + + Test() { + myc = new C(); + } + + void test() { + if (s != null) { + int n = s.length(); + } + } + + void test1() { + if (myc.s != null) { + int n = myc.s.length(); + } + } + + void test2(C c) { + if (c.s != null) { + int n = c.s.length(); + } + } + + void test2_local() { + C c = new C(); + if (c.s != null) { + int n = c.s.length(); + } + } + + void test3(CC cc) { + if (cc.c != null && cc.c.s != null) { + int n = cc.c.s.length(); + } + } + + void test4(CCC ccc) { + if (ccc.cc != null && ccc.cc.c != null && ccc.cc.c.s != null) { + int n = ccc.cc.c.s.length(); + } + } + + void test5(@Nullable CCC ccc) { + if (ccc == null || ccc.cc == null || + ccc.cc.c == null || ccc.cc.c.s == null) { + } else { + int n = ccc.cc.c.s.length(); + } + } + } + + class TestPresentAnnotationBasic { + void testBasicConditional(Optional o) { + if (o.isPresent()) { + o.get(); + } + } + + Optional absent = Optional.absent(); + @Present Optional present = Optional.of("abc"); + + @Present Optional returnPresent() { + if (absent.isPresent()) { + return absent; + } + else return Optional.of("abc"); + } + + void expectPresent(@Present Optional x) { + } + + void bar() { + expectPresent(present); + String s; + s = returnPresent().get(); + s = present.get(); + + Assertions.assertCondition(absent.isPresent()); + expectPresent(absent); + } + } + + class TestPresentFieldOfInnerClass { + class D { + @SuppressFieldNotInitialized Optional s; + } + + class D1 { + // Different bytecode generated when the field is private + @SuppressFieldNotInitialized private Optional s; + } + + void testD(D d) { + if (d.s.isPresent()) { + d.s.get(); + } + } + + void testD1(D1 d1) { + if (d1.s.isPresent()) { + d1.s.get(); + } + } + + void testD1Condition(D1 d1) { + Assertions.assertCondition(d1.s.isPresent()); + d1.s.get(); + } + } + + class TestFunctionsIdempotent { + @Nullable String s; + String dontAssignNull; + + TestFunctionsIdempotent() { + dontAssignNull = ""; + } + + @Nullable String getS(int n) { + return s; + } + + TestFunctionsIdempotent getSelf() { + return this; + } + + void FlatOK1(TestFunctionsIdempotent x) { + if(getS(3) != null) { + dontAssignNull = getS(3); + } + } + + void FlatOK2(TestFunctionsIdempotent x) { + if(x.getS(3) != null) { + dontAssignNull = x.getS(3); + } + } + + void FlatBAD1(TestFunctionsIdempotent x) { + if(x.getS(3) != null) { + dontAssignNull = getS(3); + } + } + + void FlatBAD2(TestFunctionsIdempotent x) { + if(x.getS(3) != null) { + dontAssignNull = x.getS(4); + } + } + + void NestedOK1() { + if(getSelf().getS(3) != null) { + dontAssignNull = getSelf().getS(3); + } + } + + void NestedOK2() { + if(getSelf().getSelf().getS(3) != null) { + dontAssignNull = getSelf().getSelf().getS(3); + } + } + + void NestedBAD1() { + if(getSelf().getS(3) != null) { + dontAssignNull = getSelf().getS(4); + } + } + + void NestedBAD2() { + if(getS(3) != null) { + dontAssignNull = getSelf().getS(3); + } + } + + void NestedBAD3() { + if(getSelf().getSelf().getS(3) != null) { + dontAssignNull = getSelf().getS(3); + } + } + } + + class TestContainsKey { + void testMapContainsKey (java.util.Map m) { + if (m.containsKey(3)) { + m.get(3).isEmpty(); + } + } + + void testImmutableMapContainsKey (com.google.common.collect.ImmutableMap m) { + if (m.containsKey(3)) { + m.get(3).isEmpty(); + } + } + } + +} diff --git a/infer/tests/codetoanalyze/java/eradicate/InconsistentSubclassAnnotation.java b/infer/tests/codetoanalyze/java/eradicate/InconsistentSubclassAnnotation.java new file mode 100644 index 000000000..a4a798428 --- /dev/null +++ b/infer/tests/codetoanalyze/java/eradicate/InconsistentSubclassAnnotation.java @@ -0,0 +1,88 @@ +package codetoanalyze.java.eradicate; + +import javax.annotation.Nullable; + +class SubclassExample { + + class T { + public void f() { + } + } + + class A { + + public T foo() { + return new T(); + } + + public + @Nullable + T bar() { + return null; + } + + public void deref(@Nullable T t) { + if (t != null) { + t.f(); + } + } + + public void noDeref(T t) { + } + + } + + class B extends A { + + public + @Nullable + T foo() { + return null; + } + + public T bar() { + return new T(); + } + + } + + interface I { + public T baz(); + } + + class C implements I { + + public + @Nullable + T baz() { + return null; + } + } + + class D extends A { + + public void deref(T t) { + t.f(); + } + + public void noDeref(@Nullable T t) { + if (t != null) { + t.f(); + } + } + + } +} + +public class InconsistentSubclassAnnotation { + + public static void callFromSuperclass(SubclassExample.A a) { + SubclassExample.T t = a.foo(); + t.f(); + } + + public static void callWithNullableParam(SubclassExample.A a, @Nullable SubclassExample.T t) { + a.deref(t); + } + +} diff --git a/infer/tests/codetoanalyze/java/eradicate/LibraryCalls.java b/infer/tests/codetoanalyze/java/eradicate/LibraryCalls.java new file mode 100644 index 000000000..42e1767db --- /dev/null +++ b/infer/tests/codetoanalyze/java/eradicate/LibraryCalls.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-present Facebook, Inc. + */ +package codetoanalyze.java.eradicate; + +import java.lang.ref.PhantomReference; +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.util.concurrent.atomic.AtomicReference; + +public class LibraryCalls { + + String badReferenceDereference(Reference ref) { + return ref.get().toString(); + } + + String badWeakReferenceDereference(WeakReference ref) { + return ref.get().toString(); + } + + String badPhantomReferenceDereference(PhantomReference ref) { + return ref.get().toString(); + } + + String badSoftReferenceDereference(SoftReference ref) { + return ref.get().toString(); + } + + String badAtomicReferenceDereference(AtomicReference ref) { + return ref.get().toString(); + } + +} diff --git a/infer/tests/codetoanalyze/java/eradicate/NullFieldAccess.java b/infer/tests/codetoanalyze/java/eradicate/NullFieldAccess.java new file mode 100644 index 000000000..f69d5a272 --- /dev/null +++ b/infer/tests/codetoanalyze/java/eradicate/NullFieldAccess.java @@ -0,0 +1,61 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package codetoanalyze.java.eradicate; + +import javax.annotation.Nullable; + +public class NullFieldAccess { + class C { + int n; + } + + interface I { + @Nullable + C c = null; + } + + + @Nullable + C x; + C y; + static final + @Nullable + C z = null; + + NullFieldAccess() { + y = new C(); + } + + int useX() { + C c = x; + return c.n; + } + + int useY() { + C c = y; + return c.n; + } + + int useZ() { + C c = z; + return c.n; + } + + int useInterface(I i) { + C c = i.c; + return c.n; + } + + @Nullable Object[] objects; + + int arrayLength() { + return objects.length; + } + + Object arrayAccess() { + return objects[0]; + } +} diff --git a/infer/tests/codetoanalyze/java/eradicate/NullMethodCall.java b/infer/tests/codetoanalyze/java/eradicate/NullMethodCall.java new file mode 100644 index 000000000..a3f268366 --- /dev/null +++ b/infer/tests/codetoanalyze/java/eradicate/NullMethodCall.java @@ -0,0 +1,232 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package codetoanalyze.java.eradicate; + +import com.google.common.base.Preconditions; + +import javax.annotation.Nullable; +import com.facebook.infer.annotation.Assertions; + + +public class NullMethodCall { + + void callOnNull() { + String s = null; + int n = s.length(); + } + + void callOnEmptyString() { + String s = ""; + int n = s.length(); + } + + void callAfterYodaCondition(@Nullable String s) { + if (null != s) { + int n = s.length(); + } + } + + int objectLength(@Nullable Object o) { + if (o instanceof String) { + String s = (String) o; + return s.length(); // OK: s cannot be null because of instanceof + } + return 0; + } + + int testCheckState(@Nullable String s1, @Nullable String s2) { + Preconditions.checkState(s1 != null && s2 != null, "bad"); + return s1.length() + s2.length(); + } + + int testPrivateStaticInnerClassField() { + String s; + S.sfld = "abc"; + s = S.sfld; + return s.length(); + } + + private static class S { + private static + @Nullable + String sfld; + } + + @Nullable + String fld; + private + @Nullable + String pfld; + + public class Inner { + int outerField() { + String s = fld; + return s.length(); + } + + int outerFieldInitialized() { + fld = "abc"; + String s = fld; + return s.length(); + } + + int outerPrivateField() { + String s = pfld; + return s.length(); + } + + int outerPrivateFieldInitialized() { + pfld = "abc"; + String s = pfld; + return s.length(); + } + + int outerPrivateFieldCheckNotNull() { + Preconditions.checkNotNull(pfld); + String s = pfld; + return s.length(); + } + + int outerPrivateFieldCheckState() { + Preconditions.checkState(pfld != null); + String s = pfld; + return s.length(); + } + + int outerPrivateFieldAssertNotNull() { + Assertions.assertNotNull(pfld); + String s = pfld; + return s.length(); + } + + int outerPrivateFieldAssumeNotNull() { + Assertions.assumeNotNull(pfld); + String s = pfld; + return s.length(); + } + + int outerPrivateFieldAssertCondition() { + Assertions.assertCondition(pfld != null, "explanation"); + String s = pfld; + return s.length(); + } + + int outerPrivateFieldAssumeCondition() { + Assertions.assumeCondition(pfld != null, "explanation"); + String s = pfld; + return s.length(); + } + + int outerPrivateFieldCheckStateYoda() { + Preconditions.checkState(null != pfld); + String s = pfld; + return s.length(); + } + + String outerFieldGuardPrivate() { + if (pfld != null) return pfld.toString(); + return ""; + } + + String outerFieldGuardPublic() { + if (fld != null) return fld.toString(); + return ""; + } + + public class InnerInner { + int outerouterPrivateFieldInitialized() { + pfld = "abc"; + String s = pfld; + return s.length(); + } + } + } + + @Nullable + String getNullable() { + return null; + } + + void testVariableAssigmentInsideConditional() { + String s = null; + if ((s = getNullable()) != null) { + int n = s.length(); + } + } + + void testFieldAssigmentInsideConditional() { + if ((fld = getNullable()) != null) { + int n = fld.length(); + } + } + + String abc = "abc"; + + void testFieldAssignmentIfThenElse(String name) { + String s = (name.length() == 0) ? null : abc; + int n = s.length(); + } + + static String throwsExn() throws java.io.IOException { + throw new java.io.IOException(); + } + + void testExceptionPerInstruction(int z) throws java.io.IOException { + String s = null; + + try { + s = throwsExn(); + } finally { + int n = s.length(); + } + } + + public class InitializeAndExceptions { + String s; + + String bad() throws java.io.IOException { + throw new java.io.IOException(); + } + + InitializeAndExceptions() throws java.io.IOException { + s = bad(); // should not report field not initialized + } + } + + public class InitializeViaPrivateMethod { + String name; + + private void reallyInitName(String s) { + name = s; + } + + private void initName(String s) { + reallyInitName(s); + } + + InitializeViaPrivateMethod() { + initName("abc"); + } + } + + class CheckNotNullVararg { + void checkNotNull(String msg, Object ... objects) { + } + + void testCheckNotNullVaratg(@Nullable String s1, @Nullable String s2) { + checkNotNull("hello", s1, s2); + s1.isEmpty(); + s2.isEmpty(); + } + + void testRepeatedCheckNotNull(@Nullable String s) { + checkNotNull("abc", s); + checkNotNull("abc", s.toString()); + s.toString().isEmpty(); + } + } +} + diff --git a/infer/tests/codetoanalyze/java/eradicate/ParameterNotNullable.java b/infer/tests/codetoanalyze/java/eradicate/ParameterNotNullable.java new file mode 100644 index 000000000..e655c211d --- /dev/null +++ b/infer/tests/codetoanalyze/java/eradicate/ParameterNotNullable.java @@ -0,0 +1,63 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package codetoanalyze.java.eradicate; + +import javax.annotation.Nullable; + +import android.annotation.SuppressLint; + +public class ParameterNotNullable { + + boolean field = false; + + ParameterNotNullable() { + testPrimitive(field); + } + + void testPrimitive(boolean f) { + } + + void test(String s) { + int n = s.length(); + } + + void testN(@Nullable String s) { + int n = s != null ? s.length() : 0; + } + + void callNull() { + String s = null; + test(s); + } + + @SuppressLint("ERADICATE_PARAMETER_NOT_NULLABLE") + void callNullSuppressed() { + String s = null; + test(s); + } + + void callNullable(@Nullable String s) { + test(s); + } + + void callNullOK() { + String s = null; + testN(s); + } + + void callNullableOK(@Nullable String s) { + testN(s); + } + + private ParameterNotNullable(@Nullable String s) { + } + + class Builder { + ParameterNotNullable getEradicateParameterNotNullable() { + return new ParameterNotNullable(null); + } + } +} diff --git a/infer/tests/codetoanalyze/java/eradicate/ReturnNotNullable.java b/infer/tests/codetoanalyze/java/eradicate/ReturnNotNullable.java new file mode 100644 index 000000000..ad6b5e974 --- /dev/null +++ b/infer/tests/codetoanalyze/java/eradicate/ReturnNotNullable.java @@ -0,0 +1,91 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package codetoanalyze.java.eradicate; + +import com.google.common.base.Optional; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ReturnNotNullable { + + void returnvoid() { + // No warning here. + } + + Void returnVoid() { + // This is OK too. + return null; + } + + String returnNull() { + return null; + } + + String returnNullable(@Nullable String s) { + return s; + } + + @Nonnull + String returnNonnull() { + return "abc"; + } + + @Nullable + String returnNullOK() { + return null; + } + + @Nullable + String returnNullableOK(@Nullable String s) { + return s; + } + + String throwException(@Nullable Exception e, boolean bad) throws Exception { + if (bad) { + throw (e); // no ERADICATE_RETURN_NOT_NULLABLE should be reported + } + return "OK"; + } + + @Nullable + String redundantEq() { + String s = returnNonnull(); + int n = s == null ? 0 : s.length(); + return s; + } + + @Nullable + String redundantNeq() { + String s = returnNonnull(); + int n = s != null ? 0 : s.length(); + return s; + } + + @Nonnull + BufferedReader nn(BufferedReader br) { + return br; + } + + void tryWithResources(String path) { + try (BufferedReader br = nn(new BufferedReader(new FileReader(path)))) { + } // no condition redundant should be reported on this line + catch (IOException e) { + } + } + + /* + Check that orNull is modelled and RETURN_OVER_ANNOTATED is not returned. + */ + @Nullable + String testOptional(Optional os) { + return os.orNull(); + } +} diff --git a/infer/tests/codetoanalyze/java/eradicate/SuppressedFieldNotInitializedExample.java b/infer/tests/codetoanalyze/java/eradicate/SuppressedFieldNotInitializedExample.java new file mode 100644 index 000000000..de21a42ac --- /dev/null +++ b/infer/tests/codetoanalyze/java/eradicate/SuppressedFieldNotInitializedExample.java @@ -0,0 +1,29 @@ +/* + * Copyright 2013-present Facebook, Inc. + */ +package codetoanalyze.java.eradicate; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import android.annotation.SuppressLint; + +@Retention(RetentionPolicy.CLASS) +@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD}) +@interface SuppressFieldNotInitialized { +} + +public class SuppressedFieldNotInitializedExample { + + @SuppressLint("eradicate-field-not-initialized") + String iKnowBetter; + + @SuppressFieldNotInitialized + String annotationSuppressed; + + SuppressedFieldNotInitializedExample() { + } + +} diff --git a/infer/tests/codetoanalyze/java/errors/CursorNPEs.java b/infer/tests/codetoanalyze/java/errors/CursorNPEs.java new file mode 100644 index 000000000..d71350e6d --- /dev/null +++ b/infer/tests/codetoanalyze/java/errors/CursorNPEs.java @@ -0,0 +1,107 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package com.facebook.infer.tests.codetoanalyze.java.errors; + + +import android.app.DownloadManager; +import android.content.ContentProviderClient; +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteQueryBuilder; +import android.os.RemoteException; +import android.provider.MediaStore; + +public class CursorLeaks { + + public int cursorNPEfromQuery(SQLiteDatabase sqLiteDatabase) { + Cursor cursor = sqLiteDatabase.query( + "events", null, + null, null, null, null, null); + try { + return cursor.getCount(); + } finally { + cursor.close(); + } + } + + public int cursorNotClosed(SQLiteDatabase sqLiteDatabase) { + Cursor cursor = sqLiteDatabase.query( + "events", null, + null, null, null, null, null); + if (cursor == null) { + return 0; + } else { + return cursor.getCount(); + } + } + + Context mContext; + ContentResolver mContentResolver; + + public void cursorFromContentResolverNPE(String customClause) { + String[] projection = {"COUNT(*)"}; + + String selectionClause = selectionClause = customClause; + + Cursor cursor = mContext.getContentResolver().query( + null, + projection, + selectionClause, + null, + null); + + cursor.close(); + } + + + public void cursorFromMediaNPE() { + Cursor cursor = MediaStore.Images.Media.query( + mContentResolver, null, null, null, null, null); + cursor.close(); + } + } + + private void cursorFromSQLiteQueryBuilderNPE() { + SQLiteQueryBuilder builder = new SQLiteQueryBuilder(); + builder.setTables(""); + Cursor cursor = builder.query(null, null, "", null, null, null, null); + cursor.close(); + } + + public int cursorFromDownloadManagerNPE(DownloadManager downloadManager) { + DownloadManager.Query query = new DownloadManager.Query(); + Cursor cursor = null; + try { + cursor = downloadManager.query(query); + return cursor.getColumnIndex(DownloadManager.COLUMN_STATUS); + } + } finally { + if (cursor != null) cursor.close(); + } + } + + private void cursorFromContentProviderClient() { + ContentProviderClient contentProviderClient = + mContentResolver.acquireContentProviderClient(""); + if (contentProviderClient != null) { + Cursor cursor = null; + try { + try { + cursor = contentProviderClient.query(null, null, null, null, null); + cursor.moveToFirst(); + } catch (RemoteException ex) { + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + } + +} diff --git a/infer/tests/codetoanalyze/java/harness/BasicHarnessActivity.java b/infer/tests/codetoanalyze/java/harness/BasicHarnessActivity.java new file mode 100644 index 000000000..b7a4fcbd5 --- /dev/null +++ b/infer/tests/codetoanalyze/java/harness/BasicHarnessActivity.java @@ -0,0 +1,32 @@ +package codetoanalyze.java.harness; + +import android.app.Activity; +import android.os.Bundle; + +/* + * Test if harness generation understands basics of Activity lifecycle. + */ +public class BasicHarnessActivity extends Activity { + + public BasicHarnessActivity(BasicHarnessActivity a) { + } + + private Object mObj; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mObj = new Object(); + } + + @Override + public void onPause() { + mObj = null; + } + + @Override + public void onDestroy() { + String s = mObj.toString(); + } + +} diff --git a/infer/tests/codetoanalyze/java/harness/CallbackActivity.java b/infer/tests/codetoanalyze/java/harness/CallbackActivity.java new file mode 100644 index 000000000..8ac7c22cb --- /dev/null +++ b/infer/tests/codetoanalyze/java/harness/CallbackActivity.java @@ -0,0 +1,32 @@ +package codetoanalyze.java.harness; + +import android.app.Activity; +import android.os.Bundle; +import android.view.View; +import android.widget.Button; + +/* + * Test if harness generation knows how to call a callback defined in an inner class + */ +public class CallbackActivity extends Activity { + + private Object mObj; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Button btn = new Button(this.getApplicationContext()); + mObj = new Object(); + Button.OnClickListener listener = new Button.OnClickListener() { + + @Override + public void onClick(View v) { + // oops! what if I get nulled out later? + mObj.toString(); + } + }; + btn.setOnClickListener(listener); + mObj = null; + } + +} diff --git a/infer/tests/codetoanalyze/java/harness/FindViewByIdActivity.java b/infer/tests/codetoanalyze/java/harness/FindViewByIdActivity.java new file mode 100644 index 000000000..472880a51 --- /dev/null +++ b/infer/tests/codetoanalyze/java/harness/FindViewByIdActivity.java @@ -0,0 +1,35 @@ +package codetoanalyze.java.harness; + +import android.app.Activity; +import android.os.Bundle; +import android.view.View; +import android.widget.Button; + + +public class FindViewByIdActivity extends Activity { + + private MyView mView; + + @Override + public void onCreate(Bundle b) { + mView = (MyView) findViewById(-1); + // replacing the above line with this reveals the bug + // mView = new MyView(this.getApplicationContext()); + Button btn = new Button(this.getApplicationContext()); + Button.OnClickListener listener = new Button.OnClickListener() { + + @Override + public void onClick(View v) { + // oops! what if I get nulled out later? + mView.toString(); + } + }; + btn.setOnClickListener(listener); + } + + + @Override + public void onDestroy() { + mView = null; + } +} diff --git a/infer/tests/codetoanalyze/java/harness/MyView.java b/infer/tests/codetoanalyze/java/harness/MyView.java new file mode 100644 index 000000000..834f20bb0 --- /dev/null +++ b/infer/tests/codetoanalyze/java/harness/MyView.java @@ -0,0 +1,10 @@ +package codetoanalyze.java.harness; + +import android.content.Context; +import android.view.View; + +public class MyView extends View { + public MyView(Context ctx) { + super(ctx); + } +} diff --git a/infer/tests/codetoanalyze/java/harness/SubclassActivity.java b/infer/tests/codetoanalyze/java/harness/SubclassActivity.java new file mode 100644 index 000000000..6f7c76c74 --- /dev/null +++ b/infer/tests/codetoanalyze/java/harness/SubclassActivity.java @@ -0,0 +1,10 @@ +package codetoanalyze.java.harness; + +public class SubclassActivity extends SuperclassActivity { + + @Override + public void onPause() { + super.onPause(); + mObj = null; + } +} diff --git a/infer/tests/codetoanalyze/java/harness/SuperclassActivity.java b/infer/tests/codetoanalyze/java/harness/SuperclassActivity.java new file mode 100644 index 000000000..9b4bd5043 --- /dev/null +++ b/infer/tests/codetoanalyze/java/harness/SuperclassActivity.java @@ -0,0 +1,18 @@ +package codetoanalyze.java.harness; + +import android.app.Activity; + +/** + * If my subclasses null out mObj in an earlier lifecycle method, it will cause + * a NPE in onDestroy. + */ +public class SuperclassActivity extends Activity { + + Object mObj = new Object(); + + @Override + public void onDestroy() { + super.onDestroy(); + mObj.toString(); + } +} diff --git a/infer/tests/codetoanalyze/java/harness/TrickyParamsActivity.java b/infer/tests/codetoanalyze/java/harness/TrickyParamsActivity.java new file mode 100644 index 000000000..0d20fa54a --- /dev/null +++ b/infer/tests/codetoanalyze/java/harness/TrickyParamsActivity.java @@ -0,0 +1,31 @@ +package codetoanalyze.java.harness; + +import android.app.Activity; + +/* + * Test if type inhabitation can inhabit tricky types. + */ +public class TrickyParamsActivity extends Activity { + + private Object mObj; + + // we have to be able to inhabit all params or no methods from this + // class will be called by the harness + public TrickyParamsActivity( + Object o, int i, boolean b, char c, long l, + float f, double d, short sh, byte byt, + Object[] arr1, int[] arr2) { + this.mObj = new Object(); + } + + @Override + public void onPause() { + mObj = null; + } + + @Override + public void onDestroy() { + String s = mObj.toString(); + } + +} diff --git a/infer/tests/codetoanalyze/java/infer/AnalysisStops.java b/infer/tests/codetoanalyze/java/infer/AnalysisStops.java new file mode 100644 index 000000000..0bfe44426 --- /dev/null +++ b/infer/tests/codetoanalyze/java/infer/AnalysisStops.java @@ -0,0 +1,264 @@ +package codetoanalyze.java.infer; + +import java.util.Iterator; + +public class AnalysisStops { + + private native Object externalFunc(); + + public void skipPointerDerefMayCauseLocalFalseNegative() { + Object ret = externalFunc(); + ret.toString(); + int i = 1 / 0; + } + + private Object skipPointerDerefPreventsSpecInferenceRetObj() { + Object ret = externalFunc(); + ret.toString(); + return new Object(); + } + + public void skipPointerDerefMayCauseCalleeFalsePositive() { + Object o = skipPointerDerefPreventsSpecInferenceRetObj(); + o.toString(); + } + + private int skipPointerDerefPreventsSpecInferenceRetZero() { + Object ret = externalFunc(); + ret.toString(); + return 0; + } + + public void skipPointerDerefMayCauseCalleeFalseNegative() { + int ret = skipPointerDerefPreventsSpecInferenceRetZero(); + int i = 1 / ret; + } + + private void divideByParam(int i) { + int j = 1 / i; + } + + public void skipPointerDerefMayCauseInterprocFalseNegative() { + int i = skipPointerDerefPreventsSpecInferenceRetZero(); + divideByParam(i); + } + + private String castExternalPreventsSpecInference() { + return (String) externalFunc(); + } + + public void castFailureOnUndefinedObjMayCauseFalseNegative() { + castExternalPreventsSpecInference(); + int i = 1 / 0; + } + + public void callOnCastUndefinedObjMayCauseFalseNegative() { + String s = castExternalPreventsSpecInference(); + s.toString(); + int i = 1 / 0; + } + + private static class MyObj { + Object f; + MyObj rec; + int i; + + public int retOne() { + return 1; + } + + public int retZero() { + return 0; + } + } + + private native MyObj externalFunc2(); + + public void callOnUndefinedObjMayCauseFalseNegative() { + MyObj ret = externalFunc2(); + int i = 1 / ret.retZero(); + } + + public void callOnUndefinedObjMayCauseFalsePositive() { + MyObj ret = externalFunc2(); + int i = 1 / ret.retOne(); + } + + public void fieldWriteOnUndefinedObjMayCauseFalseNegative() { + MyObj ret = externalFunc2(); + ret.f = new Object(); + int i = 1 / 0; + } + + public void fieldWriteOnUndefinedObjMayCauseFalsePositive() { + MyObj ret = externalFunc2(); + ret.f = new Object(); + ret.f.toString(); + } + + public void fieldReadOnUndefinedObjMayCauseFalseNegative() { + MyObj ret = externalFunc2(); + Object o = ret.f; + int i = 1 / 0; + } + + public void fieldReadOnUndefinedObjMayCauseFalsePositive() { + MyObj ret = externalFunc2(); + Object o = ret.f; + o.toString(); + } + + public void recursiveAngelicTypesMayCauseFalseNegative() { + MyObj ret = externalFunc2(); + MyObj rec1 = ret.rec; + MyObj rec2 = rec1.rec; + int i = 1 / 0; + } + + public void recursiveAngelicTypesMayCauseFalsePositive() { + MyObj ret = externalFunc2(); + MyObj rec1 = ret.rec; + rec1.rec.toString(); + } + + public void infiniteMaterializationMayCauseFalseNegative(boolean b) { + MyObj rec = externalFunc2(); + while (b) { + rec = rec.rec; + } + int i = 1 / 0; + } + + public void infiniteMaterializationMayCauseFalsePositive(boolean b) { + MyObj rec = externalFunc2(); + while (b) { + rec = rec.rec; + } + rec.toString(); + } + + public void primitiveFieldOfAngelicObjMayCauseFalseNegative() { + MyObj ret = externalFunc2(); + if (ret.i == 0) { + int i = 1 / 0; + } else { + int i = 1 / 0; + } + } + + public void primitiveFieldOfAngelicObjMayCauseFalsePositive() { + MyObj ret = externalFunc2(); + if (ret.i != 0) { + int i = 1 / ret.i; + } + } + + public void heapFieldOfAngelicObjMayCauseFalseNegative() { + MyObj ret = externalFunc2(); + Object obj = ret.f; + if (obj == ret.f) { + int i = 1 / 0; + } + } + + public void heapFieldOfAngelicObjMayCauseFalsePositive() { + MyObj ret = externalFunc2(); + Object obj = ret.f; + if (obj != ret.f) { + int i = 1 / 0; + } + } + + public void fieldReadAferCastMayCauseFalseNegative(Iterator iter) { + MyObj ret = iter.next(); + Object obj = ret.f; + obj.toString(); + int i = ret.i; + if (i == 7) { + int j = 1 / 0; + } + } + + public void derefParam(MyObj obj) { + Object f = obj.f; + f.toString(); + } + + public void fieldReadInCalleeMayCauseFalsePositive() { + MyObj ret = externalFunc2(); + derefParam(ret); + } + + public void fieldReadInCalleeMayCauseFalseNegative() { + MyObj ret = externalFunc2(); + ret.f = null; + derefParam(ret); + } + + public void fieldReadInCalleeWithAngelicObjFieldMayCauseFalsePositive() { + MyObj ret = externalFunc2(); + derefParam(ret.rec); + } + + public void fieldReadInCalleeWithAngelicObjFieldMayCauseFalseNegative() { + MyObj ret = externalFunc2(); + ret.rec.f = null; + derefParam(ret.rec); + } + + public void accessPathOnParam(MyObj obj) { + MyObj ret = obj.rec; + Object f = ret.f; + f.toString(); + } + + public void accessPathInCalleeMayCauseFalsePositive() { + MyObj ret = externalFunc2(); + accessPathOnParam(ret); + } + + public void accessPathInCalleeMayCauseFalseNegative() { + MyObj ret = externalFunc2(); + ret.rec.f = null; + accessPathOnParam(ret); + } + + public void skipFunctionInLoopMayCauseFalseNegative() { + Object o = null; + for (int i = 0; i < 10; i++) { + externalFunc(); + } + o.toString(); + } + + // will fail to find error unless spec inference succeeds for all callees + public void specInferenceMayFailAndCauseFalseNegative(boolean b, Iterator iter) { + skipPointerDerefMayCauseLocalFalseNegative(); + skipPointerDerefPreventsSpecInferenceRetObj(); + skipPointerDerefPreventsSpecInferenceRetZero(); + skipPointerDerefMayCauseCalleeFalseNegative(); + skipPointerDerefMayCauseInterprocFalseNegative(); + castFailureOnUndefinedObjMayCauseFalseNegative(); + callOnCastUndefinedObjMayCauseFalseNegative(); + callOnUndefinedObjMayCauseFalseNegative(); + callOnUndefinedObjMayCauseFalsePositive(); + fieldWriteOnUndefinedObjMayCauseFalseNegative(); + fieldWriteOnUndefinedObjMayCauseFalsePositive(); + fieldReadOnUndefinedObjMayCauseFalseNegative(); + fieldReadOnUndefinedObjMayCauseFalsePositive(); + recursiveAngelicTypesMayCauseFalseNegative(); + recursiveAngelicTypesMayCauseFalsePositive(); + infiniteMaterializationMayCauseFalseNegative(b); + infiniteMaterializationMayCauseFalsePositive(b); + primitiveFieldOfAngelicObjMayCauseFalsePositive(); + primitiveFieldOfAngelicObjMayCauseFalseNegative(); + heapFieldOfAngelicObjMayCauseFalsePositive(); + heapFieldOfAngelicObjMayCauseFalseNegative(); + fieldReadAferCastMayCauseFalseNegative(iter); + fieldReadInCalleeMayCauseFalsePositive(); + fieldReadInCalleeWithAngelicObjFieldMayCauseFalsePositive(); + accessPathInCalleeMayCauseFalsePositive(); + int i = 1 / 0; + } + +} diff --git a/infer/tests/codetoanalyze/java/infer/ArrayOutOfBounds.java b/infer/tests/codetoanalyze/java/infer/ArrayOutOfBounds.java new file mode 100644 index 000000000..85a1bf045 --- /dev/null +++ b/infer/tests/codetoanalyze/java/infer/ArrayOutOfBounds.java @@ -0,0 +1,92 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package codetoanalyze.java.infer; + + +public class ArrayOutOfBounds { + + public int arrayOutOfBounds() { + int[] arr = new int[1]; + return arr[3]; + } + + public int arrayInBounds() { + int[] arr = new int[2]; + return arr[1]; + } + + // tests below this line are turned off until array functionality improves + public void arrayLoopOutOfBounds(int[] arr) { + for (int i = 0; i <= arr.length; i++) { + int j = arr[i]; + } + } + + public void arrayLoopInBounds(int[] arr) { + for (int i = 0; i < arr.length; i++) { + int j = arr[i]; + } + } + + public void buggyIter(int[] arr1, int[] arr2) { + for (int i = 0; i < arr1.length; i++) { + arr2[i] = 7; + } + } + + public void switchedArrsOutOfBounds() { + buggyIter(new int[11], new int[10]); + } + + public void buggyNestedLoop1(int[] arr1, int[] arr2) { + for (int i = 0; i < arr1.length; i++) { + for (int j = 0; i < arr2.length; j++) { + arr1[i] = arr1[i] + arr2[j]; + } + } + } + + public void nestedOutOfBounds1() { + buggyNestedLoop1(new int[11], new int[10]); + } + + public void buggyNestedLoop2(int[] arr1, int[] arr2) { + for (int i = 0; i < arr1.length; i++) { + for (int j = 0; j < arr2.length; i++) { + arr1[i] = arr1[i] + arr2[j]; + } + } + } + + public void nestedOutOfBounds2() { + buggyNestedLoop2(new int[11], new int[10]); + } + + public void buggyNestedLoop3(int[] arr1, int[] arr2) { + for (int i = 0; i < arr1.length; i++) { + for (int j = 0; j < arr2.length; j++) { + arr1[i] = 2 * arr2[i]; + } + } + } + + public void nestedOutOfBounds3() { + buggyNestedLoop3(new int[11], new int[10]); + } + + public void safeNestedLoop(int[] arr1, int[] arr2) { + for (int i = 0; i < arr1.length; i++) { + for (int j = 0; j < arr2.length; j++) { + arr1[i] = arr1[i] + arr2[j]; + } + } + } + + public void nestedInBounds() { + safeNestedLoop(new int[11], new int[10]); + } + +} diff --git a/infer/tests/codetoanalyze/java/infer/AutoGenerated.java b/infer/tests/codetoanalyze/java/infer/AutoGenerated.java new file mode 100644 index 000000000..b93d10e10 --- /dev/null +++ b/infer/tests/codetoanalyze/java/infer/AutoGenerated.java @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2015- Facebook. + * All rights reserved. + */ + +package codetoanalyze.java.infer; + +/* @generated */ + +public class AutoGenerated { + + void npe() { + String s = null; + int n = s.length(); + } +} diff --git a/infer/tests/codetoanalyze/java/infer/BUCK b/infer/tests/codetoanalyze/java/infer/BUCK new file mode 100644 index 000000000..6a32e238f --- /dev/null +++ b/infer/tests/codetoanalyze/java/infer/BUCK @@ -0,0 +1,55 @@ +sources = glob(['**/*.java']) + +dependencies = [ + '//infer/lib/java/android:android', + '//dependencies/java/jackson:jackson', +] + +java_library( + name = 'infer', + srcs = sources, + deps = dependencies, + visibility = [ + 'PUBLIC' + ] +) + +def analysis_cmd(analyzer): + out = 'out' + inferconfig_file = '$(location //infer/tests/codetoanalyze/java:inferconfig)' + copy_inferconfig = ' '.join(['cp', inferconfig_file, '$SRCDIR']) + clean_cmd = ' '.join(['rm', '-rf', out]) + classpath = ':'.join([('$(classpath ' + path + ')') for path in dependencies]) + infer_cmd = ' '.join([ + 'infer', + '-o', out, + '-a', analyzer, + '--', + 'javac', + '-cp', classpath, + '$SRCS', + ]) + copy_cmd = ' '.join(['cp', out + '/report.csv', '$OUT']) + return ' && '.join([clean_cmd, copy_inferconfig, infer_cmd, copy_cmd]) + +genrule( + name = 'analyze', + out = 'report.csv', + cmd = analysis_cmd('infer'), + deps = dependencies + [':infer'], + srcs = sources, + visibility = [ + 'PUBLIC', + ], +) + +genrule( + name = 'tracing', + out = 'comparison_report.csv', + cmd = analysis_cmd('tracing'), + deps = dependencies + [':infer'], + srcs = sources, + visibility = [ + 'PUBLIC', + ], +) diff --git a/infer/tests/codetoanalyze/java/infer/ClassCastExceptions.java b/infer/tests/codetoanalyze/java/infer/ClassCastExceptions.java new file mode 100644 index 000000000..ec994182f --- /dev/null +++ b/infer/tests/codetoanalyze/java/infer/ClassCastExceptions.java @@ -0,0 +1,65 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package codetoanalyze.java.infer; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; + +class SuperClass { +} + +class SubClassA extends SuperClass { +} + +class SubClassB extends SuperClass { +} + +class ImplementationOfInterface implements MyInterface { + + public int getInt() { + return 0; + } +} + +class AnotherImplementationOfInterface implements MyInterface { + public int getInt() { + return 1; + } +} + +interface MyInterface { + public int getInt(); +} + + +public class ClassCastExceptions { + + public void classCastException() { + SuperClass a = new SubClassA(); + SubClassB b = (SubClassB) a; + } + + public int classCastExceptionImplementsInterfaceCallee(MyInterface i) { + ImplementationOfInterface impl = (ImplementationOfInterface) i; + return impl.getInt(); + } + + public int classCastExceptionImplementsInterface() { + return classCastExceptionImplementsInterfaceCallee(new AnotherImplementationOfInterface()); + } + + public String getURL() { + return "http://bla.com"; + } + + public void openHttpURLConnection() throws IOException { + URL url = new URL(getURL()); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.disconnect(); + } + +} diff --git a/infer/tests/codetoanalyze/java/infer/CursorLeaks.java b/infer/tests/codetoanalyze/java/infer/CursorLeaks.java new file mode 100644 index 000000000..76d41a274 --- /dev/null +++ b/infer/tests/codetoanalyze/java/infer/CursorLeaks.java @@ -0,0 +1,216 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package codetoanalyze.java.infer; + + +import android.app.DownloadManager; +import android.content.ContentProviderClient; +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.database.CursorWrapper; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteQueryBuilder; +import android.os.RemoteException; +import android.provider.MediaStore; + +public class CursorLeaks { + + public int cursorClosed(SQLiteDatabase sqLiteDatabase) { + Cursor cursor = sqLiteDatabase.query( + "events", null, + null, null, null, null, null); + try { + return cursor.getCount(); + } finally { + cursor.close(); + } + } + + public int cursorNotClosed(SQLiteDatabase sqLiteDatabase) { + Cursor cursor = sqLiteDatabase.query( + "events", null, + null, null, null, null, null); + return cursor.getCount(); + } + + Context mContext; + ContentResolver mContentResolver; + + public int getImageCountHelperNotClosed(String customClause) { + String[] projection = {"COUNT(*)"}; + + String selectionClause = selectionClause = customClause; + + Cursor cursor = mContext.getContentResolver().query( + null, + projection, + selectionClause, + null, + null); + + if (cursor != null) { + int count = cursor.getInt(0); + // cursor.close(); + return count; + } else { + return 0; + } + } + + public int getImageCountHelperClosed(String customClause) { + String[] projection = {"COUNT(*)"}; + + String selectionClause = selectionClause = customClause; + + Cursor cursor = mContext.getContentResolver().query( + null, + projection, + selectionClause, + null, + null); + + if (cursor != null) { + int count = cursor.getInt(0); + cursor.close(); + return count; + } else { + return 0; + } + } + + public int getBucketCountNotClosed() { + Cursor cursor = MediaStore.Images.Media.query( + mContentResolver, null, null, null, null, null); + if (cursor == null) { + return 0; + } else { + int count = 0; + while (cursor.moveToNext()) { + count++; + } + return count; + } + + } + + public int getBucketCountClosed() { + Cursor cursor = MediaStore.Images.Media.query( + mContentResolver, null, null, null, null, null); + if (cursor == null) { + return 0; + } else { + try { + int count = 0; + while (cursor.moveToNext()) { + count++; + } + return count; + } finally { + cursor.close(); + } + } + } + + private void queryUVMLegacyDbNotClosed() { + SQLiteQueryBuilder builder = new SQLiteQueryBuilder(); + builder.setTables(""); + Cursor cursor = builder.query(null, null, "", null, null, null, null); + if (cursor != null) cursor.moveToFirst(); + } + + private void queryUVMLegacyDbClosed() { + SQLiteQueryBuilder builder = new SQLiteQueryBuilder(); + builder.setTables(""); + Cursor cursor = builder.query(null, null, "", null, null, null, null); + if (cursor != null) cursor.close(); + } + + public int completeDownloadClosed(DownloadManager downloadManager) { + DownloadManager.Query query = new DownloadManager.Query(); + Cursor cursor = null; + try { + cursor = downloadManager.query(query); + if (cursor == null) { + return 0; + } else { + return cursor.getColumnIndex(DownloadManager.COLUMN_STATUS); + } + } finally { + if (cursor != null) cursor.close(); + } + } + + public int completeDownloadNotClosed(DownloadManager downloadManager) { + DownloadManager.Query query = new DownloadManager.Query(); + Cursor cursor = null; + try { + cursor = downloadManager.query(query); + if (cursor == null) { + return 0; + } else { + return cursor.getColumnIndex(DownloadManager.COLUMN_STATUS); + } + } finally { + //cursor.close(); + } + } + + private void loadPrefsFromContentProviderClosed() { + ContentProviderClient contentProviderClient = + mContentResolver.acquireContentProviderClient(""); + if (contentProviderClient != null) { + Cursor cursor = null; + try { + try { + cursor = contentProviderClient.query(null, null, null, null, null); + } catch (RemoteException ex) { + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + } + + private void loadPrefsFromContentProviderNotClosed() { + ContentProviderClient contentProviderClient = + mContentResolver.acquireContentProviderClient(""); + if (contentProviderClient == null) return; + Cursor cursor = null; + try { + try { + cursor = contentProviderClient.query(null, null, null, null, null); + } catch (RemoteException ex) { + } + } finally { + if (cursor != null) { + //cursor.close(); + } + } + } + + class NamedCursor extends CursorWrapper { + private String mName; + + NamedCursor(Cursor cursor, String name) { + super(cursor); + mName = name; + } + } + + public Cursor cursorWrapperReturned(SQLiteDatabase sqLiteDatabase) { + Cursor cursor = sqLiteDatabase.query("events", null, null, null, null, null, null); + return new NamedCursor(cursor, "abc"); + } + + public void cursorWrapperClosed(SQLiteDatabase sqLiteDatabase) { + Cursor cursor = sqLiteDatabase.query("events", null, null, null, null, null, null); + Cursor c = new NamedCursor(cursor, "abc"); + c.close(); + } +} diff --git a/infer/tests/codetoanalyze/java/infer/DivideByZero.java b/infer/tests/codetoanalyze/java/infer/DivideByZero.java new file mode 100644 index 000000000..4d4257ca0 --- /dev/null +++ b/infer/tests/codetoanalyze/java/infer/DivideByZero.java @@ -0,0 +1,39 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package codetoanalyze.java.infer; + + +public class DivideByZero { + + public int divByZeroLocal(String s) { + int denominator = 0; + int nominator = 10; + int result = nominator / denominator; + return result; + } + + public int divideByZeroInterProc(int denominator) { + return 10 / denominator; + } + + //DO NOT MOVE, test relies on line number + public int callDivideByZeroInterProc() { + return divideByZeroInterProc(0); + } + + //divide by zero with static fields + private static int x; + + public void setXToZero() { + x = 0; + } + + public int divideByZeroWithStaticField() { + setXToZero(); + return divideByZeroInterProc(x); + } + +} diff --git a/infer/tests/codetoanalyze/java/infer/FilterInputStreamLeaks.java b/infer/tests/codetoanalyze/java/infer/FilterInputStreamLeaks.java new file mode 100644 index 000000000..184eaa0dc --- /dev/null +++ b/infer/tests/codetoanalyze/java/infer/FilterInputStreamLeaks.java @@ -0,0 +1,262 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package codetoanalyze.java.infer; + + +import java.io.BufferedInputStream; +import java.io.DataInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.PushbackInputStream; +import java.security.DigestInputStream; +import java.util.zip.CheckedInputStream; +import java.util.zip.DeflaterInputStream; +import java.util.zip.GZIPInputStream; +import java.util.zip.InflaterInputStream; + +import javax.crypto.CipherInputStream; + +public class FilterInputStreamLeaks { + + //BufferedInputStream tests + + public void bufferedInputStreamNotClosedAfterRead() { + FileInputStream fis; + try { + fis = new FileInputStream("file.txt"); + BufferedInputStream bis = new BufferedInputStream(fis); + bis.read(); + bis.close(); + } catch (IOException e) { + } + } + + public void bufferedInputStreamClosedAfterReset() throws IOException { + FileInputStream fis; + BufferedInputStream bis = null; + try { + fis = new FileInputStream("file.txt"); + bis = new BufferedInputStream(fis); + bis.reset(); + } catch (IOException e) { + } finally { + if (bis != null) bis.close(); + } + } + + //CheckedInputStream tests + + public void checkedInputStreamNotClosedAfterRead() { + FileInputStream fis; + try { + fis = new FileInputStream("file.txt"); + CheckedInputStream chis = new CheckedInputStream(fis, null); + chis.read(); + chis.close(); + } catch (IOException e) { + } + } + + public void checkedInputStreamClosedAfterSkip() throws IOException { + FileInputStream fis; + CheckedInputStream chis = null; + try { + fis = new FileInputStream("file.txt"); + chis = new CheckedInputStream(fis, null); + chis.skip(5); + } catch (IOException e) { + } finally { + if (chis != null) chis.close(); + } + } + + //CipherInputStream tests + + public void cipherInputStreamNotClosedAfterSkip() { + FileInputStream fis; + try { + fis = new FileInputStream("file.txt"); + CipherInputStream cis = new CipherInputStream(fis, null); + cis.skip(8); + cis.close(); + } catch (IOException e) { + } + } + + public void cipherInputStreamClosedAfterRead() throws IOException { + FileInputStream fis; + CipherInputStream cis = null; + try { + fis = new FileInputStream("file.txt"); + cis = new CipherInputStream(fis, null); + cis.read(); + } catch (IOException e) { + } finally { + if (cis != null) cis.close(); + } + } + + //DataInputStream tests + + public void dataInputStreamNotClosedAfterRead() { + byte[] arr = new byte[10]; + FileInputStream fis; + try { + fis = new FileInputStream("file.txt"); + DataInputStream dis = new DataInputStream(fis); + dis.read(arr); + dis.close(); + } catch (IOException e) { + } + } + + public void dataInputStreamClosedAfterReadBoolean() throws IOException { + FileInputStream fis; + DataInputStream dis = null; + try { + fis = new FileInputStream("file.txt"); + dis = new DataInputStream(fis); + dis.readBoolean(); + } catch (IOException e) { + } finally { + if (dis != null) dis.close(); + } + } + + //DeflaterInputStream tests + + public void deflaterInputStreamNotClosedAfterRead() { + FileInputStream fis; + try { + fis = new FileInputStream("file.txt"); + DeflaterInputStream dis = new DeflaterInputStream(fis, null); + dis.read(); + dis.close(); + } catch (IOException e) { + } + } + + public void deflaterInputStreamClosedAfterReset() throws IOException { + FileInputStream fis; + DeflaterInputStream dis = null; + try { + fis = new FileInputStream("file.txt"); + dis = new DeflaterInputStream(fis, null); + dis.reset(); + } catch (IOException e) { + } finally { + if (dis != null) dis.close(); + } + } + + //GZipInputStream tests + + public void gzipInputStreamNotClosedAfterRead() { + FileInputStream fis; + try { + fis = new FileInputStream("file.txt"); + GZIPInputStream gzipInputStream = new GZIPInputStream(fis); + gzipInputStream.read(); + gzipInputStream.close(); + } catch (IOException e) { + } + } + + public void gzipInputStreamClosedAfterRead() throws IOException { + FileInputStream fis = null; + GZIPInputStream gzipInputStream = null; + try { + fis = new FileInputStream("file.txt"); + gzipInputStream = new GZIPInputStream(fis); + gzipInputStream.read(); + } catch (IOException e) { + } finally { + if (gzipInputStream != null) + gzipInputStream.close(); + else if (fis != null) + fis.close(); + } + } + + //DigestInputStream tests + + public void digestInputStreamNotClosedAfterRead() { + byte[] arr = new byte[10]; + FileInputStream fis; + try { + fis = new FileInputStream("file.txt"); + DigestInputStream dis = new DigestInputStream(fis, null); + dis.read(arr); + dis.close(); + } catch (IOException e) { + } + } + + public void digestInputStreamClosedAfterRead() throws IOException { + FileInputStream fis; + DigestInputStream dis = null; + try { + fis = new FileInputStream("file.txt"); + dis = new DigestInputStream(fis, null); + dis.read(); + } catch (IOException e) { + } finally { + if (dis != null) dis.close(); + } + } + + //InflaterInputStream tests + + public void inflaterInputStreamNotClosedAfterRead() { + FileInputStream fis; + try { + fis = new FileInputStream("file.txt"); + InflaterInputStream iis = new InflaterInputStream(fis, null); + iis.read(); + iis.close(); + } catch (IOException e) { + } + } + + public void inflaterInputStreamClosedAfterAvailable() throws IOException { + FileInputStream fis; + InflaterInputStream iis = null; + try { + fis = new FileInputStream("file.txt"); + iis = new InflaterInputStream(fis, null); + iis.available(); + } catch (IOException e) { + } finally { + if (iis != null) iis.close(); + } + } + + //PushbackInputStream tests + + public void pushbackInputStreamNotClosedAfterRead() { + FileInputStream fis; + try { + fis = new FileInputStream("file.txt"); + PushbackInputStream pms = new PushbackInputStream(fis); + pms.read(); + pms.close(); + } catch (IOException e) { + } + } + + public void pushbackInputStreamClosedAfterReset() throws IOException { + FileInputStream fis; + PushbackInputStream pms = null; + try { + fis = new FileInputStream("file.txt"); + pms = new PushbackInputStream(fis); + pms.reset(); + } catch (IOException e) { + } finally { + if (pms != null) pms.close(); + } + } +} diff --git a/infer/tests/codetoanalyze/java/infer/FilterOutputStreamLeaks.java b/infer/tests/codetoanalyze/java/infer/FilterOutputStreamLeaks.java new file mode 100644 index 000000000..de06628f2 --- /dev/null +++ b/infer/tests/codetoanalyze/java/infer/FilterOutputStreamLeaks.java @@ -0,0 +1,317 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package codetoanalyze.java.infer; + + +import java.io.BufferedOutputStream; +import java.io.DataOutputStream; +import java.io.FileOutputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.security.DigestOutputStream; +import java.util.zip.CheckedOutputStream; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.GZIPOutputStream; +import java.util.zip.InflaterOutputStream; + +import javax.crypto.CipherOutputStream; + +public class FilterOutputStreamLeaks { + + + //FilterOutputStream tests + + public void filterOutputStreamNotClosedAfterWrite() { + byte[] arr = {1, 2, 3}; + FileOutputStream fis; + try { + fis = new FileOutputStream("file.txt"); + FilterOutputStream fos = new FilterOutputStream(fis); + fos.write(arr); + fos.close(); + } catch (IOException e) { + } + } + + public void filterOutputStreamClosedAfterWrite() throws IOException { + byte[] arr = {1, 2, 3}; + FileOutputStream fis; + FilterOutputStream fos = null; + try { + fis = new FileOutputStream("file.txt"); + fos = new FilterOutputStream(fis); + fos.write(arr); + } catch (IOException e) { + } finally { + if (fos != null) + fos.close(); + } + } + + //DataOutputStream tests + + public void dataOutputStreamNotClosedAfterWrite() { + byte[] arr = {1, 2, 3}; + FileOutputStream fis; + try { + fis = new FileOutputStream("file.txt"); + DataOutputStream dos = new DataOutputStream(fis); + dos.write(arr); + dos.close(); + } catch (IOException e) { + } + } + + public void dataOutputStreamClosedAfterWrite() throws IOException { + byte[] arr = {1, 2, 3}; + FileOutputStream fis; + DataOutputStream dos = null; + try { + fis = new FileOutputStream("file.txt"); + dos = new DataOutputStream(fis); + dos.write(arr); + } catch (IOException e) { + } finally { + if (dos != null) + dos.close(); + } + } + + //BufferedOutputStream tests + + public void bufferedOutputStreamNotClosedAfterWrite() { + byte[] arr = {1, 2, 3}; + FileOutputStream fis = null; + try { + fis = new FileOutputStream("file.txt"); + BufferedOutputStream bos = new BufferedOutputStream(fis); + bos.write(arr); + bos.close(); + } catch (IOException e) { + } + } + + public void bufferedOutputStreamClosedAfterWrite() throws IOException { + byte[] arr = {1, 2, 3}; + FileOutputStream fis; + BufferedOutputStream bos = null; + try { + fis = new FileOutputStream("file.txt"); + bos = new BufferedOutputStream(fis); + bos.write(arr); + } catch (IOException e) { + } finally { + if (bos != null) + bos.close(); + } + } + + + //CheckedOutputStream tests + + public void checkedOutputStreamNotClosedAfterWrite() { + byte[] arr = {1, 2, 3}; + FileOutputStream fis; + try { + fis = new FileOutputStream("file.txt"); + CheckedOutputStream chos = new CheckedOutputStream(fis, null); + chos.write(arr); + chos.close(); + } catch (IOException e) { + } + } + + public void checkedOutputStreamClosedAfterWrite() throws IOException { + byte[] arr = {1, 2, 3}; + FileOutputStream fis; + CheckedOutputStream chos = null; + try { + fis = new FileOutputStream("file.txt"); + chos = new CheckedOutputStream(fis, null); + chos.write(arr); + } catch (IOException e) { + } finally { + if (chos != null) + chos.close(); + } + } + + //CipherOutputStream tests + + public void cipherOutputStreamNotClosedAfterWrite() { + byte[] arr = {1, 2, 3}; + FileOutputStream fis; + try { + fis = new FileOutputStream("file.txt"); + CipherOutputStream cos = new CipherOutputStream(fis, null); + cos.write(arr); + cos.close(); + } catch (IOException e) { + } + } + + public void cipherOutputStreamClosedAfterWrite() throws IOException { + byte[] arr = {1, 2, 3}; + FileOutputStream fis; + CipherOutputStream cos = null; + try { + fis = new FileOutputStream("file.txt"); + cos = new CipherOutputStream(fis, null); + cos.write(arr); + } catch (IOException e) { + } finally { + if (cos != null) + cos.close(); + } + } + + //DeflaterOutputStream tests + + public void deflaterOutputStreamNotClosedAfterWrite() { + byte[] arr = {1, 2, 3}; + FileOutputStream fis; + try { + fis = new FileOutputStream("file.txt"); + DeflaterOutputStream dos = new DeflaterOutputStream(fis, null); + dos.write(arr); + dos.close(); + } catch (IOException e) { + } + } + + public void deflaterOutputStreamClosedAfterWrite() throws IOException { + byte[] arr = {1, 2, 3}; + FileOutputStream fis; + DeflaterOutputStream dos = null; + try { + fis = new FileOutputStream("file.txt"); + dos = new DeflaterOutputStream(fis, null); + dos.write(arr); + } catch (IOException e) { + } finally { + if (dos != null) + dos.close(); + } + } + + + //DigestOutputStream tests + + public void digestOutputStreamNotClosedAfterWrite() { + byte[] arr = {1, 2, 3}; + FileOutputStream fis; + try { + fis = new FileOutputStream("file.txt"); + DigestOutputStream dos = new DigestOutputStream(fis, null); + dos.write(arr); + dos.close(); + } catch (IOException e) { + } + } + + public void digestOutputStreamClosedAfterWrite() throws IOException { + byte[] arr = {1, 2, 3}; + FileOutputStream fis; + DigestOutputStream dos = null; + try { + fis = new FileOutputStream("file.txt"); + dos = new DigestOutputStream(fis, null); + dos.write(arr); + } catch (IOException e) { + } finally { + if (dos != null) + dos.close(); + } + } + + //InflaterOutputStream tests + + public void inflaterOutputStreamNotClosedAfterWrite() { + byte[] arr = {1, 2, 3}; + FileOutputStream fis; + try { + fis = new FileOutputStream("file.txt"); + InflaterOutputStream ios = new InflaterOutputStream(fis, null); + ios.write(arr); + ios.close(); + } catch (IOException e) { + } + } + + public void inflaterOutputStreamClosedAfterWrite() throws IOException { + byte[] arr = {1, 2, 3}; + FileOutputStream fis; + InflaterOutputStream ios = null; + try { + fis = new FileOutputStream("file.txt"); + ios = new InflaterOutputStream(fis, null); + ios.write(arr); + } catch (IOException e) { + } finally { + if (ios != null) + ios.close(); + } + } + + //GZipOutputStream tests + + public void gzipOutputStreamNotClosedAfterFlush() { + FileOutputStream fos; + try { + fos = new FileOutputStream("file.txt"); + GZIPOutputStream gzipOutputStream = new GZIPOutputStream(fos); + gzipOutputStream.flush(); + gzipOutputStream.close(); + } catch (IOException e) { + } + } + + public void gzipOutputStreamClosedAfterWrite() throws IOException { + byte[] arr = {1, 2, 3}; + FileOutputStream fos = null; + GZIPOutputStream gzipOutputStream = null; + try { + fos = new FileOutputStream("file.txt"); + gzipOutputStream = new GZIPOutputStream(fos); + gzipOutputStream.write(arr); + } catch (IOException e) { + } finally { + if (gzipOutputStream != null) + gzipOutputStream.close(); + else if (fos != null) + fos.close(); + } + } + + //PrintStream tests + + public void printStreamNotClosedAfterWrite() { + byte[] arr = {1, 2, 3}; + FileOutputStream fis; + try { + fis = new FileOutputStream("file.txt"); + InflaterOutputStream printer = new InflaterOutputStream(fis, null); + printer.write(arr); + } catch (IOException e) { + } + } + + public void printStreamClosedAfterWrite() throws IOException { + byte[] arr = {1, 2, 3}; + FileOutputStream fis; + InflaterOutputStream printer = null; + try { + fis = new FileOutputStream("file.txt"); + printer = new InflaterOutputStream(fis, null); + printer.write(arr); + } catch (IOException e) { + } finally { + if (printer != null) + printer.close(); + } + } + +} diff --git a/infer/tests/codetoanalyze/java/infer/HashMapModelTest.java b/infer/tests/codetoanalyze/java/infer/HashMapModelTest.java new file mode 100644 index 000000000..aec73f7ee --- /dev/null +++ b/infer/tests/codetoanalyze/java/infer/HashMapModelTest.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package codetoanalyze.java.infer; + +import java.util.HashMap; + +public class HashMapModelTest { + + public static int putIntegerTwiceThenGetTwice() { + HashMap hashMap = new HashMap<>(); + Integer i32 = new Integer(32); + Integer i52 = new Integer(52); + + hashMap.put(i32, i32); + hashMap.put(i52, i52); + + Integer a = hashMap.get(i32); + Integer b = hashMap.get(i52); + + return a.intValue() * b.intValue(); + } + + public static int containsIntegerTwiceThenGetTwice( + HashMap hashMap + ) { + Integer i32 = new Integer(32); + Integer i52 = new Integer(52); + + if (hashMap.containsKey(i32) && hashMap.containsKey(i52)) { + Integer a = hashMap.get(i32); + Integer b = hashMap.get(i52); + return a.intValue() * b.intValue(); + } + + return 0; + } + + public static int getOneIntegerWithoutCheck( + HashMap hashMap + ) { + Integer i32 = new Integer(32); + + Integer a = hashMap.get(i32); + + return a.intValue(); + } + + public static int getTwoIntegersWithOneCheck( + HashMap hashMap + ) { + Integer i32 = new Integer(32); + Integer i52 = new Integer(52); + + if (hashMap.containsKey(i32)) { + Integer a = hashMap.get(i32); + Integer b = hashMap.get(i52); + + return a.intValue() * b.intValue(); + } + + return 0; + } + + public static int getTwoParameterIntegersWithOneCheck( + HashMap hashMap, + Integer i32, + Integer i52 + ) { + if (hashMap.containsKey(i32)) { + Integer a = hashMap.get(i32); + Integer b = hashMap.get(i52); + + return a.intValue() * b.intValue(); + } + + return 0; + } + +} diff --git a/infer/tests/codetoanalyze/java/infer/JunitAssertion.java b/infer/tests/codetoanalyze/java/infer/JunitAssertion.java new file mode 100644 index 000000000..0a66de798 --- /dev/null +++ b/infer/tests/codetoanalyze/java/infer/JunitAssertion.java @@ -0,0 +1,21 @@ +package codetoanalyze.java.infer; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +public class JunitAssertion { + class A { + public void f() { + } + } + + public void consistentAssumtion(A a) { + assertTrue(a != null); + a.f(); + } + + public void inconsistentAssertion(A a) { + assertFalse("Should not happen!", a != null); + a.f(); + } +} diff --git a/infer/tests/codetoanalyze/java/infer/NeverNullSource.java b/infer/tests/codetoanalyze/java/infer/NeverNullSource.java new file mode 100644 index 000000000..8dd248e8a --- /dev/null +++ b/infer/tests/codetoanalyze/java/infer/NeverNullSource.java @@ -0,0 +1,17 @@ +// Copyright (c) 2015-Present Facebook. All rights reserved. +// _AUTOMATICALLY_GENERATED_ + +package codetoanalyze.java.infer; + +import javax.annotation.Nullable; + +public class NeverNullSource { + + @Nullable + T t; + + T get() { + return t == null ? null : t; + } + +} diff --git a/infer/tests/codetoanalyze/java/infer/NullPointerExceptions.java b/infer/tests/codetoanalyze/java/infer/NullPointerExceptions.java new file mode 100644 index 000000000..9d1a5fdd4 --- /dev/null +++ b/infer/tests/codetoanalyze/java/infer/NullPointerExceptions.java @@ -0,0 +1,326 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package codetoanalyze.java.infer; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; + +import javax.annotation.Nullable; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.HashMap; + +public class NullPointerExceptions { + + class A { + int x; + + public void method() { + } + } + + // npe local with field + public int nullPointerException() { + A a = null; + return a.x; + } + + public A canReturnNullObject(boolean ok) { + A a = new A(); + if (ok) + return a; + else + return null; + } + + public static void expectNotNullObjectParameter(A a) { + a.method(); + } + + public static void expectNotNullArrayParameter(A[] array) { + array.clone(); + } + + // npe with branching, interprocedural + public int nullPointerExceptionInterProc() { + A a = canReturnNullObject(false); + return a.x; + } + + // npe with exception handling + public int nullPointerExceptionWithExceptionHandling(boolean ok) { + A a = null; + try { + throw new Exception(); + } catch (Exception e) { + return a.x; + } + } + + class B { + A a; + + void test() { + } + } + + + public static int nullPointerExceptionWithArray() { + A[] array = new A[]{null}; + A t = array[0]; + return t.x; + } + + // npe with a chain of fields + class C { + B b; + } + + public int nullPointerExceptionWithAChainOfFields(C c) { + c.b = new B(); + return c.b.a.x; + } + + // npe with a null object parameter + public static void nullPointerExceptionWithNullObjectParameter() { + expectNotNullObjectParameter(null); + } + + // npe with a null array parameter + public static void nullPointerExceptionWithNullArrayParameter() { + expectNotNullArrayParameter(null); + } + + public static void nullPointerExceptionFromFaillingResourceConstructor() throws IOException { + FileInputStream fis = null; + try { + fis = new FileInputStream(new File("whatever.txt")); + } catch (IOException e) { + } finally { + fis.close(); + } + } + + public static void nullPointerExceptionFromFailingFileOutputStreamConstructor() + throws IOException { + FileOutputStream fos = null; + try { + fos = new FileOutputStream(new File("whatever.txt")); + } catch (IOException e) { + } finally { + fos.close(); + } + } + + int x; + + public void nullPointerExceptionFromNotKnowingThatThisIsNotNull() { + if (this == null) { + } + this.x = 4; + } + + public T id_generics(T o) { + o.toString(); + return o; + } + + public A frame(A x) { + return id_generics(x); + } + + public void nullPointerExceptionUnlessFrameFails() { + String s = null; + Object a = frame(new A()); + if (a instanceof A) { + s.length(); + } + } + + class D { + int x; + } + + public int preconditionCheckStateTest(D d) { + Preconditions.checkState(d != null); + return d.x; + } + + public void genericMethodSomewhereCheckingForNull(String s) { + if (s == null) { + } + } + + public void noNullPointerExceptionAfterSkipFunction() { + String t = new String("Hello!"); + String s = t.toString(); + genericMethodSomewhereCheckingForNull(s); + s.length(); + } + + String hashmapNPE(HashMap h, Object o) { + return (h.get(o).toString()); + } + + String NPEhashmapProtectedByContainsKey(HashMap h, Object o) { + if (h.containsKey(o)) { + return (h.get(o).toString()); + } + return "aa"; + } + + int NPEvalueOfFromHashmapBad(HashMap h, int position) { + return h.get(position); + } + + Integer NPEvalueOfFromHashmapGood(HashMap h, int position) { + return h.get(position); + } + + static void ReturnedValueOfImmutableListOf() { + ImmutableList l = ImmutableList.of(); + if (l == null) { + l.toString(); + } + } + + void nullPointerExceptionInArrayLengthLoop(Object[] arr) { + for (int i = 0; i < arr.length; i++) { + Object x = null; + x.toString(); + } + } + + Context mContext; + ContentResolver mContentResolver; + + public void cursorFromContentResolverNPE(String customClause) { + String[] projection = {"COUNT(*)"}; + String selectionClause = selectionClause = customClause; + Cursor cursor = mContext.getContentResolver().query( + null, + projection, + selectionClause, + null, + null); + cursor.close(); + } + + public int cursorQueryShouldNotReturnNull(SQLiteDatabase sqLiteDatabase) { + Cursor cursor = sqLiteDatabase.query( + "events", null, null, null, null, null, null); + try { + return cursor.getCount(); + } finally { + cursor.close(); + } + } + + Object[] arr = new Object[1]; + + Object arrayReadShouldNotCauseSymexMemoryError(int i) { + arr[i].toString(); + return null; + } + + void nullPointerExceptionCallArrayReadMethod() { + arr[0] = new Object(); + arrayReadShouldNotCauseSymexMemoryError(0).toString(); + } + + public void sinkWithNeverNullSource() { + NeverNullSource source = new NeverNullSource(); + T t = source.get(); + t.f(); + } + + public void otherSinkWithNeverNullSource() { + SomeLibrary source = new SomeLibrary(); + T t = source.get(); + t.f(); + } + + private @Nullable Object mFld; + + void nullableFieldNPE() { + mFld.toString(); + } + + void guardedNullableFieldDeref() { + if (mFld != null) mFld.toString(); + } + + void allocNullableFieldDeref() { + mFld = new Object(); + mFld.toString(); + } + + void nullableParamNPE(@Nullable Object param) { + param.toString(); + } + + void guardedNullableParamDeref(@Nullable Object param) { + if (param != null) param.toString(); + } + + void allocNullableParamDeref(@Nullable Object param) { + param = new Object(); + param.toString(); + } + + native boolean test(); + + Object getObj() { + if (test()) { + return new Object(); + } else { + return null; + } + } + + Boolean getBool() { + if (test()) { + return new Boolean(true); + } else { + return null; + } + } + + void derefGetterAfterCheckShouldNotCauseNPE() { + if (getObj() != null) { + getObj().toString(); + } + } + + void derefBoxedGetterAfterCheckShouldNotCauseNPE() { + boolean b = getBool() != null && getBool(); + } + + static void derefNonThisGetterAfterCheckShouldNotCauseNPE() { + NullPointerExceptions c = new NullPointerExceptions(); + if (c.getObj() != null) { + c.getObj().toString(); + } + } + + void badCheckShouldCauseNPE() { + if (getBool() != null) getObj().toString(); + } + + void nullPointerExceptionArrayLength() { + Object[] arr = null; + int i = arr.length; + } + +} diff --git a/infer/tests/codetoanalyze/java/infer/ReaderLeaks.java b/infer/tests/codetoanalyze/java/infer/ReaderLeaks.java new file mode 100644 index 000000000..f036f813b --- /dev/null +++ b/infer/tests/codetoanalyze/java/infer/ReaderLeaks.java @@ -0,0 +1,232 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package codetoanalyze.java.infer; + + +import com.squareup.okhttp.internal.StrictLineReader; +import com.squareup.okhttp.internal.Util; + +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PipedReader; +import java.io.PipedWriter; +import java.io.PushbackReader; +import java.io.Reader; + +public class ReaderLeaks { + + + //Reader tests + + public void readerNotClosedAfterRead() { + Reader r; + try { + r = new FileReader("testing.txt"); + r.read(); + r.close(); + } catch (IOException e) { + } + } + + public void readerClosed() throws IOException { + Reader r = null; + try { + r = new FileReader("testing.txt"); + boolean ready = r.ready(); + r.close(); + } catch (IOException e) { + } finally { + if (r != null) r.close(); + } + } + + //BufferedReader tests + + public void bufferedReaderNotClosedAfterRead() { + BufferedReader reader; + try { + reader = new BufferedReader(new FileReader("testing.txt")); + reader.read(); + reader.close(); + } catch (IOException e) { + } + } + + public void bufferedReaderClosed() throws IOException { + BufferedReader reader = null; + try { + reader = new BufferedReader(new FileReader("testing.txt")); + reader.read(); + } catch (IOException e) { + } finally { + if (reader != null) reader.close(); + } + } + + + //InputStreamReader tests + + public void inputStreamReaderNotClosedAfterRead() { + InputStreamReader reader; + try { + reader = new InputStreamReader(new FileInputStream("testing.txt")); + reader.read(); + reader.close(); + } catch (IOException e) { + } + } + + public void inputStreamReaderClosed() throws IOException { + InputStreamReader reader = null; + try { + reader = new InputStreamReader(new FileInputStream("testing.txt")); + reader.read(); + } catch (IOException e) { + } finally { + if (reader != null) reader.close(); + } + } + + //FileReader tests + + public void fileReaderNotClosedAfterRead() { + FileReader reader; + try { + reader = new FileReader("testing.txt"); + reader.read(); + reader.close(); + } catch (IOException e) { + } + } + + public void fileReaderClosed() throws IOException { + FileReader reader = null; + try { + reader = new FileReader("testing.txt"); + reader.read(); + } catch (IOException e) { + } finally { + if (reader != null) reader.close(); + } + } + + //PushbackReader tests + + public void pushbackReaderNotClosedAfterRead() { + PushbackReader reader; + try { + reader = new PushbackReader(new InputStreamReader(new FileInputStream("testing.txt"))); + reader.read(); + reader.close(); + } catch (IOException e) { + } + } + + public void pushbackReaderClosed() throws IOException { + PushbackReader reader = null; + try { + reader = new PushbackReader(new InputStreamReader(new FileInputStream("testing.txt"))); + reader.read(); + } catch (IOException e) { + } finally { + if (reader != null) reader.close(); + } + } + + //PipedReader tests + + public void pipedReaderNotClosedAfterConstructedWithWriter() { + PipedReader reader; + PipedWriter writer; + try { + writer = new PipedWriter(); + reader = new PipedReader(writer); + reader.read(); + reader.close(); + } catch (IOException e) { + } + } + + public void pipedReaderNotClosedAfterConnect(PipedWriter writer) { + PipedReader reader; + try { + reader = new PipedReader(); + reader.connect(writer); + reader.read(); + reader.close(); + } catch (IOException e) { + } + } + + public void pipedReaderNotConnected() { + PipedReader reader; + try { + reader = new PipedReader(); + reader.close(); + } catch (IOException e) { + } + } + + public void pipedReaderClosed(PipedWriter writer) throws IOException { + PipedReader reader = null; + try { + reader = new PipedReader(); + reader.connect(writer); + reader.read(); + } catch (IOException e) { + } finally { + if (reader != null) + reader.close(); + } + } + + public void pipedReaderFalsePositive() throws IOException { + PipedReader reader; + PipedWriter writer = null; + try { + reader = new PipedReader(writer); + reader.read(); + } catch (IOException e) { + } finally { + if (writer != null) + writer.close(); + } + } + + private String strictLineReaderClosed(String journalFile) throws IOException { + FileInputStream fs = new FileInputStream(journalFile); + StrictLineReader reader = null; + try { + reader = new StrictLineReader(fs, Util.US_ASCII); + String magic = reader.readLine(); + return magic; + + } finally { + if (reader != null) + Util.closeQuietly(reader); + else fs.close(); + return null; + } + } + + private String strictLineReaderNoLeak(String journalFile) throws IOException { + + StrictLineReader reader = new StrictLineReader( + new FileInputStream(journalFile), Util.US_ASCII); + try { + String magic = reader.readLine(); + return magic; + + } finally { + Util.closeQuietly(reader); + return null; + } + } + +} diff --git a/infer/tests/codetoanalyze/java/infer/ResourceLeaks.java b/infer/tests/codetoanalyze/java/infer/ResourceLeaks.java new file mode 100644 index 000000000..576038085 --- /dev/null +++ b/infer/tests/codetoanalyze/java/infer/ResourceLeaks.java @@ -0,0 +1,873 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package codetoanalyze.java.infer; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.json.UTF8StreamJsonParser; +import com.google.common.base.Preconditions; +import com.google.common.io.Closeables; + +import java.io.BufferedInputStream; +import java.io.Closeable; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.io.PrintWriter; +import java.io.RandomAccessFile; +import java.net.HttpURLConnection; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.URL; +import java.net.URLConnection; +import java.nio.channels.FileChannel; +import java.util.Scanner; +import java.util.jar.JarFile; +import java.util.jar.JarInputStream; +import java.util.jar.JarOutputStream; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; +import java.util.zip.ZipFile; + +import javax.net.ssl.HttpsURLConnection; + +import android.app.Activity; +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; + +public class ResourceLeaks { + + //FileOutputStream tests + + public void fileOutputStreamNotClosed() throws IOException { + FileOutputStream fis = new FileOutputStream("file.txt"); + } + + public void fileOutputStreamNotClosedAfterWrite() { + byte[] arr = {1, 2, 3}; + FileOutputStream fis = null; + try { + fis = new FileOutputStream("file.txt"); + fis.write(arr); + fis.close(); + } catch (IOException e) { + } + } + + + public void fileOutputStreamClosed() throws IOException { + FileOutputStream fis = new FileOutputStream("file.txt"); + fis.close(); + } + + + public int fileOutputStreamTwoLeaks(boolean ok) throws IOException { + FileOutputStream fis = new FileOutputStream("file.txt"); + if (ok) { + fis.write(1); + return 1; + } else { + fis.write(2); + return 2; + } + } + + //TwoResources tests + + public static void twoResources() throws IOException { + FileInputStream fis = null; + FileOutputStream fos = null; + try { + fis = new FileInputStream(new File("infile.txt")); + fos = new FileOutputStream(new File("outfile.txt")); + fos.write(fis.read()); + } finally { + if (fis != null) fis.close(); + if (fos != null) fos.close(); + } + } + + public static void twoResourcesHeliosFix() throws IOException { + FileInputStream fis = new FileInputStream(new File("whatever.txt")); + try { + FileOutputStream fos = new FileOutputStream(new File("everwhat.txt")); + try { + fos.write(fis.read()); + } finally { + fos.close(); + } + } finally { + fis.close(); + } + } + + + public static void twoResourcesCommonFix() throws IOException { + FileInputStream fis = null; + FileOutputStream fos = null; + try { + fis = new FileInputStream(new File("infile.txt")); + fos = new FileOutputStream(new File("outfile.txt")); + fos.write(fis.read()); + } finally { + try { + if (fis != null) fis.close(); + } catch (Exception e) { + } + try { + if (fos != null) fos.close(); + } catch (Exception e) { + } + } + } + + + public static void twoResourcesServerSocket() throws IOException { + ServerSocket a = null; + ServerSocket b = null; + try { + a = new ServerSocket(); + b = new ServerSocket(); + } finally { + if (a != null) a.close(); + if (b != null) b.close(); + } + } + + + public static void twoResourcesRandomAccessFile() throws IOException { + RandomAccessFile a = null; + RandomAccessFile b = null; + try { + a = new RandomAccessFile("", "rw"); + b = new RandomAccessFile("", "rw"); + } finally { + if (a != null) a.close(); + if (b != null) b.close(); + } + } + + public static void twoResourcesRandomAccessFileCommonFix() throws IOException { + RandomAccessFile a = null; + RandomAccessFile b = null; + try { + a = new RandomAccessFile("", "rw"); + b = new RandomAccessFile("", "rw"); + } finally { + try { + if (a != null) a.close(); + } catch (Exception e) { + } + try { + if (b != null) b.close(); + } catch (Exception e) { + } + } + } + + + //NestedResource tests + + // BufferedInputStream does not throw exception, and its close + // closes the FileInputStream as well + public void nestedGood() throws IOException { + BufferedInputStream b = new BufferedInputStream(new FileInputStream("file.txt")); + b.close(); + } + + // GZipInputStream can throw IO Exception + // in which case the new FileInputStream will be dangling + public void nestedBad1() throws IOException { + GZIPInputStream g = new GZIPInputStream(new FileInputStream("file.txt")); + g.close(); + } + + + public void nestedBad2() throws IOException { + GZIPOutputStream g = new GZIPOutputStream(new FileOutputStream("file.txt")); + g.close(); + } + + + /* Fixed versions of this are below with ObjectInputStream tests */ + public void objectInputStreamClosedNestedBad() throws IOException { + ObjectInputStream oin = null; + try { + oin = new ObjectInputStream(new FileInputStream("file.txt")); + int a = oin.available(); + } catch (IOException e) { + } finally { + if (oin != null) oin.close(); + } + } + + /* Fixed versions of this are below with ObjectInputStream tests */ + public void objectOutputStreamClosedNestedBad() throws IOException { + ObjectOutputStream oin = null; + try { + oin = new ObjectOutputStream(new FileOutputStream("file.txt")); + oin.write(3); + } catch (IOException e) { + } finally { + if (oin != null) oin.close(); + } + } + + + //ZipFile tests (Jarfile Tests also test Zipfiles) + + public static void zipFileLeakExceptionalBranch() throws IOException { + ZipFile j = null; + try { + j = new ZipFile(""); + } catch (IOException e) { + FileOutputStream fis = new FileOutputStream("file.txt"); + //The purpose of this is to cause a leak, from when ZipFile constructor throws + } finally { + if (j != null) j.close(); + } + } + + public static void zipFileNoLeak() throws IOException { + ZipFile j = null; + try { + j = new ZipFile(""); + } finally { + if (j != null) j.close(); + } + } + + //JarFile tests + + public boolean jarFileClosed() { + JarFile jarFile = null; + try { + jarFile = new JarFile(""); + } catch (IOException e) { + } finally { + try { + if (jarFile != null) { + jarFile.close(); + } + } catch (IOException e) { + } + } + return false; + } + + public boolean jarFileNotClosed() { + JarFile jarFile = null; + try { + jarFile = new JarFile(""); + } catch (IOException e) { + } + return false; + } + + //FileInputStream tests + + public void fileInputStreamNotClosedAfterRead() { + FileInputStream fis; + try { + fis = new FileInputStream("file.txt"); + fis.read(); + fis.close(); + } catch (IOException e) { + } + } + + + public void fileInputStreamClosed() throws IOException { + FileInputStream fis = null; + try { + fis = new FileInputStream("file.txt"); + fis.available(); + } catch (IOException e) { + } finally { + if (fis != null) fis.close(); + } + } + + //PipedInputStream tests + + public void pipedInputStreamNotClosedAfterRead(PipedOutputStream pout) { + PipedInputStream pin; + try { + pin = new PipedInputStream(pout); + int data = pin.read(); + pin.close(); + } catch (IOException e) { + } + } + + + public void pipedInputStreamClosed(PipedOutputStream pout) throws IOException { + PipedInputStream pin = null; + try { + pin = new PipedInputStream(pout); + int data = pin.read(); + } catch (IOException e) { + } finally { + pin.close(); + } + } + + //PipedOutputStream tests + + public void pipedOutputStreamNotClosedAfterWrite() { + byte[] arr = {1, 2, 3, 4, 5}; + PipedOutputStream pout; + try { + pout = new PipedOutputStream(); + pout.write(arr); + pout.close(); + } catch (IOException e) { + } + } + + + public void pipedOutputStreamClosed(PipedInputStream pin) throws IOException { + PipedOutputStream pout = null; + try { + pout = new PipedOutputStream(pin); + pout.flush(); + } catch (IOException e) { + } finally { + pout.close(); + } + } + + //ObjectOutputStream tests + + public void objectOutputStreamNotClosedAfterWrite() { + byte[] arr = {1, 2, 3, 4, 5}; + ObjectOutputStream oout; + try { + oout = new ObjectOutputStream(new FileOutputStream("file.txt")); + oout.write(arr); + oout.close(); + } catch (IOException e) { + } + } + + + public void objectOutputStreamClosed() throws IOException { + ObjectOutputStream oout = null; + FileOutputStream fis = new FileOutputStream("file.txt"); + try { + oout = new ObjectOutputStream(fis); + oout.flush(); + } catch (IOException e) { + } finally { + fis.close(); + } + } + + + //ObjectInputStream tests + + public void objectInputStreamNotClosedAfterRead() { + ObjectInputStream oin; + try { + oin = new ObjectInputStream(new FileInputStream("file.txt")); + oin.read(); + oin.close(); + } catch (IOException e) { + } + } + + + public void objectInputStreamClosed() throws IOException { + ObjectInputStream oin = null; + FileInputStream fis = new FileInputStream("file.txt"); + try { + oin = new ObjectInputStream(fis); + int a = oin.available(); + } catch (IOException e) { + } finally { + if (oin != null) { + oin.close(); + } else { + fis.close(); + } + } + } + + public void objectInputStreamClosed2() throws IOException { + ObjectInputStream oin = null; + FileInputStream fis = new FileInputStream("file.txt"); + try { + oin = new ObjectInputStream(fis); + int a = oin.available(); + } catch (IOException e) { + } finally { + fis.close(); + } + } + + + //JarInputStream tests + + public static void jarInputStreamNoLeak() throws IOException { + FileInputStream fos = new FileInputStream(""); + try { + JarInputStream g = new JarInputStream(fos); + g.close(); + } catch (IOException e) { + fos.close(); + } + } + + public static void jarInputStreamLeak() throws IOException { + FileInputStream fos = new FileInputStream(""); + try { + JarInputStream g = new JarInputStream(fos); // Testing exceptional condition in constructor + g.close(); + } catch (IOException e) { + // fos.close(); + } + } + + public static void nestedBadJarInputStream(File file) throws IOException { + JarInputStream g = new JarInputStream(new FileInputStream(file)); + g.close(); + } + + + //JarOutputStream tests + + public static void jarOutputStreamNoLeak() throws IOException { + FileOutputStream fos = new FileOutputStream(""); + try { + JarOutputStream g = new JarOutputStream(fos); + g.close(); + } catch (IOException e) { + fos.close(); + } + } + + + public static void jarOutputStreamLeak() throws IOException { + FileOutputStream fos = new FileOutputStream(""); + try { + JarOutputStream g = new JarOutputStream(fos); // Testing exceptional condition in constructor + g.close(); + } catch (IOException e) { + // fos.close(); + } + } + + public static void nestedBadJarOutputStream() throws IOException { + JarOutputStream g = new JarOutputStream(new FileOutputStream("file.txt")); + g.close(); + } + + + //Socket tests + + public void socketNotClosed() { + Socket socket = new Socket(); + } + + public void socketClosed() throws IOException { + Socket socket = new Socket(); + socket.close(); + } + + + //Socket InputStream tests + + public int socketInputStreamNotClosed(Socket socket) throws IOException { + InputStream stream = socket.getInputStream(); + return stream.read(); + } + + public void socketInputStreamClosed() throws IOException { + Socket socket = new Socket(); + InputStream stream = socket.getInputStream(); + try { + stream.close(); + } catch (Exception e) { + } + socket.close(); + } + + //Socket OutputStream tests + + public void socketOutputStreamNotClosed(Socket socket) throws IOException { + OutputStream stream = socket.getOutputStream(); + stream.write(10); + } + + public void socketOutputStreamClosed() throws IOException { + Socket socket = new Socket(); + OutputStream stream = socket.getOutputStream(); + try { + stream.close(); + } catch (Exception e) { + } + socket.close(); + } + + //ServerSocket tests + + public void serverSocketNotClosed() throws IOException { + ServerSocket listener = new ServerSocket(9090); + while (true) { + Socket socket = listener.accept(); + try { + PrintWriter out = + new PrintWriter(socket.getOutputStream(), true); + out.println(""); + } finally { + socket.close(); + } + listener.close(); + } + } + + public void serverSocketClosed() throws IOException { + ServerSocket socket = new ServerSocket(); + socket.close(); + } + + public void serverSocketWithSocketClosed() throws IOException { + ServerSocket listener = new ServerSocket(9090); + try { + while (true) { + Socket socket = listener.accept(); + try { + PrintWriter out = + new PrintWriter(socket.getOutputStream(), true); + out.println(""); + } finally { + socket.close(); + } + } + } finally { + listener.close(); + } + } + + //HttpURLConnection + + public void openHttpURLConnectionDisconnected() throws IOException { + String content = "TEXT"; + DataOutputStream outputStream = null; + HttpURLConnection connection = null; + URL address = new URL("http://www.facebook.com"); + connection = (HttpURLConnection) address.openConnection(); + try { + outputStream = new DataOutputStream(connection.getOutputStream()); + outputStream.writeBytes(content); + outputStream.flush(); + + } finally { + connection.disconnect(); + } + } + + public void openHttpURLConnectionNotDisconnected() throws IOException { + String content = "TEXT"; + DataOutputStream outputStream = null; + HttpURLConnection connection = null; + URL address = new URL("http://www.facebook.com"); + connection = (HttpURLConnection) address.openConnection(); + + outputStream = new DataOutputStream(connection.getOutputStream()); + outputStream.writeBytes(content); + } + + public void openHttpsURLConnectionNotDisconnected() throws IOException { + HttpsURLConnection connection = null; + URL address = new URL("https://www.facebook.com"); + connection = (HttpsURLConnection) address.openConnection(); + } + + public void openHttpsURLConnectionDisconnected() throws IOException { + HttpsURLConnection connection = null; + URL address = new URL("https://www.facebook.com"); + connection = (HttpsURLConnection) address.openConnection(); + connection.disconnect(); + } + + public void closedWithCloseables() throws IOException { + FileInputStream fs = new FileInputStream("file.txt"); + try { + fs.read(); + } finally { + Closeables.close(fs, false); + } + } + + public void closedQuietlyWithCloseables() throws IOException { + FileInputStream fs = new FileInputStream("file.txt"); + try { + fs.read(); + } finally { + Closeables.closeQuietly(fs); + } + } + + public void closeNullWithCloseables() throws IOException { + FileInputStream fs = null; + try { + fs = new FileInputStream("file.txt"); + } finally { + Closeables.close(fs, true); + } + } + + public void closeNullQuietlyWithCloseables() throws IOException { + FileInputStream fs = null; + try { + fs = new FileInputStream("file.txt"); + } finally { + Closeables.closeQuietly(fs); + } + } + + // JsonParser tests + + public void parseFromStringAndNotClose(JsonFactory factory) throws IOException { + UTF8StreamJsonParser parser = null; + try { + parser = (UTF8StreamJsonParser) factory.createParser("[]"); + Object o = parser.readValueAs(Object.class); + ignore(o); + } catch (Exception e) { + } finally { + } + } + + public void parseFromInputStreamAndClose(JsonFactory factory) throws IOException { + JsonParser parser = null; + FileInputStream in = null; + try { + in = new FileInputStream(""); + parser = factory.createParser(in); + Object o = parser.readValueAs(Object.class); + ignore(o); + } catch (Exception e) { + } finally { + if (in != null) in.close(); + } + // parser does not own a resources which is closed externally + } + + public void parseFromInputStreamAndLeak(JsonFactory factory) throws IOException { + JsonParser parser = null; + FileInputStream in = null; + try { + in = new FileInputStream(""); + parser = factory.createParser(in); + Object o = parser.readValueAs(Object.class); + ignore(o); + } catch (Exception e) { + } finally { + if (parser != null) parser.close(); + } + // parser does not own a resource which is leaked + } + + private void ignore(Object o) { + } + + + // Installation.java examples. Even the fix was a fp for a while + // for several reasons, so this test is just to make sure it remains + // banished forever + + + private String readInstallationFileGood(File installation) + throws IOException { + RandomAccessFile f = new RandomAccessFile(installation, "r"); + try { + byte[] bytes = new byte[(int) f.length()]; + f.readFully(bytes); + return new String(bytes); + } finally { + f.close(); + } + } + + private String readInstallationFileBad(File installation) + throws IOException { + RandomAccessFile f = new RandomAccessFile(installation, "r"); + byte[] bytes = new byte[(int) f.length()]; + f.readFully(bytes); + f.close(); + return new String(bytes); + } + + private int readConfigCloseStream(String mTurnConfigUrl) { + try { + URL url = new URL(mTurnConfigUrl); + URLConnection connection = url.openConnection(); + InputStream stream = connection.getInputStream(); + try { + return stream.read(); + } finally { + stream.close(); + } + } catch (Exception e) { + } + return 0; + } + + private int readConfigNotCloseStream(String mTurnConfigUrl) { + try { + URL url = new URL(mTurnConfigUrl); + URLConnection connection = url.openConnection(); + InputStream stream = connection.getInputStream(); + return stream.read(); + } catch (Exception e) { + } + return 0; + } + + private void readConfigNotClosedOK(String mTurnConfigUrl) { + try { + URL url = new URL(mTurnConfigUrl); + URLConnection connection = url.openConnection(); + ignore(connection); + } catch (Exception e) { + } + } + + // TypedArray + + public void themeObtainTypedArrayAndRecycle(Resources.Theme theme) { + TypedArray array = theme.obtainStyledAttributes(new int[]{}); + ignore(array); + array.recycle(); + } + + public void themeObtainTypedArrayAndLeak(Resources.Theme theme) { + TypedArray array = theme.obtainStyledAttributes(new int[]{}); + ignore(array); + } + + public void activityObtainTypedArrayAndRecycle(Activity activity) { + TypedArray array = activity.obtainStyledAttributes(new int[]{}); + ignore(array); + array.recycle(); + } + + public void activityObtainTypedArrayAndLeak(Activity activity) { + TypedArray array = activity.obtainStyledAttributes(new int[]{}); + ignore(array); + } + + public void contextObtainTypedArrayAndRecycle(Context context) { + TypedArray array = context.obtainStyledAttributes(new int[]{}); + ignore(array); + array.recycle(); + } + + public void contextObtainTypedArrayAndLeak(Context context) { + TypedArray array = context.obtainStyledAttributes(new int[]{}); + ignore(array); + } + + // FileChannel + + void copyFileLeak(File src, File dst) throws IOException { + FileChannel inChannel = new FileInputStream(src).getChannel(); + FileChannel outChannel = new FileOutputStream(dst).getChannel(); + try { + inChannel.transferTo(0, inChannel.size(), outChannel); + } finally { + if (inChannel != null) + inChannel.close(); + if (outChannel != null) + outChannel.close(); + } + } + + void copyFileClose(File src, File dst) throws IOException { + FileChannel inChannel = new FileInputStream(src).getChannel(); + try { + ignore(inChannel); + } finally { + inChannel.close(); + } + } + + protected long checkNotNullCauseNoLeak(URL mUrl) throws IOException { + URL url = new URL("http://www.facebook.com"); + HttpURLConnection serverConnection = + (HttpURLConnection) Preconditions.checkNotNull(url.openConnection()); + try { + ignore(serverConnection); + } catch (NumberFormatException nfe) { + } finally { + serverConnection.disconnect(); + } + return 0; + } + + void scannerNotClosed() throws IOException { + Scanner scanner = new Scanner(new FileInputStream("file.txt")); + } + + void scannerClosed() throws IOException { + Scanner scanner = new Scanner(new FileInputStream("file.txt")); + scanner.close(); + } + + void processDestroyed() { + Process process = null; + try { + process = Runtime.getRuntime().exec(""); + } catch (IOException e) { + } finally { + process.destroy(); + } + } + + class Container { + FileInputStream inputStream; + } + + native Container load(FileInputStream inputStream); + + public Container resourceReturnedIndirectly() { + FileInputStream inputStream; + Container container = null; + try { + inputStream = new FileInputStream("pif.txt"); + container = load(inputStream); + } catch (FileNotFoundException e) { + return null; + } + return container; + } + + native void unknownClose(Closeable c); + + public void resourceClosedBySkippedMethod() { + FileInputStream inputStream = null; + try { + inputStream = new FileInputStream("pif.txt"); + } catch (FileNotFoundException e) { + return; + } finally { + unknownClose(inputStream); + } + } + +} diff --git a/infer/tests/codetoanalyze/java/infer/ReturnValueIgnored.java b/infer/tests/codetoanalyze/java/infer/ReturnValueIgnored.java new file mode 100644 index 000000000..dc7bd2242 --- /dev/null +++ b/infer/tests/codetoanalyze/java/infer/ReturnValueIgnored.java @@ -0,0 +1,20 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package codetoanalyze.java.infer; + + +public class ReturnValueIgnored { + + private int m() { + return 1; + } + + public void returnValueIgnored() { + m(); + } + +} + diff --git a/infer/tests/codetoanalyze/java/infer/SomeLibrary.java b/infer/tests/codetoanalyze/java/infer/SomeLibrary.java new file mode 100644 index 000000000..b781826b7 --- /dev/null +++ b/infer/tests/codetoanalyze/java/infer/SomeLibrary.java @@ -0,0 +1,11 @@ +package codetoanalyze.java.infer; + +public class SomeLibrary { + + T t; + + T get() { + return t == null ? null : t; + } + +} diff --git a/infer/tests/codetoanalyze/java/infer/T.java b/infer/tests/codetoanalyze/java/infer/T.java new file mode 100644 index 000000000..2e50377f6 --- /dev/null +++ b/infer/tests/codetoanalyze/java/infer/T.java @@ -0,0 +1,10 @@ +// Copyright (c) 2015-Present Facebook. All rights reserved. + +package codetoanalyze.java.infer; + +public class T { + int x; + + void f() { + } +} diff --git a/infer/tests/codetoanalyze/java/infer/WriterLeaks.java b/infer/tests/codetoanalyze/java/infer/WriterLeaks.java new file mode 100644 index 000000000..e670a02cc --- /dev/null +++ b/infer/tests/codetoanalyze/java/infer/WriterLeaks.java @@ -0,0 +1,196 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + + +package codetoanalyze.java.infer; + + +import java.io.BufferedWriter; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PipedReader; +import java.io.PipedWriter; +import java.io.PrintWriter; +import java.io.Writer; + +public class WriterLeaks { + + //Writer tests + + public void writerNotClosedAfterWrite() { + Writer writer; + try { + writer = new PrintWriter("file.txt"); + writer.write(10); + writer.close(); + } catch (IOException e) { + } + } + + + public void writerClosed() throws IOException { + Writer writer = null; + try { + writer = new PrintWriter("file.txt"); + writer.write(10); + } catch (IOException e) { + } finally { + if (writer != null) + writer.close(); + } + } + + //PrintWriter tests + + public void printWriterNotClosedAfterAppend() { + PrintWriter writer; + try { + writer = new PrintWriter("file.txt"); + writer = writer.append('0'); + writer.close(); + } catch (IOException e) { + } + } + + + public void printWriterClosed() throws IOException { + PrintWriter writer = null; + try { + writer = new PrintWriter("file.txt"); + writer = writer.append(null); + } catch (IOException e) { + } finally { + if (writer != null) + writer.close(); + } + } + + //BufferedWriter tests + + public void bufferedWriterNotClosedAfterWrite() { + BufferedWriter writer; + try { + FileWriter fw = new FileWriter("file.txt"); + writer = new BufferedWriter(fw); + writer.write("word"); + writer.close(); + } catch (IOException e) { + } + } + + + public void bufferedWriterClosed() throws IOException { + BufferedWriter writer = null; + try { + FileWriter fw = new FileWriter("file.txt"); + writer = new BufferedWriter(fw); + writer.flush(); + } catch (IOException e) { + } finally { + if (writer != null) + writer.close(); + } + } + + //OutputStreamWriter tests + + public void outputStreamWriterNotClosedAfterWrite() { + OutputStreamWriter writer; + try { + writer = new OutputStreamWriter(new FileOutputStream("file.txt")); + writer.write("word"); + writer.close(); + } catch (IOException e) { + } + } + + + public void outputStreamWriterClosed() throws IOException { + OutputStreamWriter writer = null; + try { + writer = new OutputStreamWriter(new FileOutputStream("file.txt")); + writer.write(10); + } catch (IOException e) { + } finally { + if (writer != null) + writer.close(); + } + } + + //FileWriter tests + + public void fileWriterNotClosedAfterWrite() { + FileWriter writer; + try { + writer = new FileWriter("file.txt"); + writer.write("word"); + writer.close(); + } catch (IOException e) { + } + } + + + public void fileWriterClosed() throws IOException { + FileWriter writer = null; + try { + writer = new FileWriter("file.txt"); + writer.write(10); + } catch (IOException e) { + } finally { + if (writer != null) + writer.close(); + } + } + + //PipedWriter tests + + public void pipedWriterNotClosedAfterConstructedWithReader() { + PipedWriter writer; + PipedReader reader; + try { + reader = new PipedReader(); + writer = new PipedWriter(reader); + writer.write(42); + writer.close(); + } catch (IOException e) { + } + } + + public void pipedWriterNotClosedAfterConnect(PipedReader reader) { + PipedWriter writer; + try { + writer = new PipedWriter(); + writer.connect(reader); + writer.write(42); + writer.close(); + } catch (IOException e) { + } + } + + public void pipedWriterNotConnected() { + PipedWriter writer; + try { + writer = new PipedWriter(); + writer.close(); + } catch (IOException e) { + } + } + + public void pipedWriterClosed(PipedReader reader) throws IOException { + PipedWriter writer = null; + try { + writer = new PipedWriter(); + writer.connect(reader); + writer.write(42); + } catch (IOException e) { + } finally { + if (writer != null) + writer.close(); + } + } + +} diff --git a/infer/tests/codetoanalyze/java/tracing/ArrayIndexOutOfBoundsExceptionExample.java b/infer/tests/codetoanalyze/java/tracing/ArrayIndexOutOfBoundsExceptionExample.java new file mode 100644 index 000000000..adffe07b2 --- /dev/null +++ b/infer/tests/codetoanalyze/java/tracing/ArrayIndexOutOfBoundsExceptionExample.java @@ -0,0 +1,28 @@ +// Copyright (c) 2015-Present Facebook. All rights reserved. + +package codetoanalyze.java.tracing; + +import com.facebook.infer.annotation.Verify; + +public class ArrayIndexOutOfBoundsExceptionExample { + + void callMethodFromArray(T[] array, int index) { + array[index].f(); + } + + @Verify + void callWithWrongIndex(T[] array, int index) { + if (array != null) { + if (index >= 0 && index <= array.length) { + callMethodFromArray(array, index); // No longer found! + } + } + } + + @Verify + void callOutOfBound() { + T[] array = new T[42]; + callMethodFromArray(array, -5); + } + +} diff --git a/infer/tests/codetoanalyze/java/tracing/BUCK b/infer/tests/codetoanalyze/java/tracing/BUCK new file mode 100644 index 000000000..f92ba7a2d --- /dev/null +++ b/infer/tests/codetoanalyze/java/tracing/BUCK @@ -0,0 +1,40 @@ +sources = glob(['**/*.java']) + +dependencies = [ + '//infer/annotations:annotations', +] + +java_library( + name = 'tracing', + srcs = sources, + deps = dependencies, + visibility = [ + 'PUBLIC' + ] +) + +out = 'out' +clean_cmd = ' '.join(['rm', '-rf', out]) +classpath = ':'.join([('$(classpath ' + path + ')') for path in dependencies]) +infer_cmd = ' '.join([ + 'infer', + '-o', out, + '-a', 'tracing', + '--', + 'javac', + '-cp', classpath, + '$SRCS', +]) +copy_cmd = ' '.join(['cp', out + '/report.csv', '$OUT']) +command = ' && '.join([clean_cmd, infer_cmd, copy_cmd]) + +genrule( + name = 'analyze', + srcs = sources, + out = 'report.csv', + cmd = command, + deps = dependencies + [':tracing'], + visibility = [ + 'PUBLIC', + ] +) diff --git a/infer/tests/codetoanalyze/java/tracing/ClassCastExceptionExample.java b/infer/tests/codetoanalyze/java/tracing/ClassCastExceptionExample.java new file mode 100644 index 000000000..288d32ae0 --- /dev/null +++ b/infer/tests/codetoanalyze/java/tracing/ClassCastExceptionExample.java @@ -0,0 +1,31 @@ +// Copyright (c) 2015-Present Facebook. All rights reserved. + +package codetoanalyze.java.tracing; + +import com.facebook.infer.annotation.Verify; + +class S extends T {} + +public class ClassCastExceptionExample { + + S cast(T t) { + return (S) t; + } + + void foo() { + T t = new T(); + S s = cast(t); + s.toString(); + } + + T m; + + @Verify + public S bar(int x) { + if (x < 4 && m != null) { + return (S) m; + } + return null; + } + +} diff --git a/infer/tests/codetoanalyze/java/tracing/LocallyDefinedExceptionExample.java b/infer/tests/codetoanalyze/java/tracing/LocallyDefinedExceptionExample.java new file mode 100644 index 000000000..03a52e8c2 --- /dev/null +++ b/infer/tests/codetoanalyze/java/tracing/LocallyDefinedExceptionExample.java @@ -0,0 +1,39 @@ +// Copyright (c) 2015-Present Facebook. All rights reserved. + +package codetoanalyze.java.tracing; + +import com.facebook.infer.annotation.Verify; + +class LocallyDefinedException extends RuntimeException { + + public LocallyDefinedException(String message) { + super(message); + } + +} + +public class LocallyDefinedExceptionExample { + + T t; + + public LocallyDefinedExceptionExample() { + this.t = new T(); + this.t.x = 42; + } + + void setT(T t) { + this.t = t; + } + + @Verify + void fieldInvariant() { + if (this.t != null) { + if (this.t.x != 42) { + throw new LocallyDefinedException("Field expected to be equal to 42"); + } else { + this.t.x = 0; + } + } + } + +} diff --git a/infer/tests/codetoanalyze/java/tracing/NullPointerExceptionExample.java b/infer/tests/codetoanalyze/java/tracing/NullPointerExceptionExample.java new file mode 100644 index 000000000..228de1025 --- /dev/null +++ b/infer/tests/codetoanalyze/java/tracing/NullPointerExceptionExample.java @@ -0,0 +1,20 @@ +// Copyright (c) 2015-Present Facebook. All rights reserved. + +package codetoanalyze.java.tracing; + +import com.facebook.infer.annotation.Verify; + +public class NullPointerExceptionExample { + + void deref(T t) { + t.f(); + } + + @Verify + void callDeref(T t, boolean condition) { + if (condition) { + deref(t); + } + } + +} diff --git a/infer/tests/codetoanalyze/java/tracing/ReportOnMainExample.java b/infer/tests/codetoanalyze/java/tracing/ReportOnMainExample.java new file mode 100644 index 000000000..38d70e2a3 --- /dev/null +++ b/infer/tests/codetoanalyze/java/tracing/ReportOnMainExample.java @@ -0,0 +1,23 @@ +// Copyright (c) 2015-Present Facebook. All rights reserved. + +package codetoanalyze.java.tracing; + +public class ReportOnMainExample { + + T t; + + native boolean test(); + + void foo() { + if (test() && t == null) { + return; + } + t.f(); + } + + public static void main(String[] args) { + ReportOnMainExample example = new ReportOnMainExample(); + example.foo(); + } + +} diff --git a/infer/tests/codetoanalyze/java/tracing/T.java b/infer/tests/codetoanalyze/java/tracing/T.java new file mode 100644 index 000000000..e137beb91 --- /dev/null +++ b/infer/tests/codetoanalyze/java/tracing/T.java @@ -0,0 +1,9 @@ +// Copyright (c) 2015-Present Facebook. All rights reserved. + +package codetoanalyze.java.tracing; + +public class T { + int x; + void f() { + } +} diff --git a/infer/tests/codetoanalyze/java/tracing/UnavoidableExceptionExample.java b/infer/tests/codetoanalyze/java/tracing/UnavoidableExceptionExample.java new file mode 100644 index 000000000..b3a6a4b56 --- /dev/null +++ b/infer/tests/codetoanalyze/java/tracing/UnavoidableExceptionExample.java @@ -0,0 +1,26 @@ +// Copyright (c) 2015-Present Facebook. All rights reserved. + +package codetoanalyze.java.tracing; + +public class UnavoidableExceptionExample { + + static T create() { + return null; + } + + static void cannotAvoidNPE() { + T t = create(); + t.f(); + } + + static void unavoidableNPEWithParameter(boolean b) { + T t = create(); + t.f(); + } + + void virtualMethodWithUnavoidableNPE(boolean b) { + T t = create(); + t.f(); + } + +} diff --git a/infer/tests/codetoanalyze/objc/errors/category_procdesc/EOCPerson.dot b/infer/tests/codetoanalyze/objc/errors/category_procdesc/EOCPerson.dot new file mode 100644 index 000000000..af6d7db89 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/category_procdesc/EOCPerson.dot @@ -0,0 +1,24 @@ +digraph iCFG { +6 [label="6: Call _fun_NSLog \n n$1=_fun_NSString_stringWithUTF8String:(\"BTaking vacations\":char *) [line 16]\n _fun_NSLog(n$1:struct objc_object *) [line 16]\n REMOVE_TEMPS(n$1); [line 16]\n APPLY_ABSTRACTION; [line 16]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: Exit EOCPerson_takeVacationFromWork \n " color=yellow style=filled] + + +4 [label="4: Start EOCPerson_takeVacationFromWork\nFormals: self:class EOCPerson *\nLocals: \n DECLARE_LOCALS(&return); [line 15]\n NULLIFY(&self,false); [line 15]\n " color=yellow style=filled] + + + 4 -> 6 ; +3 [label="3: Call _fun_NSLog \n n$0=_fun_NSString_stringWithUTF8String:(\"Performing days at work\":char *) [line 11]\n _fun_NSLog(n$0:struct objc_object *) [line 11]\n REMOVE_TEMPS(n$0); [line 11]\n APPLY_ABSTRACTION; [line 11]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit EOCPerson_performDaysWork \n " color=yellow style=filled] + + +1 [label="1: Start EOCPerson_performDaysWork\nFormals: self:class EOCPerson *\nLocals: \n DECLARE_LOCALS(&return); [line 10]\n NULLIFY(&self,false); [line 10]\n " color=yellow style=filled] + + + 1 -> 3 ; +} diff --git a/infer/tests/codetoanalyze/objc/errors/category_procdesc/EOCPerson.h b/infer/tests/codetoanalyze/objc/errors/category_procdesc/EOCPerson.h new file mode 100644 index 000000000..309a4d559 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/category_procdesc/EOCPerson.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +// Splitting EOCPerson into categories +#import +#import + +@interface EOCPerson : NSObject +@property (nonatomic, copy, readonly) NSString *firstName; +@property (nonatomic, copy, readonly) NSString *lastName; +@property (nonatomic, strong, readonly) NSArray *friends; + +- (id)initWithFirstName:(NSString*)firstName + andLastName:(NSString*)lastName; +@end + +@interface EOCPerson (Friendship) +- (void)addFriend:(EOCPerson*)person; +- (void)removeFriend:(EOCPerson*)person; +- (BOOL)isFriendsWith:(EOCPerson*)person; +@end + +@interface EOCPerson (Work) +- (void)performDaysWork; +- (void)takeVacationFromWork; +@end + +@interface EOCPerson (Play) +- (void)goToTheCinema; +- (void)goToSportsGame; +@end diff --git a/infer/tests/codetoanalyze/objc/errors/category_procdesc/EOCPerson.m b/infer/tests/codetoanalyze/objc/errors/category_procdesc/EOCPerson.m new file mode 100644 index 000000000..3cac428d6 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/category_procdesc/EOCPerson.m @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +#import "EOCPerson.h" + +@implementation EOCPerson (Work) + +- (void)performDaysWork { + NSLog(@"Performing days at work"); +} + + +- (void)takeVacationFromWork{ + NSLog(@"BTaking vacations"); +} +@end diff --git a/infer/tests/codetoanalyze/objc/errors/category_procdesc/main.c b/infer/tests/codetoanalyze/objc/errors/category_procdesc/main.c new file mode 100644 index 000000000..2f8a5a078 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/category_procdesc/main.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. + */ + +#import "EOCPerson.h" + +int main() { + EOCPerson *person = [[EOCPerson alloc] init]; + [person performDaysWork]; + int *x = malloc(sizeof(int)); + return 0; +} diff --git a/infer/tests/codetoanalyze/objc/errors/category_procdesc/main.dot b/infer/tests/codetoanalyze/objc/errors/category_procdesc/main.dot new file mode 100644 index 000000000..241f0669b --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/category_procdesc/main.dot @@ -0,0 +1,25 @@ +digraph iCFG { +6 [label="6: DeclStmt \n n$4=_fun___objc_alloc_no_fail(sizeof(class EOCPerson ):class EOCPerson *) [line 9]\n n$2=_fun_EOCPerson_init(n$4:class EOCPerson *) virtual [line 9]\n *&person:class EOCPerson *=n$2 [line 9]\n REMOVE_TEMPS(n$2,n$4); [line 9]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: Message Call: performDaysWork \n n$1=*&person:class EOCPerson * [line 10]\n _fun_EOCPerson_performDaysWork(n$1:class EOCPerson *) virtual [line 10]\n REMOVE_TEMPS(n$1); [line 10]\n NULLIFY(&person,false); [line 10]\n " shape="box"] + + + 5 -> 4 ; +4 [label="4: DeclStmt \n n$0=_fun_malloc_no_fail(sizeof(int ):int ) [line 11]\n *&x:int *=n$0 [line 11]\n REMOVE_TEMPS(n$0); [line 11]\n NULLIFY(&x,false); [line 11]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n *&return:int =0 [line 12]\n APPLY_ABSTRACTION; [line 12]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: person:class EOCPerson * x:int * \n DECLARE_LOCALS(&return,&person,&x); [line 8]\n NULLIFY(&person,false); [line 8]\n NULLIFY(&x,false); [line 8]\n " color=yellow style=filled] + + + 1 -> 6 ; +} diff --git a/infer/tests/codetoanalyze/objc/errors/field_superclass/A.h b/infer/tests/codetoanalyze/objc/errors/field_superclass/A.h new file mode 100644 index 000000000..add1dbd52 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/field_superclass/A.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + + +#import + +@interface A : NSObject { + @public int x; + @public A *a; +} + +@end diff --git a/infer/tests/codetoanalyze/objc/errors/field_superclass/B.h b/infer/tests/codetoanalyze/objc/errors/field_superclass/B.h new file mode 100644 index 000000000..30fdfdb9a --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/field_superclass/B.h @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + + +#import "A.h" + +@interface B : A + +@end diff --git a/infer/tests/codetoanalyze/objc/errors/field_superclass/B.m b/infer/tests/codetoanalyze/objc/errors/field_superclass/B.m new file mode 100644 index 000000000..c30515daf --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/field_superclass/B.m @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import "B.h" + +@implementation B + +@end diff --git a/infer/tests/codetoanalyze/objc/errors/field_superclass/SuperExample.dot b/infer/tests/codetoanalyze/objc/errors/field_superclass/SuperExample.dot new file mode 100644 index 000000000..2ddd6159a --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/field_superclass/SuperExample.dot @@ -0,0 +1,47 @@ +digraph iCFG { +12 [label="12: DeclStmt \n n$6=_fun___objc_alloc_no_fail(sizeof(class A ):class A *) [line 41]\n n$7=_fun_A_init(n$6:class A *) virtual [line 41]\n *&a:struct objc_object *=n$7 [line 41]\n REMOVE_TEMPS(n$6,n$7); [line 41]\n NULLIFY(&a,false); [line 41]\n " shape="box"] + + + 12 -> 11 ; +11 [label="11: Release the autorelease pool \n n$4=_fun___objc_release_autorelease_pool() [line 40]\n REMOVE_TEMPS(n$4); [line 40]\n APPLY_ABSTRACTION; [line 40]\n " shape="box"] + + + 11 -> 10 ; +10 [label="10: Exit main \n " color=yellow style=filled] + + +9 [label="9: Start main\nFormals: argc:int argv:char **\nLocals: a:struct objc_object * \n DECLARE_LOCALS(&return,&a); [line 39]\n NULLIFY(&a,false); [line 39]\n NULLIFY(&argc,false); [line 39]\n NULLIFY(&argv,false); [line 39]\n " color=yellow style=filled] + + + 9 -> 12 ; +8 [label="8: BinaryOperatorStmt: Assign \n n$3=*&self:class A * [line 32]\n n$2=_fun_B_init(n$3:class A *) [line 32]\n *&self:class A *=n$2 [line 32]\n REMOVE_TEMPS(n$2,n$3); [line 32]\n " shape="box"] + + + 8 -> 7 ; +7 [label="7: BinaryOperatorStmt: Assign \n n$1=*&self:class A * [line 33]\n *n$1.a:int =4 [line 33]\n REMOVE_TEMPS(n$1); [line 33]\n " shape="box"] + + + 7 -> 6 ; +6 [label="6: Return Stmt \n n$0=*&self:class A * [line 34]\n *&return:struct objc_object *=n$0 [line 34]\n REMOVE_TEMPS(n$0); [line 34]\n NULLIFY(&self,false); [line 34]\n APPLY_ABSTRACTION; [line 34]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: Exit A_init \n " color=yellow style=filled] + + +4 [label="4: Start A_init\nFormals: self:class A *\nLocals: \n DECLARE_LOCALS(&return); [line 30]\n " color=yellow style=filled] + + + 4 -> 8 ; +3 [label="3: Return Stmt \n *&return:struct objc_object *=0 [line 16]\n APPLY_ABSTRACTION; [line 16]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit B_init \n " color=yellow style=filled] + + +1 [label="1: Start B_init\nFormals: self:class B *\nLocals: \n DECLARE_LOCALS(&return); [line 14]\n NULLIFY(&self,false); [line 14]\n " color=yellow style=filled] + + + 1 -> 3 ; +} diff --git a/infer/tests/codetoanalyze/objc/errors/field_superclass/SuperExample.m b/infer/tests/codetoanalyze/objc/errors/field_superclass/SuperExample.m new file mode 100644 index 000000000..e0794e7d9 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/field_superclass/SuperExample.m @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface B : NSObject + +@end + +@implementation B + +- (instancetype) init +{ + return nil; +} + +@end + +@interface A : B + +@end + +@implementation A +{ + int a; +} + +- (instancetype) init +{ + self = [super init]; + self->a = 4; + return self; +} + +@end + +int main(int argc, char *argv[]) { + @autoreleasepool { + __unused id a = [A new]; + } +} diff --git a/infer/tests/codetoanalyze/objc/errors/field_superclass/field.c b/infer/tests/codetoanalyze/objc/errors/field_superclass/field.c new file mode 100644 index 000000000..3c4266a82 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/field_superclass/field.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#include "B.h" + + +int main() { + B *b = [B alloc]; + b->x = 5; + b->a = b; // create cycle --> leak + return 0; +} diff --git a/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/ArcExample.dot b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/ArcExample.dot new file mode 100644 index 000000000..b6c6a5df3 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/ArcExample.dot @@ -0,0 +1,54 @@ +digraph iCFG { +14 [label="14: BinaryOperatorStmt: Assign \n n$10=*&self:class A * [line -1]\n n$11=*&son:class A * [line -1]\n _fun___objc_retain(n$11:class A *) [line -1]\n n$12=*n$10._son:class A * [line -1]\n *n$10._son:class A *=n$11 [line -1]\n _fun___objc_release(n$12:class A *) [line -1]\n REMOVE_TEMPS(n$10,n$11,n$12); [line -1]\n NULLIFY(&self,false); [line -1]\n NULLIFY(&son,false); [line -1]\n APPLY_ABSTRACTION; [line -1]\n " shape="box"] + + + 14 -> 13 ; +13 [label="13: Exit A_setSon: \n " color=yellow style=filled] + + +12 [label="12: Start A_setSon:\nFormals: self:class A * son:class A *\nLocals: \n DECLARE_LOCALS(&return); [line -1]\n " color=yellow style=filled] + + + 12 -> 14 ; +11 [label="11: Return Stmt \n n$7=*&self:class A * [line -1]\n n$8=*n$7._son:class A * [line -1]\n *&return:class A *=n$8 [line -1]\n n$9=_fun___set_autorelease_attribute(n$8:class A *) [line -1]\n REMOVE_TEMPS(n$7,n$8,n$9); [line -1]\n NULLIFY(&self,false); [line -1]\n APPLY_ABSTRACTION; [line -1]\n " shape="box"] + + + 11 -> 10 ; +10 [label="10: Exit A_son \n " color=yellow style=filled] + + +9 [label="9: Start A_son\nFormals: self:class A *\nLocals: \n DECLARE_LOCALS(&return); [line -1]\n " color=yellow style=filled] + + + 9 -> 11 ; +8 [label="8: DeclStmt \n n$6=_fun___objc_alloc_no_fail(sizeof(class NSString ):class NSString *) [line 25]\n *&s:class NSString *=n$6 [line 25]\n REMOVE_TEMPS(n$6); [line 25]\n " shape="box"] + + + 8 -> 7 ; +7 [label="7: Return Stmt \n n$4=*&s:class NSString * [line 26]\n *&return:class NSString *=n$4 [line 26]\n REMOVE_TEMPS(n$4); [line 26]\n NULLIFY(&s,false); [line 26]\n APPLY_ABSTRACTION; [line 26]\n " shape="box"] + + + 7 -> 6 ; +6 [label="6: Exit A_newS \n " color=yellow style=filled] + + +5 [label="5: Start A_newS\nFormals: self:class A *\nLocals: s:class NSString * \n DECLARE_LOCALS(&return,&s); [line 24]\n NULLIFY(&s,false); [line 24]\n NULLIFY(&self,false); [line 24]\n " color=yellow style=filled] + + + 5 -> 8 ; +4 [label="4: DeclStmt \n n$3=_fun___objc_alloc_no_fail(sizeof(class NSString ):class NSString *) [line 19]\n *&s:class NSString *=n$3 [line 19]\n REMOVE_TEMPS(n$3); [line 19]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n n$0=*&s:class NSString * [line 20]\n *&return:class NSString *=n$0 [line 20]\n n$1=_fun___set_autorelease_attribute(n$0:class NSString *) [line 20]\n REMOVE_TEMPS(n$0,n$1); [line 20]\n NULLIFY(&s,false); [line 20]\n APPLY_ABSTRACTION; [line 20]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit A_getS \n " color=yellow style=filled] + + +1 [label="1: Start A_getS\nFormals: self:class A *\nLocals: s:class NSString * \n DECLARE_LOCALS(&return,&s); [line 18]\n NULLIFY(&s,false); [line 18]\n NULLIFY(&self,false); [line 18]\n " color=yellow style=filled] + + + 1 -> 4 ; +} diff --git a/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/ArcExample.m b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/ArcExample.m new file mode 100644 index 000000000..2adf6de1d --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/ArcExample.m @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2014- Facebook. + * All rights reserved. + */ + +#import +#import + +@interface A : NSObject { + int x; +} +@property A *son; +@end + +@implementation A + +/* autorelease is added */ +-(NSString*) getS{ + NSString *s = [NSString alloc]; + return s; +} + +/* autorelease is not added */ +-(NSString*) newS{ + NSString *s = [NSString alloc]; + return s; +} + +@end diff --git a/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/AutoreleaseExample.dot b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/AutoreleaseExample.dot new file mode 100644 index 000000000..116eb1720 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/AutoreleaseExample.dot @@ -0,0 +1,159 @@ +digraph iCFG { +41 [label="41: DeclStmt \n n$30=_fun___objc_alloc_no_fail(sizeof(class NSAutoreleasePool ):class NSAutoreleasePool *) [line 56]\n n$28=_fun_NSAutoreleasePool_init(n$30:class NSAutoreleasePool *) [line 56]\n *&pool:class NSAutoreleasePool *=n$28 [line 56]\n REMOVE_TEMPS(n$28,n$30); [line 56]\n " shape="box"] + + + 41 -> 40 ; +40 [label="40: DeclStmt \n n$27=_fun___objc_alloc_no_fail(sizeof(class NSString ):class NSString *) [line 57]\n n$25=_fun___set_autorelease_attribute(n$27:class NSString *) [line 57]\n *&string:class NSString *=n$25 [line 57]\n REMOVE_TEMPS(n$25,n$27); [line 57]\n " shape="box"] + + + 40 -> 39 ; +39 [label="39: Message Call: release \n n$24=*&pool:class NSAutoreleasePool * [line 59]\n _fun___objc_release_autorelease_pool(n$24:class NSAutoreleasePool *) [line 59]\n REMOVE_TEMPS(n$24); [line 59]\n NULLIFY(&pool,false); [line 59]\n " shape="box"] + + + 39 -> 38 ; +38 [label="38: DeclStmt \n n$23=*&string:class NSString * [line 60]\n *&c:class NSString *=n$23 [line 60]\n REMOVE_TEMPS(n$23); [line 60]\n NULLIFY(&c,false); [line 60]\n NULLIFY(&string,false); [line 60]\n APPLY_ABSTRACTION; [line 60]\n " shape="box"] + + + 38 -> 37 ; +37 [label="37: Exit test3 \n " color=yellow style=filled] + + +36 [label="36: Start test3\nFormals: \nLocals: pool:class NSAutoreleasePool * string:class NSString * c:class NSString * \n DECLARE_LOCALS(&return,&pool,&string,&c); [line 55]\n NULLIFY(&c,false); [line 55]\n NULLIFY(&pool,false); [line 55]\n NULLIFY(&string,false); [line 55]\n " color=yellow style=filled] + + + 36 -> 41 ; +35 [label="35: DeclStmt \n *&s1:class A *=0 [line 44]\n " shape="box"] + + + 35 -> 34 ; +34 [label="34: DeclStmt \n *&s2:class A *=0 [line 45]\n " shape="box"] + + + 34 -> 33 ; +33 [label="33: DeclStmt \n *&s3:class A *=0 [line 46]\n " shape="box"] + + + 33 -> 32 ; +32 [label="32: BinaryOperatorStmt: Assign \n n$22=_fun_createA() [line 48]\n *&s1:class A *=n$22 [line 48]\n REMOVE_TEMPS(n$22); [line 48]\n " shape="box"] + + + 32 -> 31 ; +31 [label="31: BinaryOperatorStmt: Assign \n n$21=_fun_createA() [line 49]\n *&s2:class A *=n$21 [line 49]\n REMOVE_TEMPS(n$21); [line 49]\n " shape="box"] + + + 31 -> 30 ; +30 [label="30: BinaryOperatorStmt: Assign \n n$20=_fun_createA() [line 50]\n *&s3:class A *=n$20 [line 50]\n REMOVE_TEMPS(n$20); [line 50]\n " shape="box"] + + + 30 -> 29 ; +29 [label="29: Release the autorelease pool \n n$19=_fun___objc_release_autorelease_pool(&s1:class A *,&s3:class A *,&s2:class A *) [line 47]\n REMOVE_TEMPS(n$19); [line 47]\n " shape="box"] + + + 29 -> 28 ; +28 [label="28: Return Stmt \n *&return:int =0 [line 52]\n NULLIFY(&s1,false); [line 52]\n NULLIFY(&s2,false); [line 52]\n NULLIFY(&s3,false); [line 52]\n APPLY_ABSTRACTION; [line 52]\n " shape="box"] + + + 28 -> 27 ; +27 [label="27: Exit test2 \n " color=yellow style=filled] + + +26 [label="26: Start test2\nFormals: \nLocals: s1:class A * s2:class A * s3:class A * \n DECLARE_LOCALS(&return,&s1,&s2,&s3); [line 43]\n " color=yellow style=filled] + + + 26 -> 35 ; +25 [label="25: DeclStmt \n *&s1:class A *=0 [line 31]\n " shape="box"] + + + 25 -> 24 ; +24 [label="24: DeclStmt \n *&s2:class A *=0 [line 32]\n " shape="box"] + + + 24 -> 23 ; +23 [label="23: DeclStmt \n *&s3:class A *=0 [line 33]\n " shape="box"] + + + 23 -> 22 ; +22 [label="22: BinaryOperatorStmt: Assign \n n$18=_fun_createA() [line 35]\n *&s1:class A *=n$18 [line 35]\n REMOVE_TEMPS(n$18); [line 35]\n " shape="box"] + + + 22 -> 21 ; +21 [label="21: Message Call: retain \n n$17=*&s1:class A * [line 36]\n n$16=_fun___objc_retain(n$17:class A *) [line 36]\n REMOVE_TEMPS(n$16,n$17); [line 36]\n " shape="box"] + + + 21 -> 20 ; +20 [label="20: BinaryOperatorStmt: Assign \n n$15=_fun_createA() [line 37]\n *&s2:class A *=n$15 [line 37]\n REMOVE_TEMPS(n$15); [line 37]\n " shape="box"] + + + 20 -> 19 ; +19 [label="19: BinaryOperatorStmt: Assign \n n$14=_fun_createA() [line 38]\n *&s3:class A *=n$14 [line 38]\n REMOVE_TEMPS(n$14); [line 38]\n " shape="box"] + + + 19 -> 18 ; +18 [label="18: Release the autorelease pool \n n$13=_fun___objc_release_autorelease_pool(&s1:class A *,&s2:class A *,&s3:class A *) [line 34]\n REMOVE_TEMPS(n$13); [line 34]\n " shape="box"] + + + 18 -> 17 ; +17 [label="17: Return Stmt \n *&return:int =0 [line 40]\n NULLIFY(&s1,false); [line 40]\n NULLIFY(&s2,false); [line 40]\n NULLIFY(&s3,false); [line 40]\n APPLY_ABSTRACTION; [line 40]\n " shape="box"] + + + 17 -> 16 ; +16 [label="16: Exit test1 \n " color=yellow style=filled] + + +15 [label="15: Start test1\nFormals: \nLocals: s1:class A * s2:class A * s3:class A * \n DECLARE_LOCALS(&return,&s1,&s2,&s3); [line 30]\n " color=yellow style=filled] + + + 15 -> 25 ; +14 [label="14: DeclStmt \n n$12=_fun___objc_alloc_no_fail(sizeof(class A ):class A *) [line 26]\n n$10=_fun_A_init(n$12:class A *) virtual [line 26]\n *&s1:class A *=n$10 [line 26]\n REMOVE_TEMPS(n$10,n$12); [line 26]\n " shape="box"] + + + 14 -> 13 ; +13 [label="13: Return Stmt \n n$9=*&s1:class A * [line 27]\n n$8=_fun___set_autorelease_attribute(n$9:class A *) [line 27]\n *&return:class A *=n$8 [line 27]\n REMOVE_TEMPS(n$8,n$9); [line 27]\n NULLIFY(&s1,false); [line 27]\n APPLY_ABSTRACTION; [line 27]\n " shape="box"] + + + 13 -> 12 ; +12 [label="12: Exit createA \n " color=yellow style=filled] + + +11 [label="11: Start createA\nFormals: \nLocals: s1:class A * \n DECLARE_LOCALS(&return,&s1); [line 25]\n NULLIFY(&s1,false); [line 25]\n " color=yellow style=filled] + + + 11 -> 14 ; +10 [label="10: BinaryOperatorStmt: Assign \n n$6=*&self:class A * [line -1]\n n$7=*&son:class A * [line -1]\n *n$6._son:class A *=n$7 [line -1]\n REMOVE_TEMPS(n$6,n$7); [line -1]\n NULLIFY(&self,false); [line -1]\n NULLIFY(&son,false); [line -1]\n APPLY_ABSTRACTION; [line -1]\n " shape="box"] + + + 10 -> 9 ; +9 [label="9: Exit A_setSon: \n " color=yellow style=filled] + + +8 [label="8: Start A_setSon:\nFormals: self:class A * son:class A *\nLocals: \n DECLARE_LOCALS(&return); [line -1]\n " color=yellow style=filled] + + + 8 -> 10 ; +7 [label="7: Return Stmt \n n$4=*&self:class A * [line -1]\n n$5=*n$4._son:class A * [line -1]\n *&return:class A *=n$5 [line -1]\n REMOVE_TEMPS(n$4,n$5); [line -1]\n NULLIFY(&self,false); [line -1]\n APPLY_ABSTRACTION; [line -1]\n " shape="box"] + + + 7 -> 6 ; +6 [label="6: Exit A_son \n " color=yellow style=filled] + + +5 [label="5: Start A_son\nFormals: self:class A *\nLocals: \n DECLARE_LOCALS(&return); [line -1]\n " color=yellow style=filled] + + + 5 -> 7 ; +4 [label="4: DeclStmt \n n$3=_fun___objc_alloc_no_fail(sizeof(class NSString ):class NSString *) [line 19]\n *&s:class NSString *=n$3 [line 19]\n REMOVE_TEMPS(n$3); [line 19]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n n$1=*&s:class NSString * [line 20]\n n$0=_fun___set_autorelease_attribute(n$1:class NSString *) [line 20]\n *&return:class NSString *=n$0 [line 20]\n REMOVE_TEMPS(n$0,n$1); [line 20]\n NULLIFY(&s,false); [line 20]\n APPLY_ABSTRACTION; [line 20]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit A_main \n " color=yellow style=filled] + + +1 [label="1: Start A_main\nFormals: self:class A *\nLocals: s:class NSString * \n DECLARE_LOCALS(&return,&s); [line 18]\n NULLIFY(&s,false); [line 18]\n NULLIFY(&self,false); [line 18]\n " color=yellow style=filled] + + + 1 -> 4 ; +} diff --git a/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/AutoreleaseExample.m b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/AutoreleaseExample.m new file mode 100644 index 000000000..e9b7bbc84 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/AutoreleaseExample.m @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import +#import +#import + +@interface A : NSObject { + int x; +} +@property A *son; +@end + +@implementation A + +-(NSString*) main{ + NSString *s = [NSString alloc]; + return [s autorelease]; +} + +@end + +A* createA() { + A *s1 = [[A alloc] init]; + return [s1 autorelease]; +} + +int test1() { + A *s1 = nil; + A *s2 = nil; + A *s3 = nil; + @autoreleasepool { + s1 = createA(); + [s1 retain]; + s2 = createA(); + s3 = createA(); + } + return 0; +} + +int test2() { + A *s1 = nil; + A *s2 = nil; + A *s3 = nil; + @autoreleasepool { + s1 = createA(); + s2 = createA(); + s3 = createA(); + } + return 0; +} + +void test3() { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSString *string = [[NSString alloc] autorelease]; + //use the string + [pool release]; + NSString* c = string; +} diff --git a/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/MemoryLeakExample.dot b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/MemoryLeakExample.dot new file mode 100644 index 000000000..cbd7342a2 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/MemoryLeakExample.dot @@ -0,0 +1,226 @@ +digraph iCFG { +60 [label="60: BinaryOperatorStmt: Assign \n n$41=*&self:class MemoryLeakExample * [line -1]\n n$42=*&attachmentContainerView:class UIView * [line -1]\n *n$41._attachmentContainerView:class UIView *=n$42 [line -1]\n REMOVE_TEMPS(n$41,n$42); [line -1]\n NULLIFY(&attachmentContainerView,false); [line -1]\n NULLIFY(&self,false); [line -1]\n APPLY_ABSTRACTION; [line -1]\n " shape="box"] + + + 60 -> 59 ; +59 [label="59: Exit MemoryLeakExample_setAttachmentContainerView: \n " color=yellow style=filled] + + +58 [label="58: Start MemoryLeakExample_setAttachmentContainerView:\nFormals: self:class MemoryLeakExample * attachmentContainerView:class UIView *\nLocals: \n DECLARE_LOCALS(&return); [line -1]\n " color=yellow style=filled] + + + 58 -> 60 ; +57 [label="57: Return Stmt \n n$39=*&self:class MemoryLeakExample * [line -1]\n n$40=*n$39._attachmentContainerView:class UIView * [line -1]\n *&return:class UIView *=n$40 [line -1]\n REMOVE_TEMPS(n$39,n$40); [line -1]\n NULLIFY(&self,false); [line -1]\n APPLY_ABSTRACTION; [line -1]\n " shape="box"] + + + 57 -> 56 ; +56 [label="56: Exit MemoryLeakExample_attachmentContainerView \n " color=yellow style=filled] + + +55 [label="55: Start MemoryLeakExample_attachmentContainerView\nFormals: self:class MemoryLeakExample *\nLocals: \n DECLARE_LOCALS(&return); [line -1]\n " color=yellow style=filled] + + + 55 -> 57 ; +54 [label="54: BinaryOperatorStmt: Assign \n n$37=*&self:class MemoryLeakExample * [line -1]\n n$38=*&backgroundCoveringView:class UIView * [line -1]\n *n$37._backgroundCoveringView:class UIView *=n$38 [line -1]\n REMOVE_TEMPS(n$37,n$38); [line -1]\n NULLIFY(&backgroundCoveringView,false); [line -1]\n NULLIFY(&self,false); [line -1]\n APPLY_ABSTRACTION; [line -1]\n " shape="box"] + + + 54 -> 53 ; +53 [label="53: Exit MemoryLeakExample_setBackgroundCoveringView: \n " color=yellow style=filled] + + +52 [label="52: Start MemoryLeakExample_setBackgroundCoveringView:\nFormals: self:class MemoryLeakExample * backgroundCoveringView:class UIView *\nLocals: \n DECLARE_LOCALS(&return); [line -1]\n " color=yellow style=filled] + + + 52 -> 54 ; +51 [label="51: Return Stmt \n n$35=*&self:class MemoryLeakExample * [line -1]\n n$36=*n$35._backgroundCoveringView:class UIView * [line -1]\n *&return:class UIView *=n$36 [line -1]\n REMOVE_TEMPS(n$35,n$36); [line -1]\n NULLIFY(&self,false); [line -1]\n APPLY_ABSTRACTION; [line -1]\n " shape="box"] + + + 51 -> 50 ; +50 [label="50: Exit MemoryLeakExample_backgroundCoveringView \n " color=yellow style=filled] + + +49 [label="49: Start MemoryLeakExample_backgroundCoveringView\nFormals: self:class MemoryLeakExample *\nLocals: \n DECLARE_LOCALS(&return); [line -1]\n " color=yellow style=filled] + + + 49 -> 51 ; +48 [label="48: DeclStmt \n n$34=_fun_FBColorCreateWithGray(0.000000:double ,0.300000:double ) [line 91]\n *&borderColor:struct CGColor *=n$34 [line 91]\n REMOVE_TEMPS(n$34); [line 91]\n " shape="box"] + + + 48 -> 47 ; +47 [label="47: Call _fun_CGColorRelease \n n$33=*&borderColor:struct CGColor * [line 92]\n _fun_CGColorRelease(n$33:struct CGColor *) [line 92]\n REMOVE_TEMPS(n$33); [line 92]\n NULLIFY(&borderColor,false); [line 92]\n APPLY_ABSTRACTION; [line 92]\n " shape="box"] + + + 47 -> 46 ; +46 [label="46: Exit MemoryLeakExample_testFBColorCreateWithGray \n " color=yellow style=filled] + + +45 [label="45: Start MemoryLeakExample_testFBColorCreateWithGray\nFormals: self:class MemoryLeakExample *\nLocals: borderColor:struct CGColor * \n DECLARE_LOCALS(&return,&borderColor); [line 89]\n NULLIFY(&borderColor,false); [line 89]\n NULLIFY(&self,false); [line 89]\n " color=yellow style=filled] + + + 45 -> 48 ; +44 [label="44: DeclStmt \n n$32=_fun_CGBitmapContextCreateImage(0:struct CGContext *) [line 83]\n *&newImage:struct CGImage *=n$32 [line 83]\n REMOVE_TEMPS(n$32); [line 83]\n " shape="box"] + + + 44 -> 43 ; +43 [label="43: Call _fun_CGImageRelease \n n$31=*&newImage:struct CGImage * [line 84]\n _fun_CGImageRelease(n$31:struct CGImage *) [line 84]\n REMOVE_TEMPS(n$31); [line 84]\n NULLIFY(&newImage,false); [line 84]\n APPLY_ABSTRACTION; [line 84]\n " shape="box"] + + + 43 -> 42 ; +42 [label="42: Exit MemoryLeakExample_testImageRefRelease \n " color=yellow style=filled] + + +41 [label="41: Start MemoryLeakExample_testImageRefRelease\nFormals: \nLocals: newImage:struct CGImage * \n DECLARE_LOCALS(&return,&newImage); [line 81]\n NULLIFY(&newImage,false); [line 81]\n " color=yellow style=filled] + + + 41 -> 44 ; +40 [label="40: DeclStmt \n n$30=_fun_SecTrustCopyPublicKey(0:struct __SecTrust *) [line 77]\n *&allowedPublicKey:struct __SecKey *=n$30 [line 77]\n REMOVE_TEMPS(n$30); [line 77]\n " shape="box"] + + + 40 -> 39 ; +39 [label="39: Call _fun___objc_release_cf \n n$29=*&allowedPublicKey:struct __SecKey * [line 78]\n _fun___objc_release_cf(1:_Bool ,n$29:void *) [line 78]\n REMOVE_TEMPS(n$29); [line 78]\n NULLIFY(&allowedPublicKey,false); [line 78]\n APPLY_ABSTRACTION; [line 78]\n " shape="box"] + + + 39 -> 38 ; +38 [label="38: Exit MemoryLeakExample_test2NoLeak \n " color=yellow style=filled] + + +37 [label="37: Start MemoryLeakExample_test2NoLeak\nFormals: \nLocals: allowedPublicKey:struct __SecKey * \n DECLARE_LOCALS(&return,&allowedPublicKey); [line 75]\n NULLIFY(&allowedPublicKey,false); [line 75]\n " color=yellow style=filled] + + + 37 -> 40 ; +36 [label="36: DeclStmt \n n$28=_fun_SecTrustCopyPublicKey(0:struct __SecTrust *) [line 72]\n *&allowedPublicKey:struct __SecKey *=n$28 [line 72]\n REMOVE_TEMPS(n$28); [line 72]\n NULLIFY(&allowedPublicKey,false); [line 72]\n APPLY_ABSTRACTION; [line 72]\n " shape="box"] + + + 36 -> 35 ; +35 [label="35: Exit MemoryLeakExample_test2 \n " color=yellow style=filled] + + +34 [label="34: Start MemoryLeakExample_test2\nFormals: \nLocals: allowedPublicKey:struct __SecKey * \n DECLARE_LOCALS(&return,&allowedPublicKey); [line 70]\n NULLIFY(&allowedPublicKey,false); [line 70]\n " color=yellow style=filled] + + + 34 -> 36 ; +33 [label="33: DeclStmt \n n$26=*&rect:struct CGRect [line 63]\n n$27=_fun_CGRectGetHeight(n$26:struct CGRect ) [line 63]\n *&lineThickness:double =(0.200000 * n$27) [line 63]\n REMOVE_TEMPS(n$26,n$27); [line 63]\n NULLIFY(&rect,false); [line 63]\n NULLIFY(&lineThickness,false); [line 63]\n " shape="box"] + + + 33 -> 32 ; +32 [label="32: DeclStmt \n n$25=_fun_CGPathCreateMutable() [line 66]\n *&path1:struct CGPath *=n$25 [line 66]\n REMOVE_TEMPS(n$25); [line 66]\n " shape="box"] + + + 32 -> 31 ; +31 [label="31: Call _fun___objc_release_cf \n n$24=*&path1:struct CGPath * [line 67]\n _fun___objc_release_cf(1:_Bool ,n$24:void *) [line 67]\n REMOVE_TEMPS(n$24); [line 67]\n NULLIFY(&path1,false); [line 67]\n APPLY_ABSTRACTION; [line 67]\n " shape="box"] + + + 31 -> 30 ; +30 [label="30: Exit MemoryLeakExample_createCloseCrossGlyphNoLeak: \n " color=yellow style=filled] + + +29 [label="29: Start MemoryLeakExample_createCloseCrossGlyphNoLeak:\nFormals: rect:struct CGRect \nLocals: lineThickness:double path1:struct CGPath * \n DECLARE_LOCALS(&return,&lineThickness,&path1); [line 61]\n NULLIFY(&lineThickness,false); [line 61]\n NULLIFY(&path1,false); [line 61]\n " color=yellow style=filled] + + + 29 -> 33 ; +28 [label="28: DeclStmt \n n$22=*&rect:struct CGRect [line 55]\n n$23=_fun_CGRectGetHeight(n$22:struct CGRect ) [line 55]\n *&lineThickness:double =(0.200000 * n$23) [line 55]\n REMOVE_TEMPS(n$22,n$23); [line 55]\n NULLIFY(&rect,false); [line 55]\n NULLIFY(&lineThickness,false); [line 55]\n " shape="box"] + + + 28 -> 27 ; +27 [label="27: DeclStmt \n n$21=_fun_CGPathCreateMutable() [line 58]\n *&path1:struct CGPath *=n$21 [line 58]\n REMOVE_TEMPS(n$21); [line 58]\n NULLIFY(&path1,false); [line 58]\n APPLY_ABSTRACTION; [line 58]\n " shape="box"] + + + 27 -> 26 ; +26 [label="26: Exit MemoryLeakExample_createCloseCrossGlyph: \n " color=yellow style=filled] + + +25 [label="25: Start MemoryLeakExample_createCloseCrossGlyph:\nFormals: rect:struct CGRect \nLocals: lineThickness:double path1:struct CGPath * \n DECLARE_LOCALS(&return,&lineThickness,&path1); [line 53]\n NULLIFY(&lineThickness,false); [line 53]\n NULLIFY(&path1,false); [line 53]\n " color=yellow style=filled] + + + 25 -> 28 ; +24 [label="24: DeclStmt \n n$20=_fun_CTFramesetterCreateWithAttributedString(0:struct __CFAttributedString *) [line 49]\n *&framesetter:struct __CTFramesetter *=n$20 [line 49]\n REMOVE_TEMPS(n$20); [line 49]\n " shape="box"] + + + 24 -> 23 ; +23 [label="23: Call _fun___objc_release_cf \n n$19=*&framesetter:struct __CTFramesetter * [line 50]\n _fun___objc_release_cf(1:_Bool ,n$19:void *) [line 50]\n REMOVE_TEMPS(n$19); [line 50]\n NULLIFY(&framesetter,false); [line 50]\n APPLY_ABSTRACTION; [line 50]\n " shape="box"] + + + 23 -> 22 ; +22 [label="22: Exit MemoryLeakExample_test1NoLeak \n " color=yellow style=filled] + + +21 [label="21: Start MemoryLeakExample_test1NoLeak\nFormals: \nLocals: framesetter:struct __CTFramesetter * \n DECLARE_LOCALS(&return,&framesetter); [line 47]\n NULLIFY(&framesetter,false); [line 47]\n " color=yellow style=filled] + + + 21 -> 24 ; +20 [label="20: DeclStmt \n n$18=_fun_CTFramesetterCreateWithAttributedString(0:struct __CFAttributedString *) [line 44]\n *&framesetter:struct __CTFramesetter *=n$18 [line 44]\n REMOVE_TEMPS(n$18); [line 44]\n NULLIFY(&framesetter,false); [line 44]\n APPLY_ABSTRACTION; [line 44]\n " shape="box"] + + + 20 -> 19 ; +19 [label="19: Exit MemoryLeakExample_test1 \n " color=yellow style=filled] + + +18 [label="18: Start MemoryLeakExample_test1\nFormals: \nLocals: framesetter:struct __CTFramesetter * \n DECLARE_LOCALS(&return,&framesetter); [line 42]\n NULLIFY(&framesetter,false); [line 42]\n " color=yellow style=filled] + + + 18 -> 20 ; +17 [label="17: DeclStmt \n n$17=_fun_CFAttributedStringCreateMutable(0:struct __CFAllocator *,0:long ) [line 38]\n *&maString:struct __CFAttributedString *=n$17 [line 38]\n REMOVE_TEMPS(n$17); [line 38]\n " shape="box"] + + + 17 -> 16 ; +16 [label="16: Call _fun___objc_release_cf \n n$16=*&maString:struct __CFAttributedString * [line 39]\n _fun___objc_release_cf(1:_Bool ,n$16:void *) [line 39]\n REMOVE_TEMPS(n$16); [line 39]\n NULLIFY(&maString,false); [line 39]\n APPLY_ABSTRACTION; [line 39]\n " shape="box"] + + + 16 -> 15 ; +15 [label="15: Exit MemoryLeakExample_measureFrameSizeForTextNoLeak \n " color=yellow style=filled] + + +14 [label="14: Start MemoryLeakExample_measureFrameSizeForTextNoLeak\nFormals: \nLocals: maString:struct __CFAttributedString * \n DECLARE_LOCALS(&return,&maString); [line 36]\n NULLIFY(&maString,false); [line 36]\n " color=yellow style=filled] + + + 14 -> 17 ; +13 [label="13: DeclStmt \n n$15=_fun_CFAttributedStringCreateMutable(0:struct __CFAllocator *,0:long ) [line 33]\n *&maString:struct __CFAttributedString *=n$15 [line 33]\n REMOVE_TEMPS(n$15); [line 33]\n NULLIFY(&maString,false); [line 33]\n APPLY_ABSTRACTION; [line 33]\n " shape="box"] + + + 13 -> 12 ; +12 [label="12: Exit MemoryLeakExample_measureFrameSizeForText \n " color=yellow style=filled] + + +11 [label="11: Start MemoryLeakExample_measureFrameSizeForText\nFormals: \nLocals: maString:struct __CFAttributedString * \n DECLARE_LOCALS(&return,&maString); [line 31]\n NULLIFY(&maString,false); [line 31]\n " color=yellow style=filled] + + + 11 -> 13 ; +10 [label="10: DeclStmt \n n$13=*&self:class MemoryLeakExample * [line 27]\n n$12=_fun_MemoryLeakExample_backgroundCoveringView(n$13:class MemoryLeakExample *) virtual [line 27]\n n$11=_fun_UIView_bounds(n$12:class UIView *) virtual [line 27]\n n$14=_fun_CGPathCreateWithRect(n$11:struct CGRect ,0:CGAffineTransform *) [line 27]\n *&shadowPath:struct CGPath *=n$14 [line 27]\n REMOVE_TEMPS(n$11,n$12,n$13,n$14); [line 27]\n " shape="box"] + + + 10 -> 9 ; +9 [label="9: Message Call: setShadowPath: \n n$9=*&self:class MemoryLeakExample * [line 28]\n n$8=_fun_MemoryLeakExample_backgroundCoveringView(n$9:class MemoryLeakExample *) virtual [line 28]\n n$7=_fun_UIView_layer(n$8:class UIView *) virtual [line 28]\n n$10=*&shadowPath:struct CGPath * [line 28]\n _fun_CALayer_setShadowPath:(n$7:class CALayer *,n$10:struct CGPath *) virtual [line 28]\n REMOVE_TEMPS(n$7,n$8,n$9,n$10); [line 28]\n NULLIFY(&self,false); [line 28]\n NULLIFY(&shadowPath,false); [line 28]\n APPLY_ABSTRACTION; [line 28]\n " shape="box"] + + + 9 -> 8 ; +8 [label="8: Exit MemoryLeakExample_test \n " color=yellow style=filled] + + +7 [label="7: Start MemoryLeakExample_test\nFormals: self:class MemoryLeakExample *\nLocals: shadowPath:struct CGPath * \n DECLARE_LOCALS(&return,&shadowPath); [line 25]\n NULLIFY(&shadowPath,false); [line 25]\n " color=yellow style=filled] + + + 7 -> 10 ; +6 [label="6: DeclStmt \n n$6=_fun___objc_alloc_no_fail(sizeof(class UIView ):class UIView *) [line 17]\n *&attachmentContainerView:class UIView *=n$6 [line 17]\n REMOVE_TEMPS(n$6); [line 17]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: DeclStmt \n n$3=*&attachmentContainerView:class UIView * [line 18]\n n$2=_fun_UIView_bounds(n$3:class UIView *) virtual [line 18]\n n$4=_fun_CGPathCreateWithRect(n$2:struct CGRect ,0:CGAffineTransform *) [line 18]\n *&shadowPath:struct CGPath *=n$4 [line 18]\n REMOVE_TEMPS(n$2,n$3,n$4); [line 18]\n " shape="box"] + + + 5 -> 4 ; +4 [label="4: Call _fun_CGPathRelease \n n$1=*&shadowPath:struct CGPath * [line 20]\n _fun_CGPathRelease(n$1:struct CGPath *) [line 20]\n REMOVE_TEMPS(n$1); [line 20]\n NULLIFY(&shadowPath,false); [line 20]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: Message Call: release \n n$0=*&attachmentContainerView:class UIView * [line 21]\n _fun___objc_release(n$0:class UIView *) [line 21]\n REMOVE_TEMPS(n$0); [line 21]\n NULLIFY(&attachmentContainerView,false); [line 21]\n APPLY_ABSTRACTION; [line 21]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit MemoryLeakExample_layoutSubviews \n " color=yellow style=filled] + + +1 [label="1: Start MemoryLeakExample_layoutSubviews\nFormals: self:class MemoryLeakExample *\nLocals: attachmentContainerView:class UIView * shadowPath:struct CGPath * \n DECLARE_LOCALS(&return,&attachmentContainerView,&shadowPath); [line 15]\n NULLIFY(&attachmentContainerView,false); [line 15]\n NULLIFY(&self,false); [line 15]\n NULLIFY(&shadowPath,false); [line 15]\n " color=yellow style=filled] + + + 1 -> 6 ; +} diff --git a/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/MemoryLeakExample.h b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/MemoryLeakExample.h new file mode 100644 index 000000000..079ee86b8 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/MemoryLeakExample.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import +#import + +@interface MemoryLeakExample : NSObject + +@property UIView *backgroundCoveringView; +@property UIView *attachmentContainerView; + +@end diff --git a/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/MemoryLeakExample.m b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/MemoryLeakExample.m new file mode 100644 index 000000000..68aaeba3e --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/MemoryLeakExample.m @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import "MemoryLeakExample.h" +#import +#import +#import + +@implementation MemoryLeakExample + +//For now it doesn't contain memory leak by default. Later, if we remove +//CFRelease, there should be a leak. +- (void)layoutSubviews +{ + UIView *attachmentContainerView = [UIView alloc]; + CGPathRef shadowPath = CGPathCreateWithRect(attachmentContainerView.bounds, NULL); + //self.attachmentContainerView.layer.shadowPath = shadowPath; + CGPathRelease(shadowPath); + [attachmentContainerView release]; + +} + +- (void)test +{ + CGPathRef shadowPath = CGPathCreateWithRect(self.backgroundCoveringView.bounds, NULL); + self.backgroundCoveringView.layer.shadowPath = shadowPath; +} + ++ (void)measureFrameSizeForText +{ + CFMutableAttributedStringRef maString = CFAttributedStringCreateMutable(nil, 0); +} + ++ (void)measureFrameSizeForTextNoLeak +{ + CFMutableAttributedStringRef maString = CFAttributedStringCreateMutable(nil, 0); + CFRelease(maString); +} + ++ (void)test1 + { + CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(nil); + } + + + (void)test1NoLeak + { + CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(nil); + CFRelease(framesetter); + } + ++ (void) createCloseCrossGlyph:(CGRect) rect +{ + CGFloat lineThickness = 0.20f * CGRectGetHeight(rect); + + // One rectangle + CGMutablePathRef path1 = CGPathCreateMutable(); +} + ++ (void) createCloseCrossGlyphNoLeak:(CGRect) rect +{ + CGFloat lineThickness = 0.20f * CGRectGetHeight(rect); + + // One rectangle + CGMutablePathRef path1 = CGPathCreateMutable(); + CFRelease(path1); +} + ++ (void) test2 +{ + SecKeyRef allowedPublicKey = SecTrustCopyPublicKey(nil); +} + ++ (void) test2NoLeak +{ + SecKeyRef allowedPublicKey = SecTrustCopyPublicKey(nil); + CFRelease(allowedPublicKey); +} + ++ (void) testImageRefRelease +{ + CGImageRef newImage = CGBitmapContextCreateImage(nil); + CGImageRelease(newImage); +} + +CGColorRef FBColorCreateWithGray(CGFloat gray, CGFloat a); + +- (id)testFBColorCreateWithGray +{ + CGColorRef borderColor = FBColorCreateWithGray(0.0, 0.3); + CGColorRelease(borderColor); +} + +@end diff --git a/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/NSStringInitWithBytesNoCopyExample.m b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/NSStringInitWithBytesNoCopyExample.m new file mode 100644 index 000000000..b42612935 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/NSStringInitWithBytesNoCopyExample.m @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import +#import + +@interface A : NSObject + +@end + +@implementation A + +NSString *FBCreateURLQueryStringBodyEscaping(NSDictionary *parameters, NSString* s) +{ + if (s) { + char *resultBuffer = (char *)malloc(5 * sizeof(char)); + + NSString *resultString = + [s initWithBytesNoCopy:resultBuffer + length:5 + encoding:NSUTF8StringEncoding + freeWhenDone:YES]; + } + return s; +} + ++ (NSData *)randomBytes:(NSUInteger)numOfBytes +{ + uint8_t *buffer = malloc(numOfBytes); + NSData* data = [NSData dataWithBytesNoCopy:buffer length:numOfBytes]; + if (data) { + return data; + } + else { + free(buffer); + return nil; + } +} + +- (NSData *)macForIV:(NSData *)IV +{ + uint8_t *result = malloc(10); + return [NSData dataWithBytesNoCopy:result length:10]; +} + +- (NSString *)hexStringValue { + size_t hexLen = 2 * 10 * sizeof(char); + char *outString = (char *)malloc(hexLen + 1); + NSString *result = (__bridge_transfer NSString *) + CFStringCreateWithBytesNoCopy(NULL /* default allocator */, + (const UInt8 *)outString /* sizeof(char) should always be sizeof(UInt8) on iOS */, + hexLen, + kCFStringEncodingASCII /* hex is ASCII */, + false /* no bom */, + NULL /* default deallocator -- system takes ownership of bytes */); + if (result == nil) { + // On error creating string, we're responsible for freeing outString. + free(outString); + } + return result; +} + +@end diff --git a/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/RetainReleaseExample.dot b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/RetainReleaseExample.dot new file mode 100644 index 000000000..cb3a58e18 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/RetainReleaseExample.dot @@ -0,0 +1,21 @@ +digraph iCFG { +5 [label="5: DeclStmt \n n$5=_fun___objc_alloc_no_fail(sizeof(class A ):class A *) [line 17]\n n$3=_fun_A_init(n$5:class A *) virtual [line 17]\n *&a:class A *=n$3 [line 17]\n REMOVE_TEMPS(n$3,n$5); [line 17]\n " shape="box"] + + + 5 -> 4 ; +4 [label="4: Message Call: retain \n n$2=*&a:class A * [line 18]\n n$1=_fun___objc_retain(n$2:class A *) [line 18]\n REMOVE_TEMPS(n$1,n$2); [line 18]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: Message Call: release \n n$0=*&a:class A * [line 19]\n _fun___objc_release(n$0:class A *) [line 19]\n REMOVE_TEMPS(n$0); [line 19]\n NULLIFY(&a,false); [line 19]\n APPLY_ABSTRACTION; [line 19]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit test \n " color=yellow style=filled] + + +1 [label="1: Start test\nFormals: \nLocals: a:class A * \n DECLARE_LOCALS(&return,&a); [line 16]\n NULLIFY(&a,false); [line 16]\n " color=yellow style=filled] + + + 1 -> 5 ; +} diff --git a/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/RetainReleaseExample.m b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/RetainReleaseExample.m new file mode 100644 index 000000000..ddf19fe18 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/RetainReleaseExample.m @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface A : NSObject + +@end + +@implementation A + +@end + +void test() { + A *a = [[A alloc] init]; + [a retain]; + [a release]; +} diff --git a/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/RetainReleaseExample2.dot b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/RetainReleaseExample2.dot new file mode 100644 index 000000000..e4b6134d3 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/RetainReleaseExample2.dot @@ -0,0 +1,124 @@ +digraph iCFG { +32 [label="32: Call _fun___objc_release \n n$23=*&a:class A * [line 66]\n n$24=_fun___objc_release(n$23:class A *) [line 66]\n REMOVE_TEMPS(n$23,n$24); [line 66]\n NULLIFY(&a,false); [line 66]\n APPLY_ABSTRACTION; [line 66]\n " shape="box"] + + + 32 -> 29 ; +31 [label="31: Prune (false branch) \n n$22=*&a:class A * [line 65]\n PRUNE((n$22 == 0), false); [line 65]\n REMOVE_TEMPS(n$22); [line 65]\n APPLY_ABSTRACTION; [line 65]\n " shape="invhouse"] + + + 31 -> 29 ; +30 [label="30: Prune (true branch) \n n$22=*&a:class A * [line 65]\n PRUNE((n$22 != 0), true); [line 65]\n REMOVE_TEMPS(n$22); [line 65]\n " shape="invhouse"] + + + 30 -> 32 ; +29 [label="29: + \n NULLIFY(&a,false); [line 65]\n " ] + + + 29 -> 28 ; +28 [label="28: Exit test7 \n " color=yellow style=filled] + + +27 [label="27: Start test7\nFormals: a:class A *\nLocals: \n DECLARE_LOCALS(&return); [line 64]\n " color=yellow style=filled] + + + 27 -> 30 ; + 27 -> 31 ; +26 [label="26: DeclStmt \n n$21=_fun___objc_alloc_no_fail(sizeof(class A ):class A *) [line 58]\n n$19=_fun_A_init(n$21:class A *) virtual [line 58]\n *&a:class A *=n$19 [line 58]\n REMOVE_TEMPS(n$19,n$21); [line 58]\n " shape="box"] + + + 26 -> 25 ; +25 [label="25: Message Call: retain \n n$18=*&a:class A * [line 59]\n n$17=_fun___objc_retain(n$18:class A *) [line 59]\n REMOVE_TEMPS(n$17,n$18); [line 59]\n " shape="box"] + + + 25 -> 24 ; +24 [label="24: Message Call: release \n n$16=*&a:class A * [line 60]\n _fun___objc_release(n$16:class A *) [line 60]\n REMOVE_TEMPS(n$16); [line 60]\n NULLIFY(&a,false); [line 60]\n APPLY_ABSTRACTION; [line 60]\n " shape="box"] + + + 24 -> 23 ; +23 [label="23: Exit test6 \n " color=yellow style=filled] + + +22 [label="22: Start test6\nFormals: \nLocals: a:class A * \n DECLARE_LOCALS(&return,&a); [line 57]\n NULLIFY(&a,false); [line 57]\n " color=yellow style=filled] + + + 22 -> 26 ; +21 [label="21: DeclStmt \n n$15=_fun___objc_alloc_no_fail(sizeof(class A ):class A *) [line 51]\n n$13=_fun_A_init(n$15:class A *) virtual [line 51]\n *&a:class A *=n$13 [line 51]\n REMOVE_TEMPS(n$13,n$15); [line 51]\n " shape="box"] + + + 21 -> 20 ; +20 [label="20: Message Call: release \n n$12=*&a:class A * [line 52]\n _fun___objc_release(n$12:class A *) [line 52]\n REMOVE_TEMPS(n$12); [line 52]\n NULLIFY(&a,false); [line 52]\n APPLY_ABSTRACTION; [line 52]\n " shape="box"] + + + 20 -> 19 ; +19 [label="19: Exit test5 \n " color=yellow style=filled] + + +18 [label="18: Start test5\nFormals: \nLocals: a:class A * \n DECLARE_LOCALS(&return,&a); [line 50]\n NULLIFY(&a,false); [line 50]\n " color=yellow style=filled] + + + 18 -> 21 ; +17 [label="17: DeclStmt \n n$11=_fun_test() [line 45]\n *&b:class A *=n$11 [line 45]\n REMOVE_TEMPS(n$11); [line 45]\n " shape="box"] + + + 17 -> 16 ; +16 [label="16: Message Call: release \n n$10=*&b:class A * [line 46]\n _fun___objc_release(n$10:class A *) [line 46]\n REMOVE_TEMPS(n$10); [line 46]\n NULLIFY(&b,false); [line 46]\n APPLY_ABSTRACTION; [line 46]\n " shape="box"] + + + 16 -> 15 ; +15 [label="15: Exit test4 \n " color=yellow style=filled] + + +14 [label="14: Start test4\nFormals: \nLocals: b:class A * \n DECLARE_LOCALS(&return,&b); [line 43]\n NULLIFY(&b,false); [line 43]\n " color=yellow style=filled] + + + 14 -> 17 ; +13 [label="13: DeclStmt \n n$9=_fun_test() [line 39]\n *&b:class A *=n$9 [line 39]\n REMOVE_TEMPS(n$9); [line 39]\n NULLIFY(&b,false); [line 39]\n APPLY_ABSTRACTION; [line 39]\n " shape="box"] + + + 13 -> 12 ; +12 [label="12: Exit test3 \n " color=yellow style=filled] + + +11 [label="11: Start test3\nFormals: \nLocals: b:class A * \n DECLARE_LOCALS(&return,&b); [line 37]\n NULLIFY(&b,false); [line 37]\n " color=yellow style=filled] + + + 11 -> 13 ; +10 [label="10: DeclStmt \n n$8=_fun_test() [line 32]\n *&b:class A *=n$8 [line 32]\n REMOVE_TEMPS(n$8); [line 32]\n " shape="box"] + + + 10 -> 9 ; +9 [label="9: BinaryOperatorStmt: Assign \n n$7=*&b:class A * [line 33]\n *&#GB$g:class A *=n$7 [line 33]\n REMOVE_TEMPS(n$7); [line 33]\n NULLIFY(&b,false); [line 33]\n APPLY_ABSTRACTION; [line 33]\n " shape="box"] + + + 9 -> 8 ; +8 [label="8: Exit test2 \n " color=yellow style=filled] + + +7 [label="7: Start test2\nFormals: \nLocals: b:class A * \n DECLARE_LOCALS(&return,&b); [line 30]\n NULLIFY(&b,false); [line 30]\n " color=yellow style=filled] + + + 7 -> 10 ; +6 [label="6: DeclStmt \n n$6=_fun___objc_alloc_no_fail(sizeof(class A ):class A *) [line 22]\n n$4=_fun_A_init(n$6:class A *) virtual [line 22]\n *&a:class A *=n$4 [line 22]\n REMOVE_TEMPS(n$4,n$6); [line 22]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: Message Call: retain \n n$3=*&a:class A * [line 23]\n n$2=_fun___objc_retain(n$3:class A *) [line 23]\n REMOVE_TEMPS(n$2,n$3); [line 23]\n " shape="box"] + + + 5 -> 4 ; +4 [label="4: Message Call: release \n n$1=*&a:class A * [line 24]\n _fun___objc_release(n$1:class A *) [line 24]\n REMOVE_TEMPS(n$1); [line 24]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n n$0=*&a:class A * [line 26]\n *&return:class A *=n$0 [line 26]\n REMOVE_TEMPS(n$0); [line 26]\n NULLIFY(&a,false); [line 26]\n APPLY_ABSTRACTION; [line 26]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit test \n " color=yellow style=filled] + + +1 [label="1: Start test\nFormals: \nLocals: a:class A * \n DECLARE_LOCALS(&return,&a); [line 21]\n NULLIFY(&a,false); [line 21]\n " color=yellow style=filled] + + + 1 -> 6 ; +} diff --git a/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/RetainReleaseExample2.m b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/RetainReleaseExample2.m new file mode 100644 index 000000000..d9f98203e --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/RetainReleaseExample2.m @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface A : NSObject + +@end + +@implementation A + +@end + + +A* g; + + +// no leak +A* test() { + A *a = [[A alloc] init]; + [a retain]; + [a release]; + + return a; +} + +// no leak +void test2() { + + A* b=test(); + g=b; +} + +// leak +void test3() { + + A* b=test(); +} + +// no leak +void test4() { + + A* b=test(); + [b release]; +} + +// No leak +void test5() { + A *a = [[A alloc] init]; + [a release]; + +} + +// leak +void test6() { + A *a = [[A alloc] init]; + [a retain]; + [a release]; +} + +// Creates specs +void test7 (A *a) { + if (a) + __objc_release(a); +} + diff --git a/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/RetainReleaseExampleBucketing.m b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/RetainReleaseExampleBucketing.m new file mode 100644 index 000000000..358453ecc --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/RetainReleaseExampleBucketing.m @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface A : NSObject + +@end + +@implementation A + +@end + +// no leak in bucketing cf mode +void test() { + A *a = [[A alloc] init]; +} diff --git a/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/TollBridgeExample.dot b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/TollBridgeExample.dot new file mode 100644 index 000000000..ff91ca31f --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/TollBridgeExample.dot @@ -0,0 +1,73 @@ +digraph iCFG { +19 [label="19: Return Stmt \n n$10=_fun___builtin___CFStringMakeConstantString(\"Icon\":char *) [line 39]\n n$11=_fun_CTFontCreateWithName(n$10:struct __CFString *,17.000000:double ,0:CGAffineTransform *) [line 39]\n n$12=_fun___objc_cast(n$11:void *,sizeof(void ):void ) [line 39]\n *&return:struct __CTFont *=n$12 [line 39]\n REMOVE_TEMPS(n$10,n$11,n$12); [line 39]\n APPLY_ABSTRACTION; [line 39]\n " shape="box"] + + + 19 -> 18 ; +18 [label="18: Exit cfautorelease_test \n " color=yellow style=filled] + + +17 [label="17: Start cfautorelease_test\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 38]\n " color=yellow style=filled] + + + 17 -> 19 ; +16 [label="16: DeclStmt \n n$9=_fun_CFHTTPMessageCopyAllHeaderFields(0:struct __CFHTTPMessage *) [line 34]\n *&ref:struct __CFDictionary *=n$9 [line 34]\n REMOVE_TEMPS(n$9); [line 34]\n " shape="box"] + + + 16 -> 15 ; +15 [label="15: Call _fun_CFBridgingRelease \n n$7=*&ref:struct __CFDictionary * [line 35]\n n$8=_fun___objc_cast(n$7:void *,sizeof(struct objc_object ):void ) [line 35]\n REMOVE_TEMPS(n$7,n$8); [line 35]\n NULLIFY(&ref,false); [line 35]\n APPLY_ABSTRACTION; [line 35]\n " shape="box"] + + + 15 -> 14 ; +14 [label="14: Exit TollBridgeExample__readHTTPHeader \n " color=yellow style=filled] + + +13 [label="13: Start TollBridgeExample__readHTTPHeader\nFormals: self:class TollBridgeExample *\nLocals: ref:struct __CFDictionary * \n DECLARE_LOCALS(&return,&ref); [line 32]\n NULLIFY(&ref,false); [line 32]\n NULLIFY(&self,false); [line 32]\n " color=yellow style=filled] + + + 13 -> 16 ; +12 [label="12: DeclStmt \n n$6=_fun___objc_alloc_no_fail(sizeof(class NSLocale ):class NSLocale *) [line 28]\n *&observer:struct objc_object *=n$6 [line 28]\n REMOVE_TEMPS(n$6); [line 28]\n " shape="box"] + + + 12 -> 11 ; +11 [label="11: DeclStmt \n n$4=*&observer:struct objc_object * [line 29]\n *&a:struct __CFLocale *=n$4 [line 29]\n REMOVE_TEMPS(n$4); [line 29]\n NULLIFY(&a,false); [line 29]\n NULLIFY(&observer,false); [line 29]\n APPLY_ABSTRACTION; [line 29]\n " shape="box"] + + + 11 -> 10 ; +10 [label="10: Exit TollBridgeExample_brideRetained \n " color=yellow style=filled] + + +9 [label="9: Start TollBridgeExample_brideRetained\nFormals: self:class TollBridgeExample *\nLocals: observer:struct objc_object * a:struct __CFLocale * \n DECLARE_LOCALS(&return,&observer,&a); [line 27]\n NULLIFY(&a,false); [line 27]\n NULLIFY(&observer,false); [line 27]\n NULLIFY(&self,false); [line 27]\n " color=yellow style=filled] + + + 9 -> 12 ; +8 [label="8: DeclStmt \n n$3=_fun_CFLocaleCreate(0:struct __CFAllocator *,0:struct __CFString *) [line 22]\n *&nameRef:struct __CFLocale *=n$3 [line 22]\n REMOVE_TEMPS(n$3); [line 22]\n " shape="box"] + + + 8 -> 7 ; +7 [label="7: DeclStmt \n n$2=*&nameRef:struct __CFLocale * [line 23]\n *&a:class NSLocale *=(struct __CFLocale *)n$2 [line 23]\n REMOVE_TEMPS(n$2); [line 23]\n NULLIFY(&a,false); [line 23]\n NULLIFY(&nameRef,false); [line 23]\n APPLY_ABSTRACTION; [line 23]\n " shape="box"] + + + 7 -> 6 ; +6 [label="6: Exit TollBridgeExample_bridge \n " color=yellow style=filled] + + +5 [label="5: Start TollBridgeExample_bridge\nFormals: self:class TollBridgeExample *\nLocals: nameRef:struct __CFLocale * a:class NSLocale * \n DECLARE_LOCALS(&return,&nameRef,&a); [line 21]\n NULLIFY(&a,false); [line 21]\n NULLIFY(&nameRef,false); [line 21]\n NULLIFY(&self,false); [line 21]\n " color=yellow style=filled] + + + 5 -> 8 ; +4 [label="4: DeclStmt \n n$1=_fun_CFLocaleCreate(0:struct __CFAllocator *,0:struct __CFString *) [line 17]\n *&nameRef:struct __CFLocale *=n$1 [line 17]\n REMOVE_TEMPS(n$1); [line 17]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: DeclStmt \n n$0=*&nameRef:struct __CFLocale * [line 18]\n *&a:class NSLocale *=(struct __CFLocale *)n$0 [line 18]\n REMOVE_TEMPS(n$0); [line 18]\n NULLIFY(&a,false); [line 18]\n NULLIFY(&nameRef,false); [line 18]\n APPLY_ABSTRACTION; [line 18]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit TollBridgeExample_bridgeTransfer \n " color=yellow style=filled] + + +1 [label="1: Start TollBridgeExample_bridgeTransfer\nFormals: self:class TollBridgeExample *\nLocals: nameRef:struct __CFLocale * a:class NSLocale * \n DECLARE_LOCALS(&return,&nameRef,&a); [line 16]\n NULLIFY(&a,false); [line 16]\n NULLIFY(&nameRef,false); [line 16]\n NULLIFY(&self,false); [line 16]\n " color=yellow style=filled] + + + 1 -> 4 ; +} diff --git a/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/TollBridgeExample.m b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/TollBridgeExample.m new file mode 100644 index 000000000..838f5091c --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/TollBridgeExample.m @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2015- Facebook. + * All rights reserved. + */ + +#import +#import + + +@interface TollBridgeExample : NSObject + +@end + +@implementation TollBridgeExample + +- (void)bridgeTransfer { + CFLocaleRef nameRef = CFLocaleCreate (NULL, NULL); + NSLocale *a = (__bridge_transfer NSLocale *)nameRef; +} + +- (void)bridge { + CFLocaleRef nameRef = CFLocaleCreate (NULL, NULL); + NSLocale *a = (__bridge NSLocale *)nameRef; +} + + +- (void)brideRetained { + id observer = [NSLocale alloc]; + CFLocaleRef a = (__bridge_retained CFLocaleRef)observer; +} + +- (void)_readHTTPHeader; +{ + CFDictionaryRef ref = CFHTTPMessageCopyAllHeaderFields(NULL); + CFBridgingRelease(ref); +} + +CTFontRef cfautorelease_test() { + return CFAutorelease(CTFontCreateWithName(CFSTR("Icon"), 17.0, NULL)); +} + +@end diff --git a/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/arc_methods.dot b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/arc_methods.dot new file mode 100644 index 000000000..99b8350fa --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/arc_methods.dot @@ -0,0 +1,59 @@ +digraph iCFG { +15 [label="15: DeclStmt \n n$12=_fun_A_newA() [line 38]\n *&a1:class A *=n$12 [line 38]\n REMOVE_TEMPS(n$12); [line 38]\n " shape="box"] + + + 15 -> 14 ; +14 [label="14: DeclStmt \n n$11=*&a1:class A * [line 39]\n _fun___objc_retain(n$11:class A *) [line 39]\n *&aa:class A *=n$11 [line 39]\n REMOVE_TEMPS(n$11); [line 39]\n NULLIFY(&a1,false); [line 39]\n NULLIFY(&aa,false); [line 39]\n " shape="box"] + + + 14 -> 13 ; +13 [label="13: DeclStmt \n n$10=_fun_A_someA() [line 40]\n _fun___objc_retain(n$10:class A *) [line 40]\n *&a2:class A *=n$10 [line 40]\n REMOVE_TEMPS(n$10); [line 40]\n " shape="box"] + + + 13 -> 12 ; +12 [label="12: DeclStmt \n n$9=*&a2:class A * [line 41]\n _fun___objc_retain(n$9:class A *) [line 41]\n *&ab:class A *=n$9 [line 41]\n REMOVE_TEMPS(n$9); [line 41]\n NULLIFY(&a2,false); [line 41]\n NULLIFY(&ab,false); [line 41]\n " shape="box"] + + + 12 -> 11 ; +11 [label="11: Return Stmt \n *&return:int =0 [line 42]\n APPLY_ABSTRACTION; [line 42]\n " shape="box"] + + + 11 -> 10 ; +10 [label="10: Exit main \n " color=yellow style=filled] + + +9 [label="9: Start main\nFormals: \nLocals: a1:class A * aa:class A * a2:class A * ab:class A * \n DECLARE_LOCALS(&return,&a1,&aa,&a2,&ab); [line 31]\n NULLIFY(&a1,false); [line 31]\n NULLIFY(&a2,false); [line 31]\n NULLIFY(&aa,false); [line 31]\n NULLIFY(&ab,false); [line 31]\n " color=yellow style=filled] + + + 9 -> 15 ; +8 [label="8: DeclStmt \n n$8=_fun___objc_alloc_no_fail(sizeof(class A ):class A *) [line 24]\n n$6=_fun_A_init(n$8:class A *) virtual [line 24]\n *&a:class A *=n$6 [line 24]\n REMOVE_TEMPS(n$6,n$8); [line 24]\n " shape="box"] + + + 8 -> 7 ; +7 [label="7: Return Stmt \n n$4=*&a:class A * [line 26]\n *&return:class A *=n$4 [line 26]\n n$5=_fun___set_autorelease_attribute(n$4:class A *) [line 26]\n REMOVE_TEMPS(n$4,n$5); [line 26]\n NULLIFY(&a,false); [line 26]\n APPLY_ABSTRACTION; [line 26]\n " shape="box"] + + + 7 -> 6 ; +6 [label="6: Exit A_someA \n " color=yellow style=filled] + + +5 [label="5: Start A_someA\nFormals: \nLocals: a:class A * \n DECLARE_LOCALS(&return,&a); [line 23]\n NULLIFY(&a,false); [line 23]\n " color=yellow style=filled] + + + 5 -> 8 ; +4 [label="4: DeclStmt \n n$3=_fun___objc_alloc_no_fail(sizeof(class A ):class A *) [line 19]\n n$1=_fun_A_init(n$3:class A *) virtual [line 19]\n *&a:class A *=n$1 [line 19]\n REMOVE_TEMPS(n$1,n$3); [line 19]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n n$0=*&a:class A * [line 20]\n *&return:class A *=n$0 [line 20]\n REMOVE_TEMPS(n$0); [line 20]\n NULLIFY(&a,false); [line 20]\n APPLY_ABSTRACTION; [line 20]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit A_newA \n " color=yellow style=filled] + + +1 [label="1: Start A_newA\nFormals: \nLocals: a:class A * \n DECLARE_LOCALS(&return,&a); [line 18]\n NULLIFY(&a,false); [line 18]\n " color=yellow style=filled] + + + 1 -> 4 ; +} diff --git a/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/arc_methods.m b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/arc_methods.m new file mode 100644 index 000000000..6d126c8c7 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/arc_methods.m @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2013 - Facebook. + * All rights reserved. +*/ + +#import + +@interface A : NSObject + ++(A*) newA; + ++(A*) someA; + +@end + +@implementation A + ++ (A*) newA { + A* a =[[A alloc] init]; + return a; +} + ++ (A*) someA { + A *a = [[A alloc] init]; + + return a; +} + +@end + +int main () { + + // A * __weak aWeakRef =0; +// A * __strong a1 =0; +// A * __unsafe_unretained anUnsafeUnretRef =0; +// A * __autoreleasing anAutoRelRef =0; + + A *a1=[A newA]; + A *aa = a1; + A *a2 = [A someA]; + A *ab = a2; + return 0; +} diff --git a/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/retain_cycle.m b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/retain_cycle.m new file mode 100644 index 000000000..cf366ee84 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/retain_cycle.m @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +#import + +@class AA; +@class BB; + +@interface AA : NSObject + +@property (nonatomic, strong) BB* b; +@end + + +@implementation AA +@end + + +@interface BBStrong : NSObject + +@property (nonatomic, strong) AA* a; +@end + +@implementation BBStrong +@end + + +@interface BBUnsafeUnretained : NSObject + +@property (nonatomic, unsafe_unretained) AA* a; +@end + +@implementation BBUnsafeUnretained +@end + +@interface BBWeak : NSObject + +@property (nonatomic, weak) AA* a; +@end + +@implementation BBWeak +@end + +@interface BBAssign : NSObject + +@property (nonatomic) AA* a; +@end + +@implementation BBAssign +@end + +int strongcycle() { + + AA* a_obj =[AA alloc]; + BBStrong* b_obj = [BBStrong alloc]; + + a_obj.b=b_obj; + b_obj.a=a_obj; //Retain cycle + + return 0; +} + +int unsafeunreainedcycle() { + + AA* a_obj =[AA alloc]; + BBUnsafeUnretained* b_obj = [BBUnsafeUnretained alloc]; + + a_obj.b=b_obj; + b_obj.a=a_obj; //Not a retain cycle + + return 0; +} + +int weakcycle() { + + AA* a_obj =[AA alloc]; + BBWeak* b_obj = [BBWeak alloc]; + + a_obj.b=b_obj; + b_obj.a=a_obj; //Not a retain cycle + + return 0; +} + +int assigncycle() { + + AA* a_obj =[AA alloc]; + BBAssign* b_obj = [BBAssign alloc]; + + a_obj.b=b_obj; + b_obj.a=a_obj; //Not a retain cycle + + return 0; +} \ No newline at end of file diff --git a/infer/tests/codetoanalyze/objc/errors/npe/Fraction.m b/infer/tests/codetoanalyze/objc/errors/npe/Fraction.m new file mode 100644 index 000000000..7da0ece49 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/npe/Fraction.m @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface Fraction : NSObject { + int numerator; + int denominator; +} + +- (void)setNumerator:(int)n; +- (void)setDenominator:(int)d; + +- (int)getNumerator; +- (int)getDenominator; +@end + +@implementation Fraction + +- (void)setNumerator:(int)n { + numerator = n; +} + +- (void)setDenominator:(int)d { + denominator = d; +} + +- (int)getNumerator { + return numerator; +} + +- (int)getDenominator { + return denominator; +} +@end + + +Fraction *Fraction_create() { + Fraction *f = NULL; + return f; +} + +int null_deref_objc_class() { + Fraction *fraction = Fraction_create(); + [fraction setNumerator: 5]; + return 0; +} + +void test_virtual_call() { + id *fraction = [Fraction alloc]; + // virtual call: the type of fraction is obtained from the allocation + [fraction setNumerator: 5]; + if ([fraction getNumerator] != 4) { + // unreachable + int *x = NULL; + *x = 0; + } + [fraction release]; +} + diff --git a/infer/tests/codetoanalyze/objc/errors/npe/NPD_core_foundation.dot b/infer/tests/codetoanalyze/objc/errors/npe/NPD_core_foundation.dot new file mode 100644 index 000000000..e8fdee618 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/npe/NPD_core_foundation.dot @@ -0,0 +1,131 @@ +digraph iCFG { +721 [label="721: BinaryOperatorStmt: Assign \n n$648=*&self:class NullDeref * [line 8]\n n$649=*&attachmentContainerView:class UIView * [line 8]\n *n$648.NullDeref__attachmentContainerView:class NullDeref *=n$649 [line 8]\n REMOVE_TEMPS(n$648,n$649); [line 8]\n NULLIFY(&attachmentContainerView,false); [line 8]\n NULLIFY(&self,false); [line 8]\n APPLY_ABSTRACTION; [line 8]\n " shape="box"] + + + 721 -> 720 ; +720 [label="720: Exit NullDeref_setAttachmentContainerView: \n " color=yellow style=filled] + + +719 [label="719: Start NullDeref_setAttachmentContainerView:\nFormals: self:class NullDeref * attachmentContainerView:class UIView *\nLocals: \n DECLARE_LOCALS(&return); [line 8]\n " color=yellow style=filled] + + + 719 -> 721 ; +718 [label="718: Return Stmt \n n$646=*&self:class NullDeref * [line 8]\n n$647=*n$646.NullDeref__attachmentContainerView:class UIView * [line 8]\n *&return:class UIView *=n$647 [line 8]\n REMOVE_TEMPS(n$646,n$647); [line 8]\n NULLIFY(&self,false); [line 8]\n APPLY_ABSTRACTION; [line 8]\n " shape="box"] + + + 718 -> 717 ; +717 [label="717: Exit NullDeref_attachmentContainerView \n " color=yellow style=filled] + + +716 [label="716: Start NullDeref_attachmentContainerView\nFormals: self:class NullDeref *\nLocals: \n DECLARE_LOCALS(&return); [line 8]\n " color=yellow style=filled] + + + 716 -> 718 ; +715 [label="715: BinaryOperatorStmt: Assign \n n$644=*&self:class NullDeref * [line 7]\n n$645=*&backgroundCoveringView:class UIView * [line 7]\n *n$644.NullDeref__backgroundCoveringView:class NullDeref *=n$645 [line 7]\n REMOVE_TEMPS(n$644,n$645); [line 7]\n NULLIFY(&backgroundCoveringView,false); [line 7]\n NULLIFY(&self,false); [line 7]\n APPLY_ABSTRACTION; [line 7]\n " shape="box"] + + + 715 -> 714 ; +714 [label="714: Exit NullDeref_setBackgroundCoveringView: \n " color=yellow style=filled] + + +713 [label="713: Start NullDeref_setBackgroundCoveringView:\nFormals: self:class NullDeref * backgroundCoveringView:class UIView *\nLocals: \n DECLARE_LOCALS(&return); [line 7]\n " color=yellow style=filled] + + + 713 -> 715 ; +712 [label="712: Return Stmt \n n$642=*&self:class NullDeref * [line 7]\n n$643=*n$642.NullDeref__backgroundCoveringView:class UIView * [line 7]\n *&return:class UIView *=n$643 [line 7]\n REMOVE_TEMPS(n$642,n$643); [line 7]\n NULLIFY(&self,false); [line 7]\n APPLY_ABSTRACTION; [line 7]\n " shape="box"] + + + 712 -> 711 ; +711 [label="711: Exit NullDeref_backgroundCoveringView \n " color=yellow style=filled] + + +710 [label="710: Start NullDeref_backgroundCoveringView\nFormals: self:class NullDeref *\nLocals: \n DECLARE_LOCALS(&return); [line 7]\n " color=yellow style=filled] + + + 710 -> 712 ; +709 [label="709: DeclStmt \n n$641=_fun___objc_alloc(sizeof(struct __SecKey ):struct __SecKey *) [line 39]\n *&allowedPublicKey:struct __SecKey *=n$641 [line 39]\n REMOVE_TEMPS(n$641); [line 39]\n " shape="box"] + + + 709 -> 708 ; +708 [label="708: Call _fun___objc_release \n n$638=*&allowedPublicKey:struct __SecKey * [line 40]\n n$639=_fun___objc_release(1:_Bool ,n$638:void *) [line 40]\n REMOVE_TEMPS(n$638,n$639); [line 40]\n NULLIFY(&allowedPublicKey,false); [line 40]\n APPLY_ABSTRACTION; [line 40]\n " shape="box"] + + + 708 -> 707 ; +707 [label="707: Exit NullDeref_test2 \n " color=yellow style=filled] + + +706 [label="706: Start NullDeref_test2\nFormals: \nLocals: allowedPublicKey:struct __SecKey * \n DECLARE_LOCALS(&return,&allowedPublicKey); [line 37]\n NULLIFY(&allowedPublicKey,false); [line 37]\n " color=yellow style=filled] + + + 706 -> 709 ; +705 [label="705: DeclStmt \n n$636=*&rect:struct CGRect [line 29]\n n$637=_fun_CGRectGetHeight(n$636:struct CGRect ) [line 29]\n *&lineThickness:double =(0.200000 * n$637) [line 29]\n REMOVE_TEMPS(n$636,n$637); [line 29]\n NULLIFY(&rect,false); [line 29]\n NULLIFY(&lineThickness,false); [line 29]\n " shape="box"] + + + 705 -> 704 ; +704 [label="704: DeclStmt \n n$635=_fun_CGPathCreateMutable() [line 32]\n *&path1:struct CGPath *=n$635 [line 32]\n REMOVE_TEMPS(n$635); [line 32]\n " shape="box"] + + + 704 -> 703 ; +703 [label="703: Call _fun___objc_release \n n$633=*&path1:struct CGPath * [line 33]\n n$634=_fun___objc_release(1:_Bool ,n$633:void *) [line 33]\n REMOVE_TEMPS(n$633,n$634); [line 33]\n NULLIFY(&path1,false); [line 33]\n APPLY_ABSTRACTION; [line 33]\n " shape="box"] + + + 703 -> 702 ; +702 [label="702: Exit NullDeref_createCloseCrossGlyphNoLeak: \n " color=yellow style=filled] + + +701 [label="701: Start NullDeref_createCloseCrossGlyphNoLeak:\nFormals: rect:struct CGRect \nLocals: lineThickness:double path1:struct CGPath * \n DECLARE_LOCALS(&return,&lineThickness,&path1); [line 27]\n NULLIFY(&lineThickness,false); [line 27]\n NULLIFY(&path1,false); [line 27]\n " color=yellow style=filled] + + + 701 -> 705 ; +700 [label="700: DeclStmt \n n$632=_fun___objc_alloc(sizeof(struct __CFAttributedString ):struct __CFAttributedString *) [line 22]\n *&maString:struct __CFAttributedString *=n$632 [line 22]\n REMOVE_TEMPS(n$632); [line 22]\n " shape="box"] + + + 700 -> 698 ; + 700 -> 699 ; +699 [label="699: Prune (false branch) \n n$628=*&maString:struct __CFAttributedString * [line 21]\n PRUNE(!n$628, false); [line 23]\n REMOVE_TEMPS(n$628); [line 23]\n APPLY_ABSTRACTION; [line 23]\n " shape="invhouse"] + + + 699 -> 696 ; +698 [label="698: Prune (true branch) \n n$628=*&maString:struct __CFAttributedString * [line 21]\n PRUNE(n$628, true); [line 23]\n REMOVE_TEMPS(n$628); [line 23]\n " shape="invhouse"] + + + 698 -> 697 ; +697 [label="697: Call _fun___objc_release \n n$629=*&maString:struct __CFAttributedString * [line 23]\n n$630=_fun___objc_release(1:_Bool ,n$629:void *) [line 23]\n REMOVE_TEMPS(n$629,n$630); [line 23]\n NULLIFY(&maString,false); [line 23]\n APPLY_ABSTRACTION; [line 23]\n " shape="box"] + + + 697 -> 696 ; +696 [label="696: + \n NULLIFY(&maString,false); [line 23]\n " ] + + + 696 -> 695 ; +695 [label="695: Exit NullDeref_measureFrameSizeForTextNoLeak \n " color=yellow style=filled] + + +694 [label="694: Start NullDeref_measureFrameSizeForTextNoLeak\nFormals: \nLocals: maString:struct __CFAttributedString * \n DECLARE_LOCALS(&return,&maString); [line 20]\n NULLIFY(&maString,false); [line 20]\n " color=yellow style=filled] + + + 694 -> 700 ; +693 [label="693: DeclStmt \n n$627=_fun___objc_alloc(sizeof(class UIView ):class UIView *) [line 12]\n *&attachmentContainerView:class UIView *=n$627 [line 12]\n REMOVE_TEMPS(n$627); [line 12]\n " shape="box"] + + + 693 -> 692 ; +692 [label="692: DeclStmt \n n$624=*&attachmentContainerView:class UIView * [line 13]\n n$623=_fun_UIView_bounds(n$624:class UIView *) virtual [line 13]\n n$625=_fun_CGPathCreateWithRect(n$623:struct CGRect ,0:CGAffineTransform *) [line 13]\n *&shadowPath:struct CGPath *=n$625 [line 13]\n REMOVE_TEMPS(n$623,n$624,n$625); [line 13]\n " shape="box"] + + + 692 -> 691 ; +691 [label="691: Call _fun___objc_release \n n$621=*&shadowPath:struct CGPath * [line 14]\n n$622=_fun___objc_release(1:_Bool ,n$621:void *) [line 14]\n REMOVE_TEMPS(n$621,n$622); [line 14]\n NULLIFY(&shadowPath,false); [line 14]\n " shape="box"] + + + 691 -> 690 ; +690 [label="690: Message Call: release \n n$620=*&attachmentContainerView:class UIView * [line 15]\n n$619=_fun_NSObject_release(n$620:class UIView *) [line 15]\n REMOVE_TEMPS(n$619,n$620); [line 15]\n NULLIFY(&attachmentContainerView,false); [line 15]\n APPLY_ABSTRACTION; [line 15]\n " shape="box"] + + + 690 -> 689 ; +689 [label="689: Exit NullDeref_layoutSubviews \n " color=yellow style=filled] + + +688 [label="688: Start NullDeref_layoutSubviews\nFormals: self:class NullDeref *\nLocals: attachmentContainerView:class UIView * shadowPath:struct CGPath * \n DECLARE_LOCALS(&return,&attachmentContainerView,&shadowPath); [line 10]\n NULLIFY(&attachmentContainerView,false); [line 10]\n NULLIFY(&self,false); [line 10]\n NULLIFY(&shadowPath,false); [line 10]\n " color=yellow style=filled] + + + 688 -> 693 ; +} diff --git a/infer/tests/codetoanalyze/objc/errors/npe/NPD_core_foundation.h b/infer/tests/codetoanalyze/objc/errors/npe/NPD_core_foundation.h new file mode 100644 index 000000000..f8b6c175c --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/npe/NPD_core_foundation.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import +#import + +@interface NullDeref : NSObject + +@property UIView *backgroundCoveringView; +@property UIView *attachmentContainerView; + +@end diff --git a/infer/tests/codetoanalyze/objc/errors/npe/NPD_core_foundation.m b/infer/tests/codetoanalyze/objc/errors/npe/NPD_core_foundation.m new file mode 100644 index 000000000..7046c9f38 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/npe/NPD_core_foundation.m @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import "NPD_core_foundation.h" +#import +#import +#import + +@implementation NullDeref + +//For now it doesn't contain memory leak by default. Later, if we remove +//CFRelease, there should be a leak. +- (void)layoutSubviews +{ + UIView *attachmentContainerView = [UIView alloc]; + CGPathRef shadowPath = CGPathCreateWithRect(attachmentContainerView.bounds, NULL); + CFRelease(shadowPath); + [attachmentContainerView release]; + +} + + ++ (void)measureFrameSizeForTextNoLeak +{ + CFMutableAttributedStringRef maString = CFAttributedStringCreateMutable(nil, 0); + if (maString) CFRelease(maString); +} + + ++ (void) createCloseCrossGlyphNoLeak:(CGRect) rect +{ + CGFloat lineThickness = 0.20f * CGRectGetHeight(rect); + + // One rectangle + CGMutablePathRef path1 = CGPathCreateMutable(); + CFRelease(path1); +} + + ++ (void) test2 +{ + SecKeyRef allowedPublicKey = SecTrustCopyPublicKey(nil); + CFRelease(allowedPublicKey); +} + + +@end diff --git a/infer/tests/codetoanalyze/objc/errors/npe/UpdateDict.m b/infer/tests/codetoanalyze/objc/errors/npe/UpdateDict.m new file mode 100644 index 000000000..ea09e8053 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/npe/UpdateDict.m @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + + +void update_dict_with_null() { + NSMutableDictionary *mDict = [NSMutableDictionary dictionaryWithObjectsAndKeys: + @"Matt", @"firstName", @"Galloway", @"lastName", + @28, @"age", nil]; + mDict[@"firstName"] = nil; +} + +void update_dict_with_key_null() { + NSMutableDictionary *mDict = [NSMutableDictionary dictionaryWithObjectsAndKeys: + @"Matt", @"firstName", @"Galloway", @"lastName", + @28, @"age", nil]; + id key = nil; + mDict[key] = @"Dulma"; +} + +void update_dict_without_null() { + NSMutableDictionary *mDict = [NSMutableDictionary dictionaryWithObjectsAndKeys: + @"Matt", @"firstName", @"Galloway", @"lastName", + @28, @"age", nil]; + mDict[@"firstName"] = @"Dulma"; +} + +void update_array_with_null() { + NSMutableArray *array = [NSMutableArray arrayWithObjects:@"Dulma", @"Rodriguez", nil]; + NSUInteger idx = 0; + id newObject = nil; + array[idx] = newObject; +} + +void update_array_without_null() { + NSMutableArray *array = [NSMutableArray arrayWithObjects:@"Dulma", @"Rodriguez", nil]; + NSUInteger idx = 0; + id newObject = @"Dino"; + array[idx] = newObject; +} + +void add_nil_to_array() { + NSMutableArray *array = [NSMutableArray arrayWithObjects:@"Dulma", @"Rodriguez", nil]; + id str = nil; + [array addObject:str]; +} + +void insert_nil_in_array() { + NSMutableArray *array = [NSMutableArray arrayWithObjects:@"Dulma", @"Rodriguez", nil]; + id str = nil; + [array insertObject:str atIndex:0]; +} + +void add_nil_in_dict() { + NSMutableDictionary *mDict = [NSMutableDictionary dictionaryWithObjectsAndKeys: + @"Matt", @"firstName", @"Galloway", @"lastName", + @28, @"age", nil]; + id str = nil; + [mDict setObject:str forKey:@"firstName"]; +} + +void no_npe_for_undef_values (NSDictionary* response){ + NSMutableDictionary *fileInfo = [response mutableCopy]; + fileInfo[@"type"] = @"BLA"; + NSMutableDictionary *d = [NSMutableDictionary dictionary]; + d[@"fds"] = fileInfo; +} diff --git a/infer/tests/codetoanalyze/objc/errors/npe/block.m b/infer/tests/codetoanalyze/objc/errors/npe/block.m new file mode 100644 index 000000000..493de9012 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/npe/block.m @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface A : NSObject +@end + + +@implementation A { + + void (^_block_field)(void); +} + + +- (void)doSomethingThenCallback:( void(^)(void) ) my_block +{ + // null dereference, segfault + my_block(); + +} + + +- (void) foo { + + void (^my_block)(void)=^() {}; + my_block = NULL; + my_block(); // Null deref + +} + + +- foo2: ( void(^)(void) ) my_block { + // ok to call this block! + if(my_block != nil) { + my_block(); +} + +} + + +- (void) foo3: ( void(^)(void) ) my_block { + + my_block = NULL; + my_block(); //Null deref + +} + +- (void) foo4: ( void(^)(void) ) my_block_param { + + void (^my_block)(void)=^() {}; + my_block = NULL; + my_block_param = my_block; + my_block_param(); //Null deref + +} + +- (void) foo5: ( void(^)(void) ) my_block_param { + + void (^my_block)(void)=^() {}; + my_block_param = my_block; + my_block_param(); //No error here + +} + + +- (void) foo6: (BOOL)a block_param: ( void (^)(void)) block_param { + + void (^my_block)(void)=^() { + if (block_param) + block_param(); //No error here + + }; + + if (a){ + [self foo2: ^() { + my_block(); // No error here + }]; + } + +} + +- (void) foo7 { + + _block_field(); // Ivar not nullable + +} + +@end diff --git a/infer/tests/codetoanalyze/objc/errors/npe/nil_in_array_literal.m b/infer/tests/codetoanalyze/objc/errors/npe/nil_in_array_literal.m new file mode 100644 index 000000000..0cb8a67c3 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/npe/nil_in_array_literal.m @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface A : NSObject { + +} +@end + +@implementation A + +-(void) noProblem { + NSArray *foo = @[@"aaa", @"bbb"]; + // check that array literals create valid objects + NSArray *foofoo = @[foo]; +} + +-(void) nilInArrayLiteral { + NSString *str = nil; + + // nil argument in array literal crashes + NSArray *foo = @[@"aaa", str, @"bbb"]; +} + +@end + +int main() { + A *a = [A alloc]; + [a noProblem]; + [a nilInArrayLiteral]; + return 0; +} diff --git a/infer/tests/codetoanalyze/objc/errors/npe/nil_param.m b/infer/tests/codetoanalyze/objc/errors/npe/nil_param.m new file mode 100644 index 000000000..d99918329 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/npe/nil_param.m @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface A : NSObject { + int x; +} +@end + +@implementation A + +-(void) test2 { + self->x = 1; +} + +-(void) test1:(A*) other { + + [other test2]; + + +} + +@end + +int main () { + + A* a=[A alloc]; + [a test1: nil]; + [a release]; + return 0; +} diff --git a/infer/tests/codetoanalyze/objc/errors/npe/npe_malloc.dot b/infer/tests/codetoanalyze/objc/errors/npe/npe_malloc.dot new file mode 100644 index 000000000..c5c82bfba --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/npe/npe_malloc.dot @@ -0,0 +1,21 @@ +digraph iCFG { +5 [label="5: DeclStmt \n n$2=_fun_malloc_no_fail(sizeof(struct Person ):struct Person ) [line 22]\n *&person:struct Person *=n$2 [line 22]\n REMOVE_TEMPS(n$2); [line 22]\n " shape="box"] + + + 5 -> 4 ; +4 [label="4: BinaryOperatorStmt: Assign \n n$1=*&person:struct Person * [line 23]\n *n$1.x:int =10 [line 23]\n REMOVE_TEMPS(n$1); [line 23]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n n$0=*&person:struct Person * [line 24]\n *&return:struct Person *=n$0 [line 24]\n REMOVE_TEMPS(n$0); [line 24]\n NULLIFY(&person,false); [line 24]\n APPLY_ABSTRACTION; [line 24]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit C_test \n " color=yellow style=filled] + + +1 [label="1: Start C_test\nFormals: self:class C *\nLocals: person:struct Person * \n DECLARE_LOCALS(&return,&person); [line 20]\n NULLIFY(&person,false); [line 20]\n NULLIFY(&self,false); [line 20]\n " color=yellow style=filled] + + + 1 -> 5 ; +} diff --git a/infer/tests/codetoanalyze/objc/errors/npe/npe_malloc.m b/infer/tests/codetoanalyze/objc/errors/npe/npe_malloc.m new file mode 100644 index 000000000..8b1c8d19a --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/npe/npe_malloc.m @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import +#import + +struct Person { + int x; + int y; +}; + +@interface C : NSObject + +@end + +@implementation C + +- (struct Person*) test +{ + struct Person* person = (struct Person *)malloc(sizeof(struct Person)); + person->x = 10; + return person; +} + +@end diff --git a/infer/tests/codetoanalyze/objc/errors/npe/npe_self.m b/infer/tests/codetoanalyze/objc/errors/npe/npe_self.m new file mode 100644 index 000000000..ed4845d0c --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/npe/npe_self.m @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +#import +#import + + +@interface C : NSObject { + int x; + C* _currentCompositionState; +} +@property (nonatomic, copy, readonly) NSString *JSON; + +@end + +@implementation C + +- (id)init { + self = [super init]; + self->x = 10; + return self; +} + +- (void)captureManagerSessionDidStart +{ + __weak C *weakSelf = self; + C *strongSelf = weakSelf; + int x = strongSelf->x; +} + +- (int)test +{ + if (_currentCompositionState != nil) {} + return _currentCompositionState->x; +} + +- (BOOL)isEqual:(id)object { + if (object == self) return YES; + if (![object isKindOfClass:[self class]]) return NO; + C *other = (C *)object; + return ([_JSON isEqualToString:other->_JSON]); +} + +@end diff --git a/infer/tests/codetoanalyze/objc/errors/npe/null_returned_by_method.m b/infer/tests/codetoanalyze/objc/errors/npe/null_returned_by_method.m new file mode 100644 index 000000000..29c45e3f4 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/npe/null_returned_by_method.m @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface A : NSObject + +@end + +@implementation A { + int x; +} + +-(A*)test { + return nil; +} + +-(int)test1 { + return [self test]->x; +} + +@end diff --git a/infer/tests/codetoanalyze/objc/errors/procdescs/MethodCall.h b/infer/tests/codetoanalyze/objc/errors/procdescs/MethodCall.h new file mode 100644 index 000000000..369d6aed9 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/procdescs/MethodCall.h @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface MethodCall : NSObject + +- (int)plusX :(int)x andY: (int)y; + +@end diff --git a/infer/tests/codetoanalyze/objc/errors/procdescs/MethodCall.m b/infer/tests/codetoanalyze/objc/errors/procdescs/MethodCall.m new file mode 100644 index 000000000..538027d8a --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/procdescs/MethodCall.m @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import "MethodCall.h" + +@implementation MethodCall + +- (int)plusX : (int)x andY : (int)y { + return x+y; +} + +@end diff --git a/infer/tests/codetoanalyze/objc/errors/procdescs/main.c b/infer/tests/codetoanalyze/objc/errors/procdescs/main.c new file mode 100644 index 000000000..5873bb354 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/procdescs/main.c @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#include "MethodCall.h" + + +int main() { + MethodCall *call = [MethodCall alloc]; + int n = [call plusX : 1 andY: 3]; + int *x = malloc(sizeof(int)); + return n; +} + +int call_nslog() { + MethodCall *call = [MethodCall alloc]; + NSLog(@"%s", "printing"); + int *x = malloc(sizeof(int)); + return 0; +} diff --git a/infer/tests/codetoanalyze/objc/errors/property/IvarExample.h b/infer/tests/codetoanalyze/objc/errors/property/IvarExample.h new file mode 100644 index 000000000..b35edb385 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/property/IvarExample.h @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface IvarExample : NSObject { +} +@property int aProperty; +@end + diff --git a/infer/tests/codetoanalyze/objc/errors/property/main.c b/infer/tests/codetoanalyze/objc/errors/property/main.c new file mode 100644 index 000000000..d39aac8eb --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/property/main.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#include "IvarExample.h" + +int main() { + IvarExample *i = [IvarExample alloc]; + int a = i.aProperty; + int *x = malloc(sizeof(int)); + return 0; +} diff --git a/infer/tests/codetoanalyze/objc/errors/protocol_procdesc/Bicycle.h b/infer/tests/codetoanalyze/objc/errors/protocol_procdesc/Bicycle.h new file mode 100644 index 000000000..a29d234d6 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/protocol_procdesc/Bicycle.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import "StreetVehicle.h" + +@interface Bicycle : NSObject + +- (void)startPedaling; +- (void)removeFrontWheel; +- (void)lockToStructure:(id)theStructure; + +@end diff --git a/infer/tests/codetoanalyze/objc/errors/protocol_procdesc/Bicycle.m b/infer/tests/codetoanalyze/objc/errors/protocol_procdesc/Bicycle.m new file mode 100644 index 000000000..412aabb81 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/protocol_procdesc/Bicycle.m @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import "Bicycle.h" +#import + +@implementation Bicycle + +- (void)signalStop { + NSLog(@"Bending left arm downwards"); +} +- (void)signalLeftTurn { + NSLog(@"Extending left arm outwards"); +} +- (void)signalRightTurn { + NSLog(@"Bending left arm upwards"); +} +- (void)startPedaling { + NSLog(@"Here we go!"); +} +- (void)removeFrontWheel { + NSLog(@"Front wheel is off." + "Should probably replace that before pedaling..."); +} +- (void)lockToStructure:(id)theStructure { + NSLog(@"Locked to structure. Don't forget the combination!"); +} + +@end diff --git a/infer/tests/codetoanalyze/objc/errors/protocol_procdesc/StreetVehicle.h b/infer/tests/codetoanalyze/objc/errors/protocol_procdesc/StreetVehicle.h new file mode 100644 index 000000000..6662c047c --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/protocol_procdesc/StreetVehicle.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@protocol StreetVehicle + + +- (void)signalStop; +- (void)signalLeftTurn; +- (void)signalRightTurn; + + +@end diff --git a/infer/tests/codetoanalyze/objc/errors/protocol_procdesc/main.c b/infer/tests/codetoanalyze/objc/errors/protocol_procdesc/main.c new file mode 100644 index 000000000..f68cfd906 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/protocol_procdesc/main.c @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#include "Bicycle.h" + + +int main() { + + Bicycle *bike = [Bicycle alloc]; + + [bike signalStop]; + + return 0; +} diff --git a/infer/tests/codetoanalyze/objc/errors/protocol_procdesc/main.dot b/infer/tests/codetoanalyze/objc/errors/protocol_procdesc/main.dot new file mode 100644 index 000000000..24901ca81 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/protocol_procdesc/main.dot @@ -0,0 +1,21 @@ +digraph iCFG { +5 [label="5: DeclStmt \n n$2=_fun___objc_alloc_no_fail(sizeof(class Bicycle ):class Bicycle *) [line 11]\n *&bike:class Bicycle *=n$2 [line 11]\n REMOVE_TEMPS(n$2); [line 11]\n " shape="box"] + + + 5 -> 4 ; +4 [label="4: Message Call: signalStop \n n$0=*&bike:class Bicycle * [line 13]\n _fun_Bicycle_signalStop(n$0:class Bicycle *) virtual [line 13]\n REMOVE_TEMPS(n$0); [line 13]\n NULLIFY(&bike,false); [line 13]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n *&return:int =0 [line 15]\n APPLY_ABSTRACTION; [line 15]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: bike:class Bicycle * \n DECLARE_LOCALS(&return,&bike); [line 9]\n NULLIFY(&bike,false); [line 9]\n " color=yellow style=filled] + + + 1 -> 5 ; +} diff --git a/infer/tests/codetoanalyze/objc/errors/resource_leaks/ResourceLeakExample.m b/infer/tests/codetoanalyze/objc/errors/resource_leaks/ResourceLeakExample.m new file mode 100644 index 000000000..e0306856b --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/resource_leaks/ResourceLeakExample.m @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import +#include + +@implementation NSFileHandle (A) + +- (id)fileHandleForLoggingAtPath:(NSString *)path mode:(mode_t)mode { + int fd = -1; + if (path) { + int flags = O_WRONLY | O_APPEND | O_CREAT; + fd = open([path fileSystemRepresentation], flags, mode); + } + if (fd == -1) return nil; + if (self) { + return [[self initWithFileDescriptor:fd + closeOnDealloc:YES] autorelease]; + } + else { + close(fd); + return self; + } +} + +- (id)newOutput:(out NSError**)error +{ + int fileDescriptor = open("file.txt", + O_WRONLY | O_CREAT, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (fileDescriptor == -1) + { + if (error) + *error = [NSError errorWithDomain:NSPOSIXErrorDomain + code:errno + userInfo:nil]; + return nil; + } + else { + if (self) + return [self initWithFileDescriptor:fileDescriptor]; + else { + close(fileDescriptor); + return nil; + } + } +} + +@end diff --git a/infer/tests/codetoanalyze/objc/errors/returnstmt/return_npe_test.m b/infer/tests/codetoanalyze/objc/errors/returnstmt/return_npe_test.m new file mode 100644 index 000000000..2e5e52361 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/returnstmt/return_npe_test.m @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface ContainerClass : NSObject { +@public + int containedValue; +} + +@end + +@implementation ContainerClass + +@end + +@interface MyClass : NSObject + +@end + +@implementation MyClass + +- (void) aMethod: (ContainerClass*) c { + int i = 0; + if (c == nil) { + return; + } + + if(i == 0) { + // here c cannot be nil, because of the previous if + i = c->containedValue; + } +} + +@end diff --git a/infer/tests/codetoanalyze/objc/errors/variadic_methods/premature_nil_termination.m b/infer/tests/codetoanalyze/objc/errors/variadic_methods/premature_nil_termination.m new file mode 100644 index 000000000..6e158f8d3 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/errors/variadic_methods/premature_nil_termination.m @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface A : NSObject { + +} +@end + +@implementation A + +-(void) noProblem { + NSArray *foo = [NSArray arrayWithObjects: @"aaa", @"bbb", nil]; +} + +-(void) nilInArrayWithObjects { + NSString *str = nil; + + // nil argument in arrayWithObjects terminates the list of arguments prematurely + NSArray *foo = [NSArray arrayWithObjects: @"aaa", str, @"bbb", nil]; +} + +@end + +int main() { + A *a = [A alloc]; + [a noProblem]; + [a nilInArrayWithObjects]; + return 0; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/assertions/NSAssert_example.dot b/infer/tests/codetoanalyze/objc/frontend/assertions/NSAssert_example.dot new file mode 100644 index 000000000..04edd7e51 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/assertions/NSAssert_example.dot @@ -0,0 +1,284 @@ +digraph iCFG { +69 [label="69: Assertion failure \n _fun___infer_fail(\"Assertion_failure\":void ) [line 113]\n APPLY_ABSTRACTION; [line 113]\n " shape="box"] + + + 69 -> 56 ; +68 [label="68: Prune (false branch) \n n$33=*&SIL_temp_conditional___62:int [line 112]\n NULLIFY(&SIL_temp_conditional___62,true); [line 112]\n PRUNE((n$33 == 0), false); [line 112]\n REMOVE_TEMPS(n$33); [line 112]\n " shape="invhouse"] + + + 68 -> 61 ; +67 [label="67: Prune (true branch) \n n$33=*&SIL_temp_conditional___62:int [line 112]\n NULLIFY(&SIL_temp_conditional___62,true); [line 112]\n PRUNE((n$33 != 0), true); [line 112]\n REMOVE_TEMPS(n$33); [line 112]\n NULLIFY(&target,false); [line 112]\n " shape="invhouse"] + + + 67 -> 69 ; +66 [label="66: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___62); [line 112]\n *&SIL_temp_conditional___62:int =1 [line 112]\n APPLY_ABSTRACTION; [line 112]\n " shape="box"] + + + 66 -> 62 ; +65 [label="65: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___62); [line 112]\n *&SIL_temp_conditional___62:int =0 [line 112]\n APPLY_ABSTRACTION; [line 112]\n " shape="box"] + + + 65 -> 62 ; +64 [label="64: Prune (false branch) \n n$32=*&target:class A * [line 112]\n PRUNE((n$32 == 0), false); [line 112]\n REMOVE_TEMPS(n$32); [line 112]\n " shape="invhouse"] + + + 64 -> 66 ; +63 [label="63: Prune (true branch) \n n$32=*&target:class A * [line 112]\n PRUNE((n$32 != 0), true); [line 112]\n REMOVE_TEMPS(n$32); [line 112]\n " shape="invhouse"] + + + 63 -> 65 ; +62 [label="62: + \n " ] + + + 62 -> 67 ; + 62 -> 68 ; +61 [label="61: + \n " ] + + + 61 -> 59 ; + 61 -> 60 ; +60 [label="60: Prune (false branch) \n PRUNE((0 == 0), false); [line 118]\n " shape="invhouse"] + + + 60 -> 57 ; +59 [label="59: Prune (true branch) \n PRUNE((0 != 0), true); [line 118]\n APPLY_ABSTRACTION; [line 118]\n " shape="invhouse"] + + + 59 -> 58 ; +58 [label="58: + \n " ] + + + 58 -> 63 ; + 58 -> 64 ; +57 [label="57: Return Stmt \n n$31=*&target:class A * [line 33]\n n$30=_fun_A_x(n$31:class A *) virtual [line 33]\n *&return:int =n$30 [line 33]\n REMOVE_TEMPS(n$30,n$31); [line 33]\n NULLIFY(&target,false); [line 33]\n APPLY_ABSTRACTION; [line 33]\n " shape="box"] + + + 57 -> 56 ; +56 [label="56: Exit test2 \n " color=yellow style=filled] + + +55 [label="55: Start test2\nFormals: target:class A *\nLocals: \n DECLARE_LOCALS(&return); [line 31]\n " color=yellow style=filled] + + + 55 -> 58 ; +54 [label="54: Assertion failure \n _fun___infer_fail(\"Assertion_failure\":void ) [line 113]\n APPLY_ABSTRACTION; [line 113]\n " shape="box"] + + + 54 -> 40 ; +53 [label="53: Prune (false branch) \n n$25=*&SIL_temp_conditional___46:int [line 112]\n NULLIFY(&SIL_temp_conditional___46,true); [line 112]\n PRUNE((n$25 == 0), false); [line 112]\n REMOVE_TEMPS(n$25); [line 112]\n " shape="invhouse"] + + + 53 -> 45 ; +52 [label="52: Prune (true branch) \n n$25=*&SIL_temp_conditional___46:int [line 112]\n NULLIFY(&SIL_temp_conditional___46,true); [line 112]\n PRUNE((n$25 != 0), true); [line 112]\n REMOVE_TEMPS(n$25); [line 112]\n NULLIFY(&target,false); [line 112]\n " shape="invhouse"] + + + 52 -> 54 ; +51 [label="51: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___46); [line 112]\n *&SIL_temp_conditional___46:int =1 [line 112]\n APPLY_ABSTRACTION; [line 112]\n " shape="box"] + + + 51 -> 46 ; +50 [label="50: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___46); [line 112]\n *&SIL_temp_conditional___46:int =0 [line 112]\n APPLY_ABSTRACTION; [line 112]\n " shape="box"] + + + 50 -> 46 ; +49 [label="49: Prune (false branch) \n PRUNE(((n$24 != (void *)0) == 0), false); [line 27]\n REMOVE_TEMPS(n$24); [line 27]\n " shape="invhouse"] + + + 49 -> 51 ; +48 [label="48: Prune (true branch) \n PRUNE(((n$24 != (void *)0) != 0), true); [line 27]\n REMOVE_TEMPS(n$24); [line 27]\n " shape="invhouse"] + + + 48 -> 50 ; +47 [label="47: BinaryOperatorStmt: NE \n n$24=*&target:class A * [line 27]\n " shape="box"] + + + 47 -> 48 ; + 47 -> 49 ; +46 [label="46: + \n " ] + + + 46 -> 52 ; + 46 -> 53 ; +45 [label="45: + \n " ] + + + 45 -> 43 ; + 45 -> 44 ; +44 [label="44: Prune (false branch) \n PRUNE((0 == 0), false); [line 118]\n " shape="invhouse"] + + + 44 -> 41 ; +43 [label="43: Prune (true branch) \n PRUNE((0 != 0), true); [line 118]\n APPLY_ABSTRACTION; [line 118]\n " shape="invhouse"] + + + 43 -> 42 ; +42 [label="42: + \n " ] + + + 42 -> 47 ; +41 [label="41: Return Stmt \n n$23=*&target:class A * [line 28]\n n$22=_fun_A_x(n$23:class A *) virtual [line 28]\n *&return:int =n$22 [line 28]\n REMOVE_TEMPS(n$22,n$23); [line 28]\n NULLIFY(&target,false); [line 28]\n APPLY_ABSTRACTION; [line 28]\n " shape="box"] + + + 41 -> 40 ; +40 [label="40: Exit test1 \n " color=yellow style=filled] + + +39 [label="39: Start test1\nFormals: target:class A *\nLocals: \n DECLARE_LOCALS(&return); [line 26]\n " color=yellow style=filled] + + + 39 -> 42 ; +38 [label="38: BinaryOperatorStmt: Assign \n n$20=*&self:class A * [line -1]\n n$21=*&x:int [line -1]\n *n$20._x:int =n$21 [line -1]\n REMOVE_TEMPS(n$20,n$21); [line -1]\n NULLIFY(&self,false); [line -1]\n NULLIFY(&x,false); [line -1]\n APPLY_ABSTRACTION; [line -1]\n " shape="box"] + + + 38 -> 37 ; +37 [label="37: Exit A_setX: \n " color=yellow style=filled] + + +36 [label="36: Start A_setX:\nFormals: self:class A * x:int \nLocals: \n DECLARE_LOCALS(&return); [line -1]\n " color=yellow style=filled] + + + 36 -> 38 ; +35 [label="35: Return Stmt \n n$18=*&self:class A * [line -1]\n n$19=*n$18._x:int [line -1]\n *&return:int =n$19 [line -1]\n REMOVE_TEMPS(n$18,n$19); [line -1]\n NULLIFY(&self,false); [line -1]\n APPLY_ABSTRACTION; [line -1]\n " shape="box"] + + + 35 -> 34 ; +34 [label="34: Exit A_x \n " color=yellow style=filled] + + +33 [label="33: Start A_x\nFormals: self:class A *\nLocals: \n DECLARE_LOCALS(&return); [line -1]\n " color=yellow style=filled] + + + 33 -> 35 ; +32 [label="32: Assertion failure \n _fun___infer_fail(\"Assertion_failure\":void ) [line 100]\n APPLY_ABSTRACTION; [line 100]\n " shape="box"] + + + 32 -> 18 ; +31 [label="31: Prune (false branch) \n n$12=*&SIL_temp_conditional___24:int [line 99]\n NULLIFY(&SIL_temp_conditional___24,true); [line 99]\n PRUNE((n$12 == 0), false); [line 99]\n REMOVE_TEMPS(n$12); [line 99]\n " shape="invhouse"] + + + 31 -> 23 ; +30 [label="30: Prune (true branch) \n n$12=*&SIL_temp_conditional___24:int [line 99]\n NULLIFY(&SIL_temp_conditional___24,true); [line 99]\n PRUNE((n$12 != 0), true); [line 99]\n REMOVE_TEMPS(n$12); [line 99]\n NULLIFY(&a,false); [line 99]\n " shape="invhouse"] + + + 30 -> 32 ; +29 [label="29: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___24); [line 99]\n *&SIL_temp_conditional___24:int =1 [line 99]\n APPLY_ABSTRACTION; [line 99]\n " shape="box"] + + + 29 -> 24 ; +28 [label="28: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___24); [line 99]\n *&SIL_temp_conditional___24:int =0 [line 99]\n APPLY_ABSTRACTION; [line 99]\n " shape="box"] + + + 28 -> 24 ; +27 [label="27: Prune (false branch) \n PRUNE(((n$11 != (void *)0) == 0), false); [line 20]\n REMOVE_TEMPS(n$11); [line 20]\n " shape="invhouse"] + + + 27 -> 29 ; +26 [label="26: Prune (true branch) \n PRUNE(((n$11 != (void *)0) != 0), true); [line 20]\n REMOVE_TEMPS(n$11); [line 20]\n " shape="invhouse"] + + + 26 -> 28 ; +25 [label="25: BinaryOperatorStmt: NE \n n$11=*&a:class A * [line 20]\n " shape="box"] + + + 25 -> 26 ; + 25 -> 27 ; +24 [label="24: + \n " ] + + + 24 -> 30 ; + 24 -> 31 ; +23 [label="23: + \n " ] + + + 23 -> 21 ; + 23 -> 22 ; +22 [label="22: Prune (false branch) \n PRUNE((0 == 0), false); [line 105]\n " shape="invhouse"] + + + 22 -> 19 ; +21 [label="21: Prune (true branch) \n PRUNE((0 != 0), true); [line 105]\n APPLY_ABSTRACTION; [line 105]\n " shape="invhouse"] + + + 21 -> 20 ; +20 [label="20: + \n " ] + + + 20 -> 25 ; +19 [label="19: Return Stmt \n n$10=*&a:class A * [line 21]\n n$9=_fun_A_x(n$10:class A *) virtual [line 21]\n *&return:int =n$9 [line 21]\n REMOVE_TEMPS(n$9,n$10); [line 21]\n NULLIFY(&a,false); [line 21]\n APPLY_ABSTRACTION; [line 21]\n " shape="box"] + + + 19 -> 18 ; +18 [label="18: Exit A_initWithRequest: \n " color=yellow style=filled] + + +17 [label="17: Start A_initWithRequest:\nFormals: self:class A * a:class A *\nLocals: \n DECLARE_LOCALS(&return); [line 19]\n NULLIFY(&self,false); [line 19]\n " color=yellow style=filled] + + + 17 -> 20 ; +16 [label="16: Assertion failure \n _fun___infer_fail(\"Assertion_failure\":void ) [line 100]\n APPLY_ABSTRACTION; [line 100]\n " shape="box"] + + + 16 -> 2 ; +15 [label="15: Prune (false branch) \n n$3=*&SIL_temp_conditional___8:int [line 99]\n NULLIFY(&SIL_temp_conditional___8,true); [line 99]\n PRUNE((n$3 == 0), false); [line 99]\n REMOVE_TEMPS(n$3); [line 99]\n " shape="invhouse"] + + + 15 -> 7 ; +14 [label="14: Prune (true branch) \n n$3=*&SIL_temp_conditional___8:int [line 99]\n NULLIFY(&SIL_temp_conditional___8,true); [line 99]\n PRUNE((n$3 != 0), true); [line 99]\n REMOVE_TEMPS(n$3); [line 99]\n NULLIFY(&target,false); [line 99]\n " shape="invhouse"] + + + 14 -> 16 ; +13 [label="13: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___8); [line 99]\n *&SIL_temp_conditional___8:int =1 [line 99]\n APPLY_ABSTRACTION; [line 99]\n " shape="box"] + + + 13 -> 8 ; +12 [label="12: ConditinalStmt Branch \n DECLARE_LOCALS(&SIL_temp_conditional___8); [line 99]\n *&SIL_temp_conditional___8:int =0 [line 99]\n APPLY_ABSTRACTION; [line 99]\n " shape="box"] + + + 12 -> 8 ; +11 [label="11: Prune (false branch) \n PRUNE(((n$2 != (void *)0) == 0), false); [line 15]\n REMOVE_TEMPS(n$2); [line 15]\n " shape="invhouse"] + + + 11 -> 13 ; +10 [label="10: Prune (true branch) \n PRUNE(((n$2 != (void *)0) != 0), true); [line 15]\n REMOVE_TEMPS(n$2); [line 15]\n " shape="invhouse"] + + + 10 -> 12 ; +9 [label="9: BinaryOperatorStmt: NE \n n$2=*&target:class A * [line 15]\n " shape="box"] + + + 9 -> 10 ; + 9 -> 11 ; +8 [label="8: + \n " ] + + + 8 -> 14 ; + 8 -> 15 ; +7 [label="7: + \n " ] + + + 7 -> 5 ; + 7 -> 6 ; +6 [label="6: Prune (false branch) \n PRUNE((0 == 0), false); [line 105]\n " shape="invhouse"] + + + 6 -> 3 ; +5 [label="5: Prune (true branch) \n PRUNE((0 != 0), true); [line 105]\n APPLY_ABSTRACTION; [line 105]\n " shape="invhouse"] + + + 5 -> 4 ; +4 [label="4: + \n " ] + + + 4 -> 9 ; +3 [label="3: Return Stmt \n n$1=*&target:class A * [line 16]\n n$0=_fun_A_x(n$1:class A *) virtual [line 16]\n *&return:int =n$0 [line 16]\n REMOVE_TEMPS(n$0,n$1); [line 16]\n NULLIFY(&target,false); [line 16]\n APPLY_ABSTRACTION; [line 16]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit A_addTarget: \n " color=yellow style=filled] + + +1 [label="1: Start A_addTarget:\nFormals: self:class A * target:class A *\nLocals: \n DECLARE_LOCALS(&return); [line 14]\n NULLIFY(&self,false); [line 14]\n " color=yellow style=filled] + + + 1 -> 4 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/assertions/NSAssert_example.m b/infer/tests/codetoanalyze/objc/frontend/assertions/NSAssert_example.m new file mode 100644 index 000000000..fd795fa25 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/assertions/NSAssert_example.m @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface A : NSObject +@property int x; +@end + +@implementation A + +- (int)addTarget:(A*)target { + NSAssert(target != nil, @"target must not be nil"); + return target.x; +} + +- (int)initWithRequest:(A*)a { + NSAssert1(a != nil, @"target must not be nil %s", "a"); + return a.x; +} + +@end + +int test1(A* target) { + NSCAssert(target != nil, @"target must not be nil"); + return target.x; +} + +int test2(A* target) { + NSCParameterAssert(target); + return target.x; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/block/BlockVar.dot b/infer/tests/codetoanalyze/objc/frontend/block/BlockVar.dot new file mode 100644 index 000000000..d74c61333 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/block/BlockVar.dot @@ -0,0 +1,47 @@ +digraph iCFG { +12 [label="12: DeclStmt \n *&error:class NSError *=0 [line 18]\n NULLIFY(&error,false); [line 18]\n " shape="box"] + + + 12 -> 11 ; +11 [label="11: DeclStmt \n n$5=_fun_BlockVar_test() [line 19]\n *&res:int =n$5 [line 19]\n REMOVE_TEMPS(n$5); [line 19]\n " shape="box"] + + + 11 -> 10 ; +10 [label="10: Return Stmt \n n$2=*&a:int [line 20]\n n$3=*&b:int [line 20]\n n$4=*&res:int [line 20]\n *&return:int =((n$2 + n$3) + n$4) [line 20]\n REMOVE_TEMPS(n$2,n$3,n$4); [line 20]\n NULLIFY(&a,false); [line 20]\n NULLIFY(&b,false); [line 20]\n NULLIFY(&res,false); [line 20]\n APPLY_ABSTRACTION; [line 20]\n " shape="box"] + + + 10 -> 9 ; +9 [label="9: Exit __objc_anonymous_block_BlockVar_navigateToURLInBackground:resolver:______1 \n " color=yellow style=filled] + + +8 [label="8: Start __objc_anonymous_block_BlockVar_navigateToURLInBackground:resolver:______1\nFormals: a:int b:int \nLocals: error:class NSError * res:int \n DECLARE_LOCALS(&return,&error,&res); [line 17]\n NULLIFY(&error,false); [line 17]\n NULLIFY(&res,false); [line 17]\n " color=yellow style=filled] + + + 8 -> 12 ; +7 [label="7: DeclStmt \n DECLARE_LOCALS(&__objc_anonymous_block_BlockVar_navigateToURLInBackground:resolver:______1); [line 17]\n n$6=_fun___objc_alloc_no_fail(sizeof(class __objc_anonymous_block_BlockVar_navigateToURLInBackground:resolver:______1 ):class __objc_anonymous_block_BlockVar_navigateToURLInBackground:resolver:______1 *) [line 17]\n *&__objc_anonymous_block_BlockVar_navigateToURLInBackground:resolver:______1:class __objc_anonymous_block_BlockVar_navigateToURLInBackground:resolver:______1 =n$6 [line 17]\n *&addBlock:_fn_ (*)=(_fun___objc_anonymous_block_BlockVar_navigateToURLInBackground:resolver:______1) [line 17]\n REMOVE_TEMPS(n$6); [line 17]\n " shape="box"] + + + 7 -> 6 ; +6 [label="6: Return Stmt \n n$0=*&addBlock:_fn_ (*) [line 22]\n n$1=n$0(1:int ,2:int ) [line 22]\n *&return:int =n$1 [line 22]\n REMOVE_TEMPS(n$0,n$1); [line 22]\n NULLIFY(&__objc_anonymous_block_BlockVar_navigateToURLInBackground:resolver:______1,true); [line 22]\n NULLIFY(&addBlock,false); [line 22]\n APPLY_ABSTRACTION; [line 22]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: Exit BlockVar_navigateToURLInBackground:resolver: \n " color=yellow style=filled] + + +4 [label="4: Start BlockVar_navigateToURLInBackground:resolver:\nFormals: destination:class NSURL * resolver:struct objc_object *\nLocals: addBlock:_fn_ (*) \n DECLARE_LOCALS(&return,&addBlock); [line 14]\n NULLIFY(&addBlock,false); [line 14]\n NULLIFY(&destination,false); [line 14]\n NULLIFY(&resolver,false); [line 14]\n " color=yellow style=filled] + + + 4 -> 7 ; +3 [label="3: Return Stmt \n *&return:int =5 [line 11]\n APPLY_ABSTRACTION; [line 11]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit BlockVar_test \n " color=yellow style=filled] + + +1 [label="1: Start BlockVar_test\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 10]\n " color=yellow style=filled] + + + 1 -> 3 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/block/BlockVar.h b/infer/tests/codetoanalyze/objc/frontend/block/BlockVar.h new file mode 100644 index 000000000..2f8a8e559 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/block/BlockVar.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface BlockVar : NSObject + ++ (int)test; + ++ (int)navigateToURLInBackground:(NSURL *)destination + resolver:(id)resolver; + +@end diff --git a/infer/tests/codetoanalyze/objc/frontend/block/BlockVar.m b/infer/tests/codetoanalyze/objc/frontend/block/BlockVar.m new file mode 100644 index 000000000..d092314a9 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/block/BlockVar.m @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import "BlockVar.h" + +@implementation BlockVar + ++ (int)test { + return 5; +} + ++ (int)navigateToURLInBackground:(NSURL *)destination + resolver:(id)resolver { + + int (^addBlock)(int a, int b) = ^(int a, int b){ + NSError *error = nil; + int res = [self test]; + return a + b + res; + }; + return addBlock(1, 2); +} + +@end diff --git a/infer/tests/codetoanalyze/objc/frontend/block/block.dot b/infer/tests/codetoanalyze/objc/frontend/block/block.dot new file mode 100644 index 000000000..55bad466b --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/block/block.dot @@ -0,0 +1,97 @@ +digraph iCFG { +25 [label="25: Return Stmt \n n$27=_fun_main1(4:int ) [line 47]\n *&return:int =n$27 [line 47]\n REMOVE_TEMPS(n$27); [line 47]\n APPLY_ABSTRACTION; [line 47]\n " shape="box"] + + + 25 -> 24 ; +24 [label="24: Exit main \n " color=yellow style=filled] + + +23 [label="23: Start main\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 46]\n " color=yellow style=filled] + + + 23 -> 25 ; +22 [label="22: DeclStmt \n *&#GB$main1_s:int =3 [line 8]\n " shape="box"] + + + 22 -> 21 ; +21 [label="21: DeclStmt \n *&x:int =7 [line 9]\n " shape="box"] + + + 21 -> 20 ; +20 [label="20: BinaryOperatorStmt: Assign \n DECLARE_LOCALS(&__objc_anonymous_block_main1______2); [line 15]\n n$25=_fun___objc_alloc_no_fail(sizeof(class __objc_anonymous_block_main1______2 ):class __objc_anonymous_block_main1______2 *) [line 15]\n *&__objc_anonymous_block_main1______2:class __objc_anonymous_block_main1______2 =n$25 [line 15]\n n$26=*&x:int [line 15]\n *n$25.x:int =n$26 [line 15]\n n$10=*&x:int [line 15]\n *&addblock:_fn_ (*)=(_fun___objc_anonymous_block_main1______2,n$10) [line 15]\n REMOVE_TEMPS(n$25,n$26,n$10); [line 15]\n " shape="box"] + + + 20 -> 10 ; +19 [label="19: DeclStmt \n *&bla:int =3 [line 19]\n " shape="box"] + + + 19 -> 18 ; +18 [label="18: BinaryOperatorStmt: Assign \n DECLARE_LOCALS(&__objc_anonymous_block___objc_anonymous_block_main1______2______3); [line 21]\n n$22=_fun___objc_alloc_no_fail(sizeof(class __objc_anonymous_block___objc_anonymous_block_main1______2______3 ):class __objc_anonymous_block___objc_anonymous_block_main1______2______3 *) [line 21]\n *&__objc_anonymous_block___objc_anonymous_block_main1______2______3:class __objc_anonymous_block___objc_anonymous_block_main1______2______3 =n$22 [line 21]\n n$23=*&x:int [line 21]\n n$24=*&bla:int [line 21]\n *n$22.x:int =n$23 [line 21]\n *n$22.bla:int =n$24 [line 21]\n n$16=*&x:int [line 21]\n n$17=*&bla:int [line 21]\n *&addblock2:_fn_ (*)=(_fun___objc_anonymous_block___objc_anonymous_block_main1______2______3,n$16,n$17) [line 21]\n REMOVE_TEMPS(n$22,n$23,n$24,n$16,n$17); [line 21]\n " shape="box"] + + + 18 -> 14 ; +17 [label="17: Return Stmt \n n$18=*&z:int [line 22]\n n$19=*&#GB$main1_s:int [line 22]\n n$20=*&x:int [line 22]\n n$21=*&bla:int [line 22]\n *&return:int =(((n$18 + n$19) + n$20) + n$21) [line 22]\n REMOVE_TEMPS(n$18,n$19,n$20,n$21); [line 22]\n NULLIFY(&z,false); [line 22]\n APPLY_ABSTRACTION; [line 22]\n " shape="box"] + + + 17 -> 16 ; +16 [label="16: Exit __objc_anonymous_block___objc_anonymous_block_main1______2______3 \n " color=yellow style=filled] + + +15 [label="15: Start __objc_anonymous_block___objc_anonymous_block_main1______2______3\nFormals: x:int bla:int z:int \nLocals: \nCaptured: x:int bla:int \n DECLARE_LOCALS(&return); [line 21]\n " color=yellow style=filled] + + + 15 -> 17 ; +14 [label="14: BinaryOperatorStmt: Assign \n n$14=*&addblock2:_fn_ (*) [line 25]\n n$15=n$14(1:int ) [line 25]\n *&add2:int =n$15 [line 25]\n REMOVE_TEMPS(n$14,n$15); [line 25]\n NULLIFY(&addblock2,false); [line 25]\n " shape="box"] + + + 14 -> 13 ; +13 [label="13: Return Stmt \n n$11=*&c:int [line 26]\n n$12=*&add2:int [line 26]\n n$13=*&bla:int [line 26]\n *&return:int =((n$11 + n$12) + n$13) [line 26]\n REMOVE_TEMPS(n$11,n$12,n$13); [line 26]\n NULLIFY(&__objc_anonymous_block___objc_anonymous_block_main1______2______3,true); [line 26]\n NULLIFY(&add2,false); [line 26]\n NULLIFY(&c,false); [line 26]\n APPLY_ABSTRACTION; [line 26]\n " shape="box"] + + + 13 -> 12 ; +12 [label="12: Exit __objc_anonymous_block_main1______2 \n " color=yellow style=filled] + + +11 [label="11: Start __objc_anonymous_block_main1______2\nFormals: x:int c:int d:int \nLocals: addblock2:_fn_ (*) add2:int bla:int \nCaptured: x:int \n DECLARE_LOCALS(&return,&addblock2,&add2,&bla); [line 15]\n NULLIFY(&add2,false); [line 15]\n NULLIFY(&addblock2,false); [line 15]\n NULLIFY(&d,false); [line 15]\n " color=yellow style=filled] + + + 11 -> 19 ; +10 [label="10: BinaryOperatorStmt: Assign \n n$8=*&addblock:_fn_ (*) [line 29]\n n$9=n$8(1:int ,2:int ) [line 29]\n *&add1:int =n$9 [line 29]\n REMOVE_TEMPS(n$8,n$9); [line 29]\n NULLIFY(&addblock,false); [line 29]\n " shape="box"] + + + 10 -> 9 ; +9 [label="9: BinaryOperatorStmt: Assign \n DECLARE_LOCALS(&__objc_anonymous_block_main1______1); [line 32]\n n$7=_fun___objc_alloc_no_fail(sizeof(class __objc_anonymous_block_main1______1 ):class __objc_anonymous_block_main1______1 *) [line 32]\n *&__objc_anonymous_block_main1______1:class __objc_anonymous_block_main1______1 =n$7 [line 32]\n *&addblock:_fn_ (*)=(_fun___objc_anonymous_block_main1______1) [line 32]\n REMOVE_TEMPS(n$7); [line 32]\n " shape="box"] + + + 9 -> 5 ; +8 [label="8: Return Stmt \n n$5=*&e:int [line 33]\n n$6=*&#GB$main1_s:int [line 33]\n *&return:int =(n$5 - n$6) [line 33]\n REMOVE_TEMPS(n$5,n$6); [line 33]\n NULLIFY(&e,false); [line 33]\n APPLY_ABSTRACTION; [line 33]\n " shape="box"] + + + 8 -> 7 ; +7 [label="7: Exit __objc_anonymous_block_main1______1 \n " color=yellow style=filled] + + +6 [label="6: Start __objc_anonymous_block_main1______1\nFormals: e:int f:int \nLocals: \n DECLARE_LOCALS(&return); [line 32]\n NULLIFY(&f,false); [line 32]\n " color=yellow style=filled] + + + 6 -> 8 ; +5 [label="5: BinaryOperatorStmt: Assign \n n$3=*&addblock:_fn_ (*) [line 37]\n n$4=n$3(3:int ,2:int ) [line 37]\n *&add2:int =n$4 [line 37]\n REMOVE_TEMPS(n$3,n$4); [line 37]\n NULLIFY(&addblock,false); [line 37]\n " shape="box"] + + + 5 -> 4 ; +4 [label="4: BinaryOperatorStmt: Assign \n n$1=*&add1:int [line 40]\n n$2=*&add2:int [line 40]\n *&y:int =(n$1 / n$2) [line 40]\n REMOVE_TEMPS(n$1,n$2); [line 40]\n NULLIFY(&add1,false); [line 40]\n NULLIFY(&add2,false); [line 40]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n n$0=*&y:int [line 42]\n *&return:int =n$0 [line 42]\n REMOVE_TEMPS(n$0); [line 42]\n NULLIFY(&__objc_anonymous_block_main1______1,true); [line 42]\n NULLIFY(&__objc_anonymous_block_main1______2,true); [line 42]\n NULLIFY(&y,false); [line 42]\n APPLY_ABSTRACTION; [line 42]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main1 \n " color=yellow style=filled] + + +1 [label="1: Start main1\nFormals: y:int \nLocals: x:int add1:int add2:int addblock:_fn_ (*) \n DECLARE_LOCALS(&return,&x,&add1,&add2,&addblock); [line 6]\n NULLIFY(&add1,false); [line 6]\n NULLIFY(&add2,false); [line 6]\n NULLIFY(&addblock,false); [line 6]\n NULLIFY(&y,false); [line 6]\n " color=yellow style=filled] + + + 1 -> 22 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/block/block.m b/infer/tests/codetoanalyze/objc/frontend/block/block.m new file mode 100644 index 000000000..f07a09662 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/block/block.m @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +int main1 (int y) { + + static int s = 3; + int x =7; + + int add1, add2; + int (^addblock)(int a, int b); + + + addblock=^(int c,int d){ + + int (^addblock2)(int a); + int add2; + int bla =3; + + addblock2=^(int z){ + return z+s+x+bla; + }; + + add2 = addblock2(1); + return c+add2+bla; + }; + + add1 = addblock(1,2); + + + addblock=^(int e,int f){ + return e-s; + }; + + + add2 = addblock(3,2); + + // Here we should get a division by zero + y=add1/add2; + + return y; +} + + +int main () { + return main1(4); +} + diff --git a/infer/tests/codetoanalyze/objc/frontend/block/block_no_args.dot b/infer/tests/codetoanalyze/objc/frontend/block/block_no_args.dot new file mode 100644 index 000000000..1fb102080 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/block/block_no_args.dot @@ -0,0 +1,40 @@ +digraph iCFG { +10 [label="10: BinaryOperatorStmt: Assign \n *&#GB$g:int =7 [line 19]\n " shape="box"] + + + 10 -> 9 ; +9 [label="9: DeclStmt \n *&z:int =3 [line 21]\n " shape="box"] + + + 9 -> 8 ; +8 [label="8: BinaryOperatorStmt: Assign \n DECLARE_LOCALS(&__objc_anonymous_block_My_manager_my_mehtod______1); [line 22]\n n$4=_fun___objc_alloc_no_fail(sizeof(class __objc_anonymous_block_My_manager_my_mehtod______1 ):class __objc_anonymous_block_My_manager_my_mehtod______1 *) [line 22]\n *&__objc_anonymous_block_My_manager_my_mehtod______1:class __objc_anonymous_block_My_manager_my_mehtod______1 =n$4 [line 22]\n n$5=*&z:int [line 22]\n *n$4.z:int =n$5 [line 22]\n n$2=*&z:int [line 22]\n *&b:_fn_ (*)=(_fun___objc_anonymous_block_My_manager_my_mehtod______1,n$2) [line 22]\n REMOVE_TEMPS(n$4,n$5,n$2); [line 22]\n " shape="box"] + + + 8 -> 4 ; +7 [label="7: BinaryOperatorStmt: Assign \n n$3=*&z:int [line 23]\n *&#GB$g:int =(n$3 + 3) [line 23]\n REMOVE_TEMPS(n$3); [line 23]\n APPLY_ABSTRACTION; [line 23]\n " shape="box"] + + + 7 -> 6 ; +6 [label="6: Exit __objc_anonymous_block_My_manager_my_mehtod______1 \n " color=yellow style=filled] + + +5 [label="5: Start __objc_anonymous_block_My_manager_my_mehtod______1\nFormals: z:int \nLocals: \nCaptured: z:int \n DECLARE_LOCALS(&return); [line 22]\n " color=yellow style=filled] + + + 5 -> 7 ; +4 [label="4: Call n$1 \n n$1=*&b:_fn_ (*) [line 25]\n n$1() [line 25]\n REMOVE_TEMPS(n$1); [line 25]\n NULLIFY(&b,false); [line 25]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n n$0=*&z:int [line 26]\n *&return:int =n$0 [line 26]\n REMOVE_TEMPS(n$0); [line 26]\n NULLIFY(&__objc_anonymous_block_My_manager_my_mehtod______1,true); [line 26]\n APPLY_ABSTRACTION; [line 26]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit My_manager_my_mehtod \n " color=yellow style=filled] + + +1 [label="1: Start My_manager_my_mehtod\nFormals: self:class My_manager *\nLocals: b:_fn_ (*) z:int \n DECLARE_LOCALS(&return,&b,&z); [line 17]\n NULLIFY(&b,false); [line 17]\n NULLIFY(&self,false); [line 17]\n " color=yellow style=filled] + + + 1 -> 10 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/block/block_no_args.m b/infer/tests/codetoanalyze/objc/frontend/block/block_no_args.m new file mode 100644 index 000000000..7baf20dc3 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/block/block_no_args.m @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +int g; + +@interface My_manager :NSObject +- (int) my_mehtod; + +@end + +@implementation My_manager + +- (int) my_mehtod +{ + g=7; + void (^b)(); + int z=3; + b=^( ){ + g=z+3; + }; + b(); + return z; +} + + + +@end diff --git a/infer/tests/codetoanalyze/objc/frontend/block/block_release.dot b/infer/tests/codetoanalyze/objc/frontend/block/block_release.dot new file mode 100644 index 000000000..82f01da13 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/block/block_release.dot @@ -0,0 +1,78 @@ +digraph iCFG { +19 [label="19: BinaryOperatorStmt: Assign \n *&#GB$g:int =7 [line 20]\n " shape="box"] + + + 19 -> 18 ; +18 [label="18: DeclStmt \n *&z:int =3 [line 22]\n " shape="box"] + + + 18 -> 17 ; +17 [label="17: DeclStmt \n n$12=_fun_CGBitmapContextCreate(0:void *,0:unsigned long ,0:unsigned long ,8:unsigned long ,0:unsigned long ,0:struct CGColorSpace *,0:enum CGBitmapInfo ) [line 23]\n *&context:struct CGContext *=n$12 [line 23]\n REMOVE_TEMPS(n$12); [line 23]\n " shape="box"] + + + 17 -> 16 ; +16 [label="16: DeclStmt \n n$10=*&context:struct CGContext * [line 24]\n n$11=_fun_CGBitmapContextCreateImage(n$10:struct CGContext *) [line 24]\n *&newImage:struct CGImage *=n$11 [line 24]\n REMOVE_TEMPS(n$10,n$11); [line 24]\n " shape="box"] + + + 16 -> 15 ; +15 [label="15: BinaryOperatorStmt: Assign \n DECLARE_LOCALS(&__objc_anonymous_block_My_manager_my_mehtod______1); [line 25]\n n$8=_fun___objc_alloc_no_fail(sizeof(class __objc_anonymous_block_My_manager_my_mehtod______1 ):class __objc_anonymous_block_My_manager_my_mehtod______1 *) [line 25]\n *&__objc_anonymous_block_My_manager_my_mehtod______1:class __objc_anonymous_block_My_manager_my_mehtod______1 =n$8 [line 25]\n n$9=*&newImage:struct CGImage * [line 25]\n *n$8.newImage:struct CGImage *=n$9 [line 25]\n n$5=*&newImage:struct CGImage * [line 25]\n *&b:_fn_ (*)=(_fun___objc_anonymous_block_My_manager_my_mehtod______1,n$5) [line 25]\n REMOVE_TEMPS(n$8,n$9,n$5); [line 25]\n " shape="box"] + + + 15 -> 8 ; +14 [label="14: Call _fun_CGImageRelease \n n$7=*&newImage:struct CGImage * [line 26]\n _fun_CGImageRelease(n$7:struct CGImage *) [line 26]\n REMOVE_TEMPS(n$7); [line 26]\n APPLY_ABSTRACTION; [line 26]\n " shape="box"] + + + 14 -> 11 ; +13 [label="13: Prune (false branch) \n n$6=*&newImage:struct CGImage * [line 26]\n PRUNE((n$6 == 0), false); [line 26]\n REMOVE_TEMPS(n$6); [line 26]\n APPLY_ABSTRACTION; [line 26]\n " shape="invhouse"] + + + 13 -> 11 ; +12 [label="12: Prune (true branch) \n n$6=*&newImage:struct CGImage * [line 26]\n PRUNE((n$6 != 0), true); [line 26]\n REMOVE_TEMPS(n$6); [line 26]\n " shape="invhouse"] + + + 12 -> 14 ; +11 [label="11: + \n " ] + + + 11 -> 10 ; +10 [label="10: Exit __objc_anonymous_block_My_manager_my_mehtod______1 \n " color=yellow style=filled] + + +9 [label="9: Start __objc_anonymous_block_My_manager_my_mehtod______1\nFormals: newImage:struct CGImage * a:int \nLocals: \nCaptured: newImage:struct CGImage * \n DECLARE_LOCALS(&return); [line 25]\n NULLIFY(&a,false); [line 25]\n " color=yellow style=filled] + + + 9 -> 12 ; + 9 -> 13 ; +8 [label="8: Call n$3 \n n$3=*&b:_fn_ (*) [line 28]\n n$4=*&z:int [line 28]\n n$3(n$4:int ) [line 28]\n REMOVE_TEMPS(n$3,n$4); [line 28]\n NULLIFY(&b,false); [line 28]\n " shape="box"] + + + 8 -> 5 ; + 8 -> 6 ; +7 [label="7: Call _fun_CGContextRelease \n n$2=*&context:struct CGContext * [line 29]\n _fun_CGContextRelease(n$2:struct CGContext *) [line 29]\n REMOVE_TEMPS(n$2); [line 29]\n NULLIFY(&context,false); [line 29]\n APPLY_ABSTRACTION; [line 29]\n " shape="box"] + + + 7 -> 4 ; +6 [label="6: Prune (false branch) \n n$1=*&context:struct CGContext * [line 29]\n PRUNE((n$1 == 0), false); [line 29]\n REMOVE_TEMPS(n$1); [line 29]\n APPLY_ABSTRACTION; [line 29]\n " shape="invhouse"] + + + 6 -> 4 ; +5 [label="5: Prune (true branch) \n n$1=*&context:struct CGContext * [line 29]\n PRUNE((n$1 != 0), true); [line 29]\n REMOVE_TEMPS(n$1); [line 29]\n " shape="invhouse"] + + + 5 -> 7 ; +4 [label="4: + \n " ] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n NULLIFY(&context,false); [line 30]\n n$0=*&z:int [line 30]\n *&return:int =n$0 [line 30]\n REMOVE_TEMPS(n$0); [line 30]\n NULLIFY(&__objc_anonymous_block_My_manager_my_mehtod______1,true); [line 30]\n NULLIFY(&z,false); [line 30]\n APPLY_ABSTRACTION; [line 30]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit My_manager_my_mehtod \n " color=yellow style=filled] + + +1 [label="1: Start My_manager_my_mehtod\nFormals: self:class My_manager *\nLocals: b:_fn_ (*) z:int context:struct CGContext * newImage:struct CGImage * \n DECLARE_LOCALS(&return,&b,&z,&context,&newImage); [line 18]\n NULLIFY(&b,false); [line 18]\n NULLIFY(&context,false); [line 18]\n NULLIFY(&self,false); [line 18]\n NULLIFY(&z,false); [line 18]\n " color=yellow style=filled] + + + 1 -> 19 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/block/block_release.m b/infer/tests/codetoanalyze/objc/frontend/block/block_release.m new file mode 100644 index 000000000..8b6f34c67 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/block/block_release.m @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import +#import + +int g; + +@interface My_manager :NSObject +- (int) my_mehtod; + +@end + +@implementation My_manager + +- (int) my_mehtod +{ + g=7; + void (^b)(int a); + int z=3; + CGContextRef context = CGBitmapContextCreate(NULL,0, 0, 8, 0, 0, 0); + CGImageRef newImage = CGBitmapContextCreateImage(context); + b=^(int a){ + if (newImage) CGImageRelease(newImage); + }; + b(z); + if (context) CGContextRelease(context); + return z; +} + + + +@end diff --git a/infer/tests/codetoanalyze/objc/frontend/block/dispatch.dot b/infer/tests/codetoanalyze/objc/frontend/block/dispatch.dot new file mode 100644 index 000000000..974252913 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/block/dispatch.dot @@ -0,0 +1,103 @@ +digraph iCFG { +27 [label="27: DeclStmt \n n$18=_fun_A_sharedInstance() [line 46]\n *&b:class A *=n$18 [line 46]\n REMOVE_TEMPS(n$18); [line 46]\n " shape="box"] + + + 27 -> 26 ; +26 [label="26: Message Call: setX: \n n$17=*&b:class A * [line 47]\n _fun_A_setX:(n$17:class A *,17:int ) virtual [line 47]\n REMOVE_TEMPS(n$17); [line 47]\n NULLIFY(&b,false); [line 47]\n " shape="box"] + + + 26 -> 25 ; +25 [label="25: Return Stmt \n *&return:int =0 [line 48]\n APPLY_ABSTRACTION; [line 48]\n " shape="box"] + + + 25 -> 24 ; +24 [label="24: Exit main \n " color=yellow style=filled] + + +23 [label="23: Start main\nFormals: \nLocals: b:class A * \n DECLARE_LOCALS(&return,&b); [line 45]\n NULLIFY(&b,false); [line 45]\n " color=yellow style=filled] + + + 23 -> 27 ; +22 [label="22: BinaryOperatorStmt: Assign \n n$15=*&self:class A * [line -1]\n n$16=*&x:int [line -1]\n *n$15._x:int =n$16 [line -1]\n REMOVE_TEMPS(n$15,n$16); [line -1]\n NULLIFY(&self,false); [line -1]\n NULLIFY(&x,false); [line -1]\n APPLY_ABSTRACTION; [line -1]\n " shape="box"] + + + 22 -> 21 ; +21 [label="21: Exit A_setX: \n " color=yellow style=filled] + + +20 [label="20: Start A_setX:\nFormals: self:class A * x:int \nLocals: \n DECLARE_LOCALS(&return); [line -1]\n " color=yellow style=filled] + + + 20 -> 22 ; +19 [label="19: Return Stmt \n n$13=*&self:class A * [line -1]\n n$14=*n$13._x:int [line -1]\n *&return:int =n$14 [line -1]\n REMOVE_TEMPS(n$13,n$14); [line -1]\n NULLIFY(&self,false); [line -1]\n APPLY_ABSTRACTION; [line -1]\n " shape="box"] + + + 19 -> 18 ; +18 [label="18: Exit A_x \n " color=yellow style=filled] + + +17 [label="17: Start A_x\nFormals: self:class A *\nLocals: \n DECLARE_LOCALS(&return); [line -1]\n " color=yellow style=filled] + + + 17 -> 19 ; +16 [label="16: BinaryOperatorStmt: Assign \n n$11=_fun___objc_alloc_no_fail(sizeof(class A ):class A *) [line 37]\n n$9=_fun_A_init(n$11:class A *) virtual [line 37]\n *&#GB$A_trans_SI_sharedInstance:struct objc_object *=n$9 [line 37]\n REMOVE_TEMPS(n$9,n$11); [line 37]\n APPLY_ABSTRACTION; [line 37]\n " shape="box"] + + + 16 -> 15 ; +15 [label="15: Exit __objc_anonymous_block_A_trans_SI______2 \n " color=yellow style=filled] + + +14 [label="14: Start __objc_anonymous_block_A_trans_SI______2\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 36]\n " color=yellow style=filled] + + + 14 -> 16 ; +13 [label="13: DeclStmt \n DECLARE_LOCALS(&__objc_anonymous_block_A_trans_SI______2); [line 36]\n n$12=_fun___objc_alloc_no_fail(sizeof(class __objc_anonymous_block_A_trans_SI______2 ):class __objc_anonymous_block_A_trans_SI______2 *) [line 36]\n *&__objc_anonymous_block_A_trans_SI______2:class __objc_anonymous_block_A_trans_SI______2 =n$12 [line 36]\n *&dummy_block:_fn_ (*)=(_fun___objc_anonymous_block_A_trans_SI______2) [line 36]\n REMOVE_TEMPS(n$12); [line 36]\n " shape="box"] + + + 13 -> 12 ; +12 [label="12: Call n$8 \n n$8=*&dummy_block:_fn_ (*) [line 39]\n n$8() [line 39]\n REMOVE_TEMPS(n$8); [line 39]\n NULLIFY(&dummy_block,false); [line 39]\n " shape="box"] + + + 12 -> 11 ; +11 [label="11: Return Stmt \n n$7=*&#GB$A_trans_SI_sharedInstance:struct objc_object * [line 40]\n *&return:struct objc_object *=n$7 [line 40]\n REMOVE_TEMPS(n$7); [line 40]\n NULLIFY(&__objc_anonymous_block_A_trans_SI______2,true); [line 40]\n APPLY_ABSTRACTION; [line 40]\n " shape="box"] + + + 11 -> 10 ; +10 [label="10: Exit A_trans_SI \n " color=yellow style=filled] + + +9 [label="9: Start A_trans_SI\nFormals: \nLocals: dummy_block:_fn_ (*) \n DECLARE_LOCALS(&return,&dummy_block); [line 32]\n NULLIFY(&dummy_block,false); [line 32]\n " color=yellow style=filled] + + + 9 -> 13 ; +8 [label="8: BinaryOperatorStmt: Assign \n n$5=_fun___objc_alloc_no_fail(sizeof(class A ):class A *) [line 25]\n n$3=_fun_A_init(n$5:class A *) virtual [line 25]\n *&#GB$A_sharedInstance_sharedInstance:struct objc_object *=n$3 [line 25]\n REMOVE_TEMPS(n$3,n$5); [line 25]\n APPLY_ABSTRACTION; [line 25]\n " shape="box"] + + + 8 -> 7 ; +7 [label="7: Exit __objc_anonymous_block_A_sharedInstance______1 \n " color=yellow style=filled] + + +6 [label="6: Start __objc_anonymous_block_A_sharedInstance______1\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 24]\n " color=yellow style=filled] + + + 6 -> 8 ; +5 [label="5: DeclStmt \n DECLARE_LOCALS(&infer___objc_anonymous_block_A_sharedInstance______1); [line 26]\n DECLARE_LOCALS(&__objc_anonymous_block_A_sharedInstance______1); [line 24]\n n$6=_fun___objc_alloc_no_fail(sizeof(class __objc_anonymous_block_A_sharedInstance______1 ):class __objc_anonymous_block_A_sharedInstance______1 *) [line 24]\n *&__objc_anonymous_block_A_sharedInstance______1:class __objc_anonymous_block_A_sharedInstance______1 =n$6 [line 24]\n *&infer___objc_anonymous_block_A_sharedInstance______1:_fn_ (*)=(_fun___objc_anonymous_block_A_sharedInstance______1) [line 26]\n REMOVE_TEMPS(n$6); [line 26]\n " shape="box"] + + + 5 -> 4 ; +4 [label="4: Call n$1 \n n$1=*&infer___objc_anonymous_block_A_sharedInstance______1:_fn_ (*) [line 26]\n n$2=n$1() [line 26]\n REMOVE_TEMPS(n$1,n$2); [line 26]\n NULLIFY(&infer___objc_anonymous_block_A_sharedInstance______1,true); [line 26]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n n$0=*&#GB$A_sharedInstance_sharedInstance:struct objc_object * [line 28]\n *&return:struct objc_object *=n$0 [line 28]\n REMOVE_TEMPS(n$0); [line 28]\n NULLIFY(&__objc_anonymous_block_A_sharedInstance______1,true); [line 28]\n APPLY_ABSTRACTION; [line 28]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit A_sharedInstance \n " color=yellow style=filled] + + +1 [label="1: Start A_sharedInstance\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 19]\n " color=yellow style=filled] + + + 1 -> 5 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/block/dispatch.m b/infer/tests/codetoanalyze/objc/frontend/block/dispatch.m new file mode 100644 index 000000000..5ac648d16 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/block/dispatch.m @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface A : NSObject + +@property int x; + ++ (instancetype)sharedInstance; + +@end + +@implementation A { +} + ++ (instancetype)sharedInstance +{ + static dispatch_once_t once; + static id sharedInstance; + dispatch_once(&once, + ^{ + sharedInstance = [[self alloc] init]; + }); + + return sharedInstance; +} + + ++ (instancetype)trans_SI +{ + static id sharedInstance; + + void (^dummy_block)()=^{ + sharedInstance = [[self alloc] init]; + }; + dummy_block(); + return sharedInstance; + +} +@end + +int main () { + A *b =[A sharedInstance]; + b.x=17; + return 0; +} + + + + + + diff --git a/infer/tests/codetoanalyze/objc/frontend/block/dispatch_examples.dot b/infer/tests/codetoanalyze/objc/frontend/block/dispatch_examples.dot new file mode 100644 index 000000000..e8876eebd --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/block/dispatch_examples.dot @@ -0,0 +1,230 @@ +digraph iCFG { +60 [label="60: DeclStmt \n *&#GB$A_dispatch_barrier_example_a:class A *=0 [line 65]\n " shape="box"] + + + 60 -> 55 ; +59 [label="59: BinaryOperatorStmt: Assign \n n$52=_fun___objc_alloc_no_fail(sizeof(class A ):class A *) [line 67]\n n$50=_fun_A_init(n$52:class A *) virtual [line 67]\n *&#GB$A_dispatch_barrier_example_a:class A *=n$50 [line 67]\n REMOVE_TEMPS(n$50,n$52); [line 67]\n " shape="box"] + + + 59 -> 58 ; +58 [label="58: BinaryOperatorStmt: Assign \n n$49=*&#GB$A_dispatch_barrier_example_a:class A * [line 68]\n *n$49.x:int =10 [line 68]\n REMOVE_TEMPS(n$49); [line 68]\n APPLY_ABSTRACTION; [line 68]\n " shape="box"] + + + 58 -> 57 ; +57 [label="57: Exit __objc_anonymous_block_A_dispatch_barrier_example______6 \n " color=yellow style=filled] + + +56 [label="56: Start __objc_anonymous_block_A_dispatch_barrier_example______6\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 66]\n " color=yellow style=filled] + + + 56 -> 59 ; +55 [label="55: DeclStmt \n DECLARE_LOCALS(&infer___objc_anonymous_block_A_dispatch_barrier_example______6); [line 66]\n DECLARE_LOCALS(&__objc_anonymous_block_A_dispatch_barrier_example______6); [line 66]\n n$53=_fun___objc_alloc_no_fail(sizeof(class __objc_anonymous_block_A_dispatch_barrier_example______6 ):class __objc_anonymous_block_A_dispatch_barrier_example______6 *) [line 66]\n *&__objc_anonymous_block_A_dispatch_barrier_example______6:class __objc_anonymous_block_A_dispatch_barrier_example______6 =n$53 [line 66]\n *&infer___objc_anonymous_block_A_dispatch_barrier_example______6:_fn_ (*)=(_fun___objc_anonymous_block_A_dispatch_barrier_example______6) [line 66]\n REMOVE_TEMPS(n$53); [line 66]\n " shape="box"] + + + 55 -> 54 ; +54 [label="54: Call n$47 \n n$47=*&infer___objc_anonymous_block_A_dispatch_barrier_example______6:_fn_ (*) [line 66]\n n$48=n$47() [line 66]\n REMOVE_TEMPS(n$47,n$48); [line 66]\n NULLIFY(&infer___objc_anonymous_block_A_dispatch_barrier_example______6,true); [line 66]\n " shape="box"] + + + 54 -> 53 ; +53 [label="53: Return Stmt \n n$45=*&#GB$A_dispatch_barrier_example_a:class A * [line 70]\n n$46=*n$45.x:int [line 70]\n *&return:int =n$46 [line 70]\n REMOVE_TEMPS(n$45,n$46); [line 70]\n NULLIFY(&__objc_anonymous_block_A_dispatch_barrier_example______6,true); [line 70]\n APPLY_ABSTRACTION; [line 70]\n " shape="box"] + + + 53 -> 52 ; +52 [label="52: Exit A_dispatch_barrier_example \n " color=yellow style=filled] + + +51 [label="51: Start A_dispatch_barrier_example\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 64]\n " color=yellow style=filled] + + + 51 -> 60 ; +50 [label="50: DeclStmt \n *&#GB$A_dispatch_group_notify_example_a:class A *=0 [line 56]\n " shape="box"] + + + 50 -> 45 ; +49 [label="49: BinaryOperatorStmt: Assign \n n$43=_fun___objc_alloc_no_fail(sizeof(class A ):class A *) [line 58]\n n$41=_fun_A_init(n$43:class A *) virtual [line 58]\n *&#GB$A_dispatch_group_notify_example_a:class A *=n$41 [line 58]\n REMOVE_TEMPS(n$41,n$43); [line 58]\n " shape="box"] + + + 49 -> 48 ; +48 [label="48: BinaryOperatorStmt: Assign \n n$40=*&#GB$A_dispatch_group_notify_example_a:class A * [line 59]\n *n$40.x:int =10 [line 59]\n REMOVE_TEMPS(n$40); [line 59]\n APPLY_ABSTRACTION; [line 59]\n " shape="box"] + + + 48 -> 47 ; +47 [label="47: Exit __objc_anonymous_block_A_dispatch_group_notify_example______5 \n " color=yellow style=filled] + + +46 [label="46: Start __objc_anonymous_block_A_dispatch_group_notify_example______5\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 57]\n " color=yellow style=filled] + + + 46 -> 49 ; +45 [label="45: DeclStmt \n DECLARE_LOCALS(&infer___objc_anonymous_block_A_dispatch_group_notify_example______5); [line 57]\n DECLARE_LOCALS(&__objc_anonymous_block_A_dispatch_group_notify_example______5); [line 57]\n n$44=_fun___objc_alloc_no_fail(sizeof(class __objc_anonymous_block_A_dispatch_group_notify_example______5 ):class __objc_anonymous_block_A_dispatch_group_notify_example______5 *) [line 57]\n *&__objc_anonymous_block_A_dispatch_group_notify_example______5:class __objc_anonymous_block_A_dispatch_group_notify_example______5 =n$44 [line 57]\n *&infer___objc_anonymous_block_A_dispatch_group_notify_example______5:_fn_ (*)=(_fun___objc_anonymous_block_A_dispatch_group_notify_example______5) [line 57]\n REMOVE_TEMPS(n$44); [line 57]\n " shape="box"] + + + 45 -> 44 ; +44 [label="44: Call n$38 \n n$38=*&infer___objc_anonymous_block_A_dispatch_group_notify_example______5:_fn_ (*) [line 57]\n n$39=n$38() [line 57]\n REMOVE_TEMPS(n$38,n$39); [line 57]\n NULLIFY(&infer___objc_anonymous_block_A_dispatch_group_notify_example______5,true); [line 57]\n " shape="box"] + + + 44 -> 43 ; +43 [label="43: Return Stmt \n n$36=*&#GB$A_dispatch_group_notify_example_a:class A * [line 61]\n n$37=*n$36.x:int [line 61]\n *&return:int =n$37 [line 61]\n REMOVE_TEMPS(n$36,n$37); [line 61]\n NULLIFY(&__objc_anonymous_block_A_dispatch_group_notify_example______5,true); [line 61]\n APPLY_ABSTRACTION; [line 61]\n " shape="box"] + + + 43 -> 42 ; +42 [label="42: Exit A_dispatch_group_notify_example \n " color=yellow style=filled] + + +41 [label="41: Start A_dispatch_group_notify_example\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 55]\n " color=yellow style=filled] + + + 41 -> 50 ; +40 [label="40: DeclStmt \n *&#GB$A_dispatch_group_example_a:class A *=0 [line 47]\n " shape="box"] + + + 40 -> 35 ; +39 [label="39: BinaryOperatorStmt: Assign \n n$34=_fun___objc_alloc_no_fail(sizeof(class A ):class A *) [line 49]\n n$32=_fun_A_init(n$34:class A *) virtual [line 49]\n *&#GB$A_dispatch_group_example_a:class A *=n$32 [line 49]\n REMOVE_TEMPS(n$32,n$34); [line 49]\n " shape="box"] + + + 39 -> 38 ; +38 [label="38: BinaryOperatorStmt: Assign \n n$31=*&#GB$A_dispatch_group_example_a:class A * [line 50]\n *n$31.x:int =10 [line 50]\n REMOVE_TEMPS(n$31); [line 50]\n APPLY_ABSTRACTION; [line 50]\n " shape="box"] + + + 38 -> 37 ; +37 [label="37: Exit __objc_anonymous_block_A_dispatch_group_example______4 \n " color=yellow style=filled] + + +36 [label="36: Start __objc_anonymous_block_A_dispatch_group_example______4\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 48]\n " color=yellow style=filled] + + + 36 -> 39 ; +35 [label="35: DeclStmt \n DECLARE_LOCALS(&infer___objc_anonymous_block_A_dispatch_group_example______4); [line 48]\n DECLARE_LOCALS(&__objc_anonymous_block_A_dispatch_group_example______4); [line 48]\n n$35=_fun___objc_alloc_no_fail(sizeof(class __objc_anonymous_block_A_dispatch_group_example______4 ):class __objc_anonymous_block_A_dispatch_group_example______4 *) [line 48]\n *&__objc_anonymous_block_A_dispatch_group_example______4:class __objc_anonymous_block_A_dispatch_group_example______4 =n$35 [line 48]\n *&infer___objc_anonymous_block_A_dispatch_group_example______4:_fn_ (*)=(_fun___objc_anonymous_block_A_dispatch_group_example______4) [line 48]\n REMOVE_TEMPS(n$35); [line 48]\n " shape="box"] + + + 35 -> 34 ; +34 [label="34: Call n$29 \n n$29=*&infer___objc_anonymous_block_A_dispatch_group_example______4:_fn_ (*) [line 48]\n n$30=n$29() [line 48]\n REMOVE_TEMPS(n$29,n$30); [line 48]\n NULLIFY(&infer___objc_anonymous_block_A_dispatch_group_example______4,true); [line 48]\n " shape="box"] + + + 34 -> 33 ; +33 [label="33: Return Stmt \n n$27=*&#GB$A_dispatch_group_example_a:class A * [line 52]\n n$28=*n$27.x:int [line 52]\n *&return:int =n$28 [line 52]\n REMOVE_TEMPS(n$27,n$28); [line 52]\n NULLIFY(&__objc_anonymous_block_A_dispatch_group_example______4,true); [line 52]\n APPLY_ABSTRACTION; [line 52]\n " shape="box"] + + + 33 -> 32 ; +32 [label="32: Exit A_dispatch_group_example \n " color=yellow style=filled] + + +31 [label="31: Start A_dispatch_group_example\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 46]\n " color=yellow style=filled] + + + 31 -> 40 ; +30 [label="30: DeclStmt \n *&#GB$A_dispatch_after_example_a:class A *=0 [line 37]\n " shape="box"] + + + 30 -> 25 ; +29 [label="29: BinaryOperatorStmt: Assign \n n$25=_fun___objc_alloc_no_fail(sizeof(class A ):class A *) [line 40]\n n$23=_fun_A_init(n$25:class A *) virtual [line 40]\n *&#GB$A_dispatch_after_example_a:class A *=n$23 [line 40]\n REMOVE_TEMPS(n$23,n$25); [line 40]\n " shape="box"] + + + 29 -> 28 ; +28 [label="28: BinaryOperatorStmt: Assign \n n$22=*&#GB$A_dispatch_after_example_a:class A * [line 41]\n *n$22.x:int =10 [line 41]\n REMOVE_TEMPS(n$22); [line 41]\n APPLY_ABSTRACTION; [line 41]\n " shape="box"] + + + 28 -> 27 ; +27 [label="27: Exit __objc_anonymous_block_A_dispatch_after_example______3 \n " color=yellow style=filled] + + +26 [label="26: Start __objc_anonymous_block_A_dispatch_after_example______3\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 39]\n " color=yellow style=filled] + + + 26 -> 29 ; +25 [label="25: DeclStmt \n DECLARE_LOCALS(&infer___objc_anonymous_block_A_dispatch_after_example______3); [line 38]\n DECLARE_LOCALS(&__objc_anonymous_block_A_dispatch_after_example______3); [line 38]\n n$26=_fun___objc_alloc_no_fail(sizeof(class __objc_anonymous_block_A_dispatch_after_example______3 ):class __objc_anonymous_block_A_dispatch_after_example______3 *) [line 38]\n *&__objc_anonymous_block_A_dispatch_after_example______3:class __objc_anonymous_block_A_dispatch_after_example______3 =n$26 [line 38]\n *&infer___objc_anonymous_block_A_dispatch_after_example______3:_fn_ (*)=(_fun___objc_anonymous_block_A_dispatch_after_example______3) [line 38]\n REMOVE_TEMPS(n$26); [line 38]\n " shape="box"] + + + 25 -> 24 ; +24 [label="24: Call n$20 \n n$20=*&infer___objc_anonymous_block_A_dispatch_after_example______3:_fn_ (*) [line 38]\n n$21=n$20() [line 38]\n REMOVE_TEMPS(n$20,n$21); [line 38]\n NULLIFY(&infer___objc_anonymous_block_A_dispatch_after_example______3,true); [line 38]\n " shape="box"] + + + 24 -> 23 ; +23 [label="23: Return Stmt \n n$18=*&#GB$A_dispatch_after_example_a:class A * [line 43]\n n$19=*n$18.x:int [line 43]\n *&return:int =n$19 [line 43]\n REMOVE_TEMPS(n$18,n$19); [line 43]\n NULLIFY(&__objc_anonymous_block_A_dispatch_after_example______3,true); [line 43]\n APPLY_ABSTRACTION; [line 43]\n " shape="box"] + + + 23 -> 22 ; +22 [label="22: Exit A_dispatch_after_example \n " color=yellow style=filled] + + +21 [label="21: Start A_dispatch_after_example\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 36]\n " color=yellow style=filled] + + + 21 -> 30 ; +20 [label="20: DeclStmt \n *&#GB$A_dispatch_async_example_a:class A *=0 [line 28]\n " shape="box"] + + + 20 -> 15 ; +19 [label="19: BinaryOperatorStmt: Assign \n n$16=_fun___objc_alloc_no_fail(sizeof(class A ):class A *) [line 30]\n n$14=_fun_A_init(n$16:class A *) virtual [line 30]\n *&#GB$A_dispatch_async_example_a:class A *=n$14 [line 30]\n REMOVE_TEMPS(n$14,n$16); [line 30]\n " shape="box"] + + + 19 -> 18 ; +18 [label="18: BinaryOperatorStmt: Assign \n n$13=*&#GB$A_dispatch_async_example_a:class A * [line 31]\n *n$13.x:int =10 [line 31]\n REMOVE_TEMPS(n$13); [line 31]\n APPLY_ABSTRACTION; [line 31]\n " shape="box"] + + + 18 -> 17 ; +17 [label="17: Exit __objc_anonymous_block_A_dispatch_async_example______2 \n " color=yellow style=filled] + + +16 [label="16: Start __objc_anonymous_block_A_dispatch_async_example______2\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 29]\n " color=yellow style=filled] + + + 16 -> 19 ; +15 [label="15: DeclStmt \n DECLARE_LOCALS(&infer___objc_anonymous_block_A_dispatch_async_example______2); [line 29]\n DECLARE_LOCALS(&__objc_anonymous_block_A_dispatch_async_example______2); [line 29]\n n$17=_fun___objc_alloc_no_fail(sizeof(class __objc_anonymous_block_A_dispatch_async_example______2 ):class __objc_anonymous_block_A_dispatch_async_example______2 *) [line 29]\n *&__objc_anonymous_block_A_dispatch_async_example______2:class __objc_anonymous_block_A_dispatch_async_example______2 =n$17 [line 29]\n *&infer___objc_anonymous_block_A_dispatch_async_example______2:_fn_ (*)=(_fun___objc_anonymous_block_A_dispatch_async_example______2) [line 29]\n REMOVE_TEMPS(n$17); [line 29]\n " shape="box"] + + + 15 -> 14 ; +14 [label="14: Call n$11 \n n$11=*&infer___objc_anonymous_block_A_dispatch_async_example______2:_fn_ (*) [line 29]\n n$12=n$11() [line 29]\n REMOVE_TEMPS(n$11,n$12); [line 29]\n NULLIFY(&infer___objc_anonymous_block_A_dispatch_async_example______2,true); [line 29]\n " shape="box"] + + + 14 -> 13 ; +13 [label="13: Return Stmt \n n$9=*&#GB$A_dispatch_async_example_a:class A * [line 33]\n n$10=*n$9.x:int [line 33]\n *&return:int =n$10 [line 33]\n REMOVE_TEMPS(n$9,n$10); [line 33]\n NULLIFY(&__objc_anonymous_block_A_dispatch_async_example______2,true); [line 33]\n APPLY_ABSTRACTION; [line 33]\n " shape="box"] + + + 13 -> 12 ; +12 [label="12: Exit A_dispatch_async_example \n " color=yellow style=filled] + + +11 [label="11: Start A_dispatch_async_example\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 27]\n " color=yellow style=filled] + + + 11 -> 20 ; +10 [label="10: DeclStmt \n *&#GB$A_dispatch_once_example_a:class A *=0 [line 17]\n " shape="box"] + + + 10 -> 5 ; +9 [label="9: BinaryOperatorStmt: Assign \n n$7=_fun___objc_alloc_no_fail(sizeof(class A ):class A *) [line 21]\n n$5=_fun_A_init(n$7:class A *) virtual [line 21]\n *&#GB$A_dispatch_once_example_a:class A *=n$5 [line 21]\n REMOVE_TEMPS(n$5,n$7); [line 21]\n " shape="box"] + + + 9 -> 8 ; +8 [label="8: BinaryOperatorStmt: Assign \n n$4=*&#GB$A_dispatch_once_example_a:class A * [line 22]\n *n$4.x:int =10 [line 22]\n REMOVE_TEMPS(n$4); [line 22]\n APPLY_ABSTRACTION; [line 22]\n " shape="box"] + + + 8 -> 7 ; +7 [label="7: Exit __objc_anonymous_block_A_dispatch_once_example______1 \n " color=yellow style=filled] + + +6 [label="6: Start __objc_anonymous_block_A_dispatch_once_example______1\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 20]\n " color=yellow style=filled] + + + 6 -> 9 ; +5 [label="5: DeclStmt \n DECLARE_LOCALS(&infer___objc_anonymous_block_A_dispatch_once_example______1); [line 23]\n DECLARE_LOCALS(&__objc_anonymous_block_A_dispatch_once_example______1); [line 23]\n n$8=_fun___objc_alloc_no_fail(sizeof(class __objc_anonymous_block_A_dispatch_once_example______1 ):class __objc_anonymous_block_A_dispatch_once_example______1 *) [line 23]\n *&__objc_anonymous_block_A_dispatch_once_example______1:class __objc_anonymous_block_A_dispatch_once_example______1 =n$8 [line 23]\n *&infer___objc_anonymous_block_A_dispatch_once_example______1:_fn_ (*)=(_fun___objc_anonymous_block_A_dispatch_once_example______1) [line 23]\n REMOVE_TEMPS(n$8); [line 23]\n " shape="box"] + + + 5 -> 4 ; +4 [label="4: Call n$2 \n n$2=*&infer___objc_anonymous_block_A_dispatch_once_example______1:_fn_ (*) [line 23]\n n$3=n$2() [line 23]\n REMOVE_TEMPS(n$2,n$3); [line 23]\n NULLIFY(&infer___objc_anonymous_block_A_dispatch_once_example______1,true); [line 23]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n n$0=*&#GB$A_dispatch_once_example_a:class A * [line 24]\n n$1=*n$0.x:int [line 24]\n *&return:int =n$1 [line 24]\n REMOVE_TEMPS(n$0,n$1); [line 24]\n NULLIFY(&__objc_anonymous_block_A_dispatch_once_example______1,true); [line 24]\n APPLY_ABSTRACTION; [line 24]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit A_dispatch_once_example \n " color=yellow style=filled] + + +1 [label="1: Start A_dispatch_once_example\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 16]\n " color=yellow style=filled] + + + 1 -> 10 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/block/dispatch_examples.m b/infer/tests/codetoanalyze/objc/frontend/block/dispatch_examples.m new file mode 100644 index 000000000..f278c4f40 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/block/dispatch_examples.m @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface A : NSObject { + int x; +} + +@end + +@implementation A + ++ (int)dispatch_once_example +{ static A* a = nil; + //dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + a = [[A alloc] init]; + a->x = 10; + }); + return a->x; +} + ++ (int)dispatch_async_example +{ static A* a = nil; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{ + a = [[A alloc] init]; + a->x = 10; + }); + return a->x; +} + ++ (int)dispatch_after_example +{ static A* a = nil; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + a = [[A alloc] init]; + a->x = 10; + }); + return a->x; +} + ++ (int)dispatch_group_example +{ static A* a = nil; + dispatch_group_async(NULL, dispatch_get_main_queue(), ^{ + a = [[A alloc] init]; + a->x = 10; + }); + return a->x; +} + ++ (int)dispatch_group_notify_example +{ static A* a = nil; + dispatch_group_async(NULL, dispatch_get_main_queue(), ^{ + a = [[A alloc] init]; + a->x = 10; + }); + return a->x; +} + ++ (int)dispatch_barrier_example +{ static A* a = nil; + dispatch_barrier_async(dispatch_get_main_queue(), ^{ + a = [[A alloc] init]; + a->x = 10; + }); + return a->x; +} + +@end diff --git a/infer/tests/codetoanalyze/objc/frontend/block/main.m b/infer/tests/codetoanalyze/objc/frontend/block/main.m new file mode 100644 index 000000000..b37efe793 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/block/main.m @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import "BlockVar.m" + +int main(int argc, const char * argv[]) +{ + int res = [BlockVar navigateToURLInBackground:nil resolver:nil]; + NSLog(@"Hello, World! The result is %d" , res); + if (res == 8) { + return 1/0; + } + return 0; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/block/retain_cycle.dot b/infer/tests/codetoanalyze/objc/frontend/block/retain_cycle.dot new file mode 100644 index 000000000..a4febf496 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/block/retain_cycle.dot @@ -0,0 +1,73 @@ +digraph iCFG { +19 [label="19: DeclStmt \n n$18=_fun___objc_alloc_no_fail(sizeof(class A ):class A *) [line 64]\n *&a:class A *=n$18 [line 64]\n REMOVE_TEMPS(n$18); [line 64]\n " shape="box"] + + + 19 -> 18 ; +18 [label="18: BinaryOperatorStmt: Assign \n n$15=*&a:class A * [line 66]\n n$16=_fun_foo(n$15:class A *) [line 66]\n *&a:class A *=n$16 [line 66]\n REMOVE_TEMPS(n$15,n$16); [line 66]\n NULLIFY(&a,false); [line 66]\n " shape="box"] + + + 18 -> 17 ; +17 [label="17: Return Stmt \n *&return:int =0 [line 68]\n APPLY_ABSTRACTION; [line 68]\n " shape="box"] + + + 17 -> 16 ; +16 [label="16: Exit main \n " color=yellow style=filled] + + +15 [label="15: Start main\nFormals: argc:int argv:char **\nLocals: a:class A * \n DECLARE_LOCALS(&return,&a); [line 62]\n NULLIFY(&a,false); [line 62]\n NULLIFY(&argc,false); [line 62]\n NULLIFY(&argv,false); [line 62]\n " color=yellow style=filled] + + + 15 -> 19 ; +14 [label="14: Message Call: capture \n n$14=*&a:class A * [line 56]\n _fun_A_capture(n$14:class A *) virtual [line 56]\n REMOVE_TEMPS(n$14); [line 56]\n " shape="box"] + + + 14 -> 13 ; +13 [label="13: Return Stmt \n n$13=*&a:class A * [line 58]\n *&return:class A *=n$13 [line 58]\n REMOVE_TEMPS(n$13); [line 58]\n NULLIFY(&a,false); [line 58]\n APPLY_ABSTRACTION; [line 58]\n " shape="box"] + + + 13 -> 12 ; +12 [label="12: Exit foo \n " color=yellow style=filled] + + +11 [label="11: Start foo\nFormals: a:class A *\nLocals: \n DECLARE_LOCALS(&return); [line 54]\n " color=yellow style=filled] + + + 11 -> 14 ; +10 [label="10: BinaryOperatorStmt: Assign \n n$10=*&self:class A * [line 46]\n n$12=_fun___objc_alloc_no_fail(sizeof(class B ):class B *) [line 46]\n *n$10._b:class B *=n$12 [line 46]\n REMOVE_TEMPS(n$10,n$12); [line 46]\n " shape="box"] + + + 10 -> 9 ; +9 [label="9: Message Call: sHandler: \n n$2=*&self:class A * [line 47]\n n$3=*n$2._b:class B * [line 47]\n DECLARE_LOCALS(&__objc_anonymous_block_A_capture______1); [line 47]\n n$7=_fun___objc_alloc_no_fail(sizeof(class __objc_anonymous_block_A_capture______1 ):class __objc_anonymous_block_A_capture______1 *) [line 47]\n *&__objc_anonymous_block_A_capture______1:class __objc_anonymous_block_A_capture______1 =n$7 [line 47]\n n$8=*&self:class A * [line 47]\n *n$7.self:class A *=n$8 [line 47]\n n$4=*&self:class A * [line 47]\n n$9=*&__objc_anonymous_block_A_capture______1:_fn_ (*) [line 47]\n _fun_B_sHandler:(n$3:class B *,n$9:_fn_ (*),n$4:_fn_ (*)) virtual [line 47]\n REMOVE_TEMPS(n$2,n$3,n$7,n$8,n$4,n$9); [line 47]\n NULLIFY(&__objc_anonymous_block_A_capture______1,true); [line 47]\n NULLIFY(&self,false); [line 47]\n APPLY_ABSTRACTION; [line 47]\n " shape="box"] + + + 9 -> 5 ; +8 [label="8: BinaryOperatorStmt: Assign \n n$5=*&self:class A * [line 48]\n n$6=*&d:class D * [line 48]\n *n$5._data:class D *=n$6 [line 48]\n REMOVE_TEMPS(n$5,n$6); [line 48]\n NULLIFY(&d,false); [line 48]\n APPLY_ABSTRACTION; [line 48]\n " shape="box"] + + + 8 -> 7 ; +7 [label="7: Exit __objc_anonymous_block_A_capture______1 \n " color=yellow style=filled] + + +6 [label="6: Start __objc_anonymous_block_A_capture______1\nFormals: self:class A * d:class D *\nLocals: \nCaptured: self:class A * \n DECLARE_LOCALS(&return); [line 47]\n " color=yellow style=filled] + + + 6 -> 8 ; +5 [label="5: Exit A_capture \n " color=yellow style=filled] + + +4 [label="4: Start A_capture\nFormals: self:class A *\nLocals: \n DECLARE_LOCALS(&return); [line 44]\n " color=yellow style=filled] + + + 4 -> 10 ; +3 [label="3: BinaryOperatorStmt: Assign \n n$0=*&self:class B * [line 28]\n n$1=*&h:_fn_ (*) [line 28]\n *n$0._h:_fn_ (*)=n$1 [line 28]\n REMOVE_TEMPS(n$0,n$1); [line 28]\n NULLIFY(&h,false); [line 28]\n NULLIFY(&self,false); [line 28]\n APPLY_ABSTRACTION; [line 28]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit B_sHandler: \n " color=yellow style=filled] + + +1 [label="1: Start B_sHandler:\nFormals: self:class B * h:_fn_ (*)\nLocals: \n DECLARE_LOCALS(&return); [line 26]\n " color=yellow style=filled] + + + 1 -> 3 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/block/retain_cycle.m b/infer/tests/codetoanalyze/objc/frontend/block/retain_cycle.m new file mode 100644 index 000000000..2ab64f291 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/block/retain_cycle.m @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2015 - Facebook. + * All rights reserved. + */ + +#import + +@interface D : NSObject + +@end + + +typedef void (^MyHandler)(D* data); + + +@interface B : NSObject + +- (void)sHandler: (MyHandler) h; +@end + +@implementation B { + D* _d; + MyHandler _h; +} + +- (void)sHandler:(MyHandler)h { + + self->_h=h; +} + +@end + + +@interface A : NSObject + +- (void)capture; +@end + +@implementation A { + B* _b; + D* _data; +} + +- (void)capture +{ + _b=[B alloc]; + [_b sHandler:^(D *d){ + _data=d; + }]; +} + +@end + +A* foo(A* a) { + + [a capture]; + + return a; +}; + + +int main(int argc, const char * argv[]) { + + A* a = [A alloc]; + + a=foo(a); + + return 0; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/block/static.dot b/infer/tests/codetoanalyze/objc/frontend/block/static.dot new file mode 100644 index 000000000..502b88856 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/block/static.dot @@ -0,0 +1,117 @@ +digraph iCFG { +31 [label="31: Return Stmt \n *&return:int =0 [line 69]\n APPLY_ABSTRACTION; [line 69]\n " shape="box"] + + + 31 -> 30 ; +30 [label="30: Exit main \n " color=yellow style=filled] + + +29 [label="29: Start main\nFormals: argc:int argv:char **\nLocals: \n DECLARE_LOCALS(&return); [line 67]\n NULLIFY(&argc,false); [line 67]\n NULLIFY(&argv,false); [line 67]\n " color=yellow style=filled] + + + 29 -> 31 ; +28 [label="28: Call (_fun___objc_anonymous_block_A_test3______4) \n DECLARE_LOCALS(&__objc_anonymous_block_A_test3______4); [line 55]\n n$17=_fun___objc_alloc_no_fail(sizeof(class __objc_anonymous_block_A_test3______4 ):class __objc_anonymous_block_A_test3______4 *) [line 55]\n *&__objc_anonymous_block_A_test3______4:class __objc_anonymous_block_A_test3______4 =n$17 [line 55]\n (_fun___objc_anonymous_block_A_test3______4)() [line 55]\n REMOVE_TEMPS(n$17); [line 55]\n " shape="box"] + + + 28 -> 24 ; +27 [label="27: UnaryOperator \n n$16=*&#GB$A_test3_i:int [line 57]\n *&#GB$A_test3_i:int =(n$16 + 1) [line 57]\n REMOVE_TEMPS(n$16); [line 57]\n APPLY_ABSTRACTION; [line 57]\n " shape="box"] + + + 27 -> 26 ; +26 [label="26: Exit __objc_anonymous_block_A_test3______4 \n " color=yellow style=filled] + + +25 [label="25: Start __objc_anonymous_block_A_test3______4\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 55]\n " color=yellow style=filled] + + + 25 -> 27 ; +24 [label="24: Return Stmt \n n$15=*&#GB$A_test3_i:int [line 60]\n *&return:int =n$15 [line 60]\n REMOVE_TEMPS(n$15); [line 60]\n NULLIFY(&__objc_anonymous_block_A_test3______4,true); [line 60]\n APPLY_ABSTRACTION; [line 60]\n " shape="box"] + + + 24 -> 23 ; +23 [label="23: Exit A_test3 \n " color=yellow style=filled] + + +22 [label="22: Start A_test3\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 51]\n " color=yellow style=filled] + + + 22 -> 28 ; +21 [label="21: BinaryOperatorStmt: Assign \n n$14=_fun___objc_alloc_no_fail(sizeof(class A ):class A *) [line 39]\n n$12=_fun_A_init(n$14:class A *) virtual [line 39]\n *&#GB$A_test2_sharedInstance:struct objc_object *=n$12 [line 39]\n REMOVE_TEMPS(n$12,n$14); [line 39]\n " shape="box"] + + + 21 -> 20 ; +20 [label="20: Call (_fun___objc_anonymous_block_A_test2______3) \n DECLARE_LOCALS(&__objc_anonymous_block_A_test2______3); [line 40]\n n$11=_fun___objc_alloc_no_fail(sizeof(class __objc_anonymous_block_A_test2______3 ):class __objc_anonymous_block_A_test2______3 *) [line 40]\n *&__objc_anonymous_block_A_test2______3:class __objc_anonymous_block_A_test2______3 =n$11 [line 40]\n (_fun___objc_anonymous_block_A_test2______3)() [line 40]\n REMOVE_TEMPS(n$11); [line 40]\n " shape="box"] + + + 20 -> 16 ; +19 [label="19: DeclStmt \n n$10=*&#GB$A_test2_sharedInstance:struct objc_object * [line 42]\n *&p:struct objc_object *=n$10 [line 42]\n REMOVE_TEMPS(n$10); [line 42]\n NULLIFY(&p,false); [line 42]\n APPLY_ABSTRACTION; [line 42]\n " shape="box"] + + + 19 -> 18 ; +18 [label="18: Exit __objc_anonymous_block_A_test2______3 \n " color=yellow style=filled] + + +17 [label="17: Start __objc_anonymous_block_A_test2______3\nFormals: \nLocals: p:struct objc_object * \n DECLARE_LOCALS(&return,&p); [line 40]\n NULLIFY(&p,false); [line 40]\n " color=yellow style=filled] + + + 17 -> 19 ; +16 [label="16: Return Stmt \n n$9=*&#GB$A_test2_sharedInstance:struct objc_object * [line 45]\n *&return:struct objc_object *=n$9 [line 45]\n REMOVE_TEMPS(n$9); [line 45]\n NULLIFY(&__objc_anonymous_block_A_test2______3,true); [line 45]\n APPLY_ABSTRACTION; [line 45]\n " shape="box"] + + + 16 -> 15 ; +15 [label="15: Exit A_test2 \n " color=yellow style=filled] + + +14 [label="14: Start A_test2\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 36]\n " color=yellow style=filled] + + + 14 -> 21 ; +13 [label="13: Call (_fun___objc_anonymous_block_A_test_leak______2) \n DECLARE_LOCALS(&__objc_anonymous_block_A_test_leak______2); [line 28]\n n$8=_fun___objc_alloc_no_fail(sizeof(class __objc_anonymous_block_A_test_leak______2 ):class __objc_anonymous_block_A_test_leak______2 *) [line 28]\n *&__objc_anonymous_block_A_test_leak______2:class __objc_anonymous_block_A_test_leak______2 =n$8 [line 28]\n (_fun___objc_anonymous_block_A_test_leak______2)() [line 28]\n REMOVE_TEMPS(n$8); [line 28]\n NULLIFY(&__objc_anonymous_block_A_test_leak______2,true); [line 28]\n APPLY_ABSTRACTION; [line 28]\n " shape="box"] + + + 13 -> 9 ; +12 [label="12: BinaryOperatorStmt: Assign \n n$7=_fun___objc_alloc_no_fail(sizeof(class A ):class A *) [line 29]\n n$5=_fun_A_init(n$7:class A *) virtual [line 29]\n *&#GB$A_test_leak_sharedInstance:struct objc_object *=n$5 [line 29]\n REMOVE_TEMPS(n$5,n$7); [line 29]\n APPLY_ABSTRACTION; [line 29]\n " shape="box"] + + + 12 -> 11 ; +11 [label="11: Exit __objc_anonymous_block_A_test_leak______2 \n " color=yellow style=filled] + + +10 [label="10: Start __objc_anonymous_block_A_test_leak______2\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 28]\n " color=yellow style=filled] + + + 10 -> 12 ; +9 [label="9: Exit A_test_leak \n " color=yellow style=filled] + + +8 [label="8: Start A_test_leak\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 25]\n " color=yellow style=filled] + + + 8 -> 13 ; +7 [label="7: Call (_fun___objc_anonymous_block_A_test______1) \n DECLARE_LOCALS(&__objc_anonymous_block_A_test______1); [line 17]\n n$4=_fun___objc_alloc_no_fail(sizeof(class __objc_anonymous_block_A_test______1 ):class __objc_anonymous_block_A_test______1 *) [line 17]\n *&__objc_anonymous_block_A_test______1:class __objc_anonymous_block_A_test______1 =n$4 [line 17]\n (_fun___objc_anonymous_block_A_test______1)() [line 17]\n REMOVE_TEMPS(n$4); [line 17]\n " shape="box"] + + + 7 -> 3 ; +6 [label="6: BinaryOperatorStmt: Assign \n n$3=_fun___objc_alloc_no_fail(sizeof(class A ):class A *) [line 18]\n n$1=_fun_A_init(n$3:class A *) virtual [line 18]\n *&#GB$A_test_sharedInstance:struct objc_object *=n$1 [line 18]\n REMOVE_TEMPS(n$1,n$3); [line 18]\n APPLY_ABSTRACTION; [line 18]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: Exit __objc_anonymous_block_A_test______1 \n " color=yellow style=filled] + + +4 [label="4: Start __objc_anonymous_block_A_test______1\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 17]\n " color=yellow style=filled] + + + 4 -> 6 ; +3 [label="3: Return Stmt \n n$0=*&#GB$A_test_sharedInstance:struct objc_object * [line 22]\n *&return:struct objc_object *=n$0 [line 22]\n REMOVE_TEMPS(n$0); [line 22]\n NULLIFY(&__objc_anonymous_block_A_test______1,true); [line 22]\n APPLY_ABSTRACTION; [line 22]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit A_test \n " color=yellow style=filled] + + +1 [label="1: Start A_test\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 14]\n " color=yellow style=filled] + + + 1 -> 7 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/block/static.m b/infer/tests/codetoanalyze/objc/frontend/block/static.m new file mode 100644 index 000000000..20900606c --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/block/static.m @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface A : NSObject + +@end + +@implementation A + ++ (instancetype)test +{ + static id sharedInstance; + ^{ + sharedInstance = [[self alloc] init]; + //return sharedInstance; + }(); + + return sharedInstance; +} + ++ (void)test_leak +{ + static id sharedInstance; + ^{ + sharedInstance = [[self alloc] init]; + //return sharedInstance; + }(); + +} + + ++ (instancetype)test2 +{ + static id sharedInstance; + sharedInstance = [[self alloc] init]; + ^{ + //NSLog(@"Passing from block...\n"); + id p = sharedInstance; + }(); + + return sharedInstance; +} + + + + ++ (int)test3 +{ + static int i; + + ^{ + // NSLog(@"Passing from block...\n"); + i++; + }(); + + return i; +} + + +@end + + +int main(int argc, const char * argv[]) { + + return 0; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/boxing/Boxing.dot b/infer/tests/codetoanalyze/objc/frontend/boxing/Boxing.dot new file mode 100644 index 000000000..649d4675b --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/boxing/Boxing.dot @@ -0,0 +1,100 @@ +digraph iCFG { +26 [label="26: DeclStmt \n n$16=_fun_strdup(\"hello world\":char *) [line 38]\n n$15=_fun_NSString_stringWithUTF8String:(n$16:char *) [line 38]\n *&s:class NSString *=n$15 [line 38]\n REMOVE_TEMPS(n$15,n$16); [line 38]\n NULLIFY(&s,false); [line 38]\n " shape="box"] + + + 26 -> 25 ; +25 [label="25: Return Stmt \n n$14=_fun_NSString_stringWithUTF8String:(\"hello world\":char *) [line 39]\n *&return:class NSString *=n$14 [line 39]\n REMOVE_TEMPS(n$14); [line 39]\n APPLY_ABSTRACTION; [line 39]\n " shape="box"] + + + 25 -> 24 ; +24 [label="24: Exit Boxing_getS \n " color=yellow style=filled] + + +23 [label="23: Start Boxing_getS\nFormals: self:class Boxing *\nLocals: s:class NSString * \n DECLARE_LOCALS(&return,&s); [line 37]\n NULLIFY(&s,false); [line 37]\n NULLIFY(&self,false); [line 37]\n " color=yellow style=filled] + + + 23 -> 26 ; +22 [label="22: DeclStmt \n n$13=_fun_NSNumber_numberWithBool:(1:signed char ) [line 33]\n *&n:class NSNumber *=n$13 [line 33]\n REMOVE_TEMPS(n$13); [line 33]\n NULLIFY(&n,false); [line 33]\n " shape="box"] + + + 22 -> 21 ; +21 [label="21: Return Stmt \n n$12=_fun_NSNumber_numberWithBool:(1:signed char ) [line 34]\n *&return:class NSNumber *=n$12 [line 34]\n REMOVE_TEMPS(n$12); [line 34]\n APPLY_ABSTRACTION; [line 34]\n " shape="box"] + + + 21 -> 20 ; +20 [label="20: Exit Boxing_getBool \n " color=yellow style=filled] + + +19 [label="19: Start Boxing_getBool\nFormals: self:class Boxing *\nLocals: n:class NSNumber * \n DECLARE_LOCALS(&return,&n); [line 32]\n NULLIFY(&n,false); [line 32]\n NULLIFY(&self,false); [line 32]\n " color=yellow style=filled] + + + 19 -> 22 ; +18 [label="18: DeclStmt \n n$11=_fun_NSNumber_numberWithDouble:(1.500000:double ) [line 28]\n *&n:class NSNumber *=n$11 [line 28]\n REMOVE_TEMPS(n$11); [line 28]\n NULLIFY(&n,false); [line 28]\n " shape="box"] + + + 18 -> 17 ; +17 [label="17: Return Stmt \n n$10=_fun_NSNumber_numberWithDouble:(1.500000:double ) [line 29]\n *&return:class NSNumber *=n$10 [line 29]\n REMOVE_TEMPS(n$10); [line 29]\n APPLY_ABSTRACTION; [line 29]\n " shape="box"] + + + 17 -> 16 ; +16 [label="16: Exit Boxing_getDouble \n " color=yellow style=filled] + + +15 [label="15: Start Boxing_getDouble\nFormals: self:class Boxing *\nLocals: n:class NSNumber * \n DECLARE_LOCALS(&return,&n); [line 27]\n NULLIFY(&n,false); [line 27]\n NULLIFY(&self,false); [line 27]\n " color=yellow style=filled] + + + 15 -> 18 ; +14 [label="14: DeclStmt \n n$9=_fun_NSNumber_numberWithFloat:(1.500000:float ) [line 23]\n *&n:class NSNumber *=n$9 [line 23]\n REMOVE_TEMPS(n$9); [line 23]\n NULLIFY(&n,false); [line 23]\n " shape="box"] + + + 14 -> 13 ; +13 [label="13: Return Stmt \n n$8=_fun_NSNumber_numberWithFloat:(1.500000:float ) [line 24]\n *&return:class NSNumber *=n$8 [line 24]\n REMOVE_TEMPS(n$8); [line 24]\n APPLY_ABSTRACTION; [line 24]\n " shape="box"] + + + 13 -> 12 ; +12 [label="12: Exit Boxing_getFloat \n " color=yellow style=filled] + + +11 [label="11: Start Boxing_getFloat\nFormals: self:class Boxing *\nLocals: n:class NSNumber * \n DECLARE_LOCALS(&return,&n); [line 22]\n NULLIFY(&n,false); [line 22]\n NULLIFY(&self,false); [line 22]\n " color=yellow style=filled] + + + 11 -> 14 ; +10 [label="10: DeclStmt \n n$7=_fun_NSNumber_numberWithInt:(5:int ) [line 18]\n *&n:class NSNumber *=n$7 [line 18]\n REMOVE_TEMPS(n$7); [line 18]\n NULLIFY(&n,false); [line 18]\n " shape="box"] + + + 10 -> 9 ; +9 [label="9: Return Stmt \n n$6=_fun_NSNumber_numberWithInt:(5:int ) [line 19]\n *&return:class NSNumber *=n$6 [line 19]\n REMOVE_TEMPS(n$6); [line 19]\n APPLY_ABSTRACTION; [line 19]\n " shape="box"] + + + 9 -> 8 ; +8 [label="8: Exit Boxing_getInt \n " color=yellow style=filled] + + +7 [label="7: Start Boxing_getInt\nFormals: self:class Boxing *\nLocals: n:class NSNumber * \n DECLARE_LOCALS(&return,&n); [line 17]\n NULLIFY(&n,false); [line 17]\n NULLIFY(&self,false); [line 17]\n " color=yellow style=filled] + + + 7 -> 10 ; +6 [label="6: DeclStmt \n *&x:int =4 [line 11]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: DeclStmt \n *&y:int =5 [line 12]\n " shape="box"] + + + 5 -> 4 ; +4 [label="4: DeclStmt \n n$4=*&x:int [line 13]\n n$5=*&y:int [line 13]\n n$3=_fun_NSNumber_numberWithInt:((n$4 + n$5):int ) [line 13]\n *&n:class NSNumber *=n$3 [line 13]\n REMOVE_TEMPS(n$3,n$4,n$5); [line 13]\n NULLIFY(&n,false); [line 13]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n n$1=*&x:int [line 14]\n n$2=*&y:int [line 14]\n n$0=_fun_NSNumber_numberWithInt:((n$1 + n$2):int ) [line 14]\n *&return:class NSNumber *=n$0 [line 14]\n REMOVE_TEMPS(n$0,n$1,n$2); [line 14]\n NULLIFY(&x,false); [line 14]\n NULLIFY(&y,false); [line 14]\n APPLY_ABSTRACTION; [line 14]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit Boxing_getIntExp \n " color=yellow style=filled] + + +1 [label="1: Start Boxing_getIntExp\nFormals: self:class Boxing *\nLocals: x:int y:int n:class NSNumber * \n DECLARE_LOCALS(&return,&x,&y,&n); [line 10]\n NULLIFY(&n,false); [line 10]\n NULLIFY(&self,false); [line 10]\n NULLIFY(&x,false); [line 10]\n NULLIFY(&y,false); [line 10]\n " color=yellow style=filled] + + + 1 -> 6 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/boxing/Boxing.h b/infer/tests/codetoanalyze/objc/frontend/boxing/Boxing.h new file mode 100644 index 000000000..3e7f5a27e --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/boxing/Boxing.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface Boxing : NSObject + +- (NSNumber*)getIntExp; + +- (NSNumber*)getInt; + +- (NSNumber*)getFloat; + +- (NSNumber*)getDouble; + +- (NSNumber*)getBool; + +- (NSString*)getS; + +@end diff --git a/infer/tests/codetoanalyze/objc/frontend/boxing/Boxing.m b/infer/tests/codetoanalyze/objc/frontend/boxing/Boxing.m new file mode 100644 index 000000000..dba81f2a0 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/boxing/Boxing.m @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import "Boxing.h" + +@implementation Boxing + +- (NSNumber*)getIntExp { + int x = 4; + int y = 5; + NSNumber *n = [NSNumber numberWithInt: x+y]; + return @(x+y); +} + +- (NSNumber*)getInt { + NSNumber *n = [NSNumber numberWithInt: 5]; + return @5; +} + +- (NSNumber*)getFloat { + NSNumber *n = [NSNumber numberWithFloat: 1.5f]; + return @1.5f; +} + +- (NSNumber*)getDouble { + NSNumber *n = [NSNumber numberWithDouble: 1.5]; + return @1.5; +} + +- (NSNumber*)getBool { + NSNumber *n = [NSNumber numberWithBool: YES]; + return @YES; +} + +- (NSString*)getS{ + NSString *s = @(strdup("hello world")) ; + return [NSString stringWithUTF8String: "hello world"];; +} + +@end diff --git a/infer/tests/codetoanalyze/objc/frontend/boxing/array.dot b/infer/tests/codetoanalyze/objc/frontend/boxing/array.dot new file mode 100644 index 000000000..eb06d7a6a --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/boxing/array.dot @@ -0,0 +1,42 @@ +digraph iCFG { +10 [label="10: DeclStmt \n n$6=_fun_NSString_stringWithUTF8String:(\"Mercedes-Benz\":char *) [line 14]\n n$7=_fun_NSString_stringWithUTF8String:(\"BMW\":char *) [line 14]\n n$8=_fun_NSString_stringWithUTF8String:(\"Porsche\":char *) [line 14]\n n$9=_fun_NSString_stringWithUTF8String:(\"Opel\":char *) [line 15]\n n$10=_fun_NSString_stringWithUTF8String:(\"Volkswagen\":char *) [line 14]\n n$11=_fun_NSString_stringWithUTF8String:(\"Audi\":char *) [line 14]\n n$5=_fun_NSArray_arrayWithObjects:count:(n$6:struct objc_object *,n$7:struct objc_object *,n$8:struct objc_object *,n$9:struct objc_object *,n$10:struct objc_object *,n$11:struct objc_object *,0:struct objc_object *) [line 14]\n *&germanCars:class NSArray *=n$5 [line 14]\n REMOVE_TEMPS(n$5,n$6,n$7,n$8,n$9,n$10,n$11); [line 14]\n " shape="box"] + + + 10 -> 9 ; +9 [label="9: BinaryOperatorStmt: Assign \n n$4=*&germanCars:class NSArray * [line 16]\n n$3=_fun_NSArray_objectAtIndexedSubscript:(n$4:class NSArray *,3:unsigned long ) virtual [line 16]\n *&s:class NSString *=n$3 [line 16]\n REMOVE_TEMPS(n$3,n$4); [line 16]\n NULLIFY(&germanCars,false); [line 16]\n NULLIFY(&s,false); [line 16]\n APPLY_ABSTRACTION; [line 16]\n " shape="box"] + + + 9 -> 4 ; +8 [label="8: Call _fun_NSLog \n n$1=_fun_NSString_stringWithUTF8String:(\"%@\":char *) [line 19]\n n$2=*&item:class NSString * [line 19]\n _fun_NSLog(n$1:struct objc_object *,n$2:class NSString *) [line 19]\n REMOVE_TEMPS(n$1,n$2); [line 19]\n APPLY_ABSTRACTION; [line 19]\n " shape="box"] + + + 8 -> 4 ; +7 [label="7: Prune (false branch) \n PRUNE(((n$0 > 0) == 0), false); [line 18]\n REMOVE_TEMPS(n$0); [line 18]\n " shape="invhouse"] + + + 7 -> 3 ; +6 [label="6: Prune (true branch) \n PRUNE(((n$0 > 0) != 0), true); [line 18]\n REMOVE_TEMPS(n$0); [line 18]\n " shape="invhouse"] + + + 6 -> 8 ; +5 [label="5: BinaryOperatorStmt: GT \n n$0=*&__INFER_NON_DET:int [line 18]\n " shape="box"] + + + 5 -> 6 ; + 5 -> 7 ; +4 [label="4: + \n " ] + + + 4 -> 5 ; +3 [label="3: Return Stmt \n NULLIFY(&item,false); [line 22]\n *&return:int =0 [line 22]\n APPLY_ABSTRACTION; [line 22]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: s:class NSString * germanCars:class NSArray * item:class NSString * \n DECLARE_LOCALS(&return,&s,&germanCars,&item); [line 10]\n NULLIFY(&germanCars,false); [line 10]\n NULLIFY(&s,false); [line 10]\n " color=yellow style=filled] + + + 1 -> 10 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/boxing/array.m b/infer/tests/codetoanalyze/objc/frontend/boxing/array.m new file mode 100644 index 000000000..178d5634b --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/boxing/array.m @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + + + +int main() { + + NSString *s; + + NSArray *germanCars = @[@"Mercedes-Benz", @"BMW", @"Porsche", + @"Opel", @"Volkswagen", @"Audi"]; + s = germanCars[3]; + + for (NSString *item in germanCars) { + NSLog(@"%@", item); + } + + return 0; + +} diff --git a/infer/tests/codetoanalyze/objc/frontend/boxing/array_literal.c b/infer/tests/codetoanalyze/objc/frontend/boxing/array_literal.c new file mode 100644 index 000000000..d9731bfa4 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/boxing/array_literal.c @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +NSArray* get_array() { + NSArray *animals = [NSArray arrayWithObjects:@"cat", @"dog", nil]; + return @[@"cat", @"dog"]; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/boxing/array_literal.dot b/infer/tests/codetoanalyze/objc/frontend/boxing/array_literal.dot new file mode 100644 index 000000000..a35a12239 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/boxing/array_literal.dot @@ -0,0 +1,17 @@ +digraph iCFG { +4 [label="4: DeclStmt \n n$4=_fun_NSString_stringWithUTF8String:(\"cat\":char *) [line 9]\n n$5=_fun_NSString_stringWithUTF8String:(\"dog\":char *) [line 9]\n n$3=_fun_NSArray_arrayWithObjects:(n$4:struct objc_object *,n$5:class NSString *,0:void *) [line 9]\n *&animals:class NSArray *=n$3 [line 9]\n REMOVE_TEMPS(n$3,n$4,n$5); [line 9]\n NULLIFY(&animals,false); [line 9]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n n$1=_fun_NSString_stringWithUTF8String:(\"cat\":char *) [line 10]\n n$2=_fun_NSString_stringWithUTF8String:(\"dog\":char *) [line 10]\n n$0=_fun_NSArray_arrayWithObjects:count:(n$1:struct objc_object *,n$2:struct objc_object *,0:struct objc_object *) [line 10]\n *&return:class NSArray *=n$0 [line 10]\n REMOVE_TEMPS(n$0,n$1,n$2); [line 10]\n APPLY_ABSTRACTION; [line 10]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit get_array \n " color=yellow style=filled] + + +1 [label="1: Start get_array\nFormals: \nLocals: animals:class NSArray * \n DECLARE_LOCALS(&return,&animals); [line 8]\n NULLIFY(&animals,false); [line 8]\n " color=yellow style=filled] + + + 1 -> 4 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/boxing/dict_literal.c b/infer/tests/codetoanalyze/objc/frontend/boxing/dict_literal.c new file mode 100644 index 000000000..cfe5210ca --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/boxing/dict_literal.c @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + + + +#import + +NSDictionary* get_array1() { + + return [NSDictionary dictionaryWithObjectsAndKeys: + @"Matt", @"firstName", @"Galloway", @"lastName", + @28, @"age", nil]; +} + +NSDictionary* get_array2() { + + return @{@"firstName" : @"Matt", @"lastName" : @"Galloway", @"age" : @28}; +} + diff --git a/infer/tests/codetoanalyze/objc/frontend/boxing/dict_literal.dot b/infer/tests/codetoanalyze/objc/frontend/boxing/dict_literal.dot new file mode 100644 index 000000000..7aabf5b30 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/boxing/dict_literal.dot @@ -0,0 +1,24 @@ +digraph iCFG { +6 [label="6: Return Stmt \n n$8=_fun_NSString_stringWithUTF8String:(\"Matt\":char *) [line 19]\n n$9=_fun_NSString_stringWithUTF8String:(\"firstName\":char *) [line 19]\n n$10=_fun_NSString_stringWithUTF8String:(\"Galloway\":char *) [line 19]\n n$11=_fun_NSString_stringWithUTF8String:(\"lastName\":char *) [line 19]\n n$12=_fun_NSNumber_numberWithInt:(28:int ) [line 19]\n n$13=_fun_NSString_stringWithUTF8String:(\"age\":char *) [line 19]\n n$7=_fun_NSDictionary_dictionaryWithObjectsAndKeys:(n$8:struct objc_object *,n$9:struct objc_object *,n$10:struct objc_object *,n$11:struct objc_object *,n$12:struct objc_object *,n$13:struct objc_object *,0:struct objc_object *) [line 19]\n *&return:class NSDictionary *=n$7 [line 19]\n REMOVE_TEMPS(n$7,n$8,n$9,n$10,n$11,n$12,n$13); [line 19]\n APPLY_ABSTRACTION; [line 19]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: Exit get_array2 \n " color=yellow style=filled] + + +4 [label="4: Start get_array2\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 17]\n " color=yellow style=filled] + + + 4 -> 6 ; +3 [label="3: Return Stmt \n n$1=_fun_NSString_stringWithUTF8String:(\"Matt\":char *) [line 13]\n n$2=_fun_NSString_stringWithUTF8String:(\"firstName\":char *) [line 12]\n n$3=_fun_NSString_stringWithUTF8String:(\"Galloway\":char *) [line 12]\n n$4=_fun_NSString_stringWithUTF8String:(\"lastName\":char *) [line 12]\n n$5=_fun_NSNumber_numberWithInt:(28:int ) [line 14]\n n$6=_fun_NSString_stringWithUTF8String:(\"age\":char *) [line 12]\n n$0=_fun_NSDictionary_dictionaryWithObjectsAndKeys:(n$1:struct objc_object *,n$2:class NSString *,n$3:class NSString *,n$4:class NSString *,n$5:class NSNumber *,n$6:class NSString *,0:void *) [line 12]\n *&return:class NSDictionary *=n$0 [line 12]\n REMOVE_TEMPS(n$0,n$1,n$2,n$3,n$4,n$5,n$6); [line 12]\n APPLY_ABSTRACTION; [line 12]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit get_array1 \n " color=yellow style=filled] + + +1 [label="1: Start get_array1\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 10]\n " color=yellow style=filled] + + + 1 -> 3 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/boxing/string_literal.c b/infer/tests/codetoanalyze/objc/frontend/boxing/string_literal.c new file mode 100644 index 000000000..88fa8329b --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/boxing/string_literal.c @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +NSString* get_string1() { + + return [NSString stringWithUTF8String: "Hello World!" ]; +} + +NSString* get_string2() { + + return @"Hello World!"; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/boxing/string_literal.dot b/infer/tests/codetoanalyze/objc/frontend/boxing/string_literal.dot new file mode 100644 index 000000000..6a4d97145 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/boxing/string_literal.dot @@ -0,0 +1,24 @@ +digraph iCFG { +6 [label="6: Return Stmt \n n$1=_fun_NSString_stringWithUTF8String:(\"Hello World!\":char *) [line 15]\n *&return:class NSString *=n$1 [line 15]\n REMOVE_TEMPS(n$1); [line 15]\n APPLY_ABSTRACTION; [line 15]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: Exit get_string2 \n " color=yellow style=filled] + + +4 [label="4: Start get_string2\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 13]\n " color=yellow style=filled] + + + 4 -> 6 ; +3 [label="3: Return Stmt \n n$0=_fun_NSString_stringWithUTF8String:(\"Hello World!\":char *) [line 10]\n *&return:class NSString *=n$0 [line 10]\n REMOVE_TEMPS(n$0); [line 10]\n APPLY_ABSTRACTION; [line 10]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit get_string1 \n " color=yellow style=filled] + + +1 [label="1: Start get_string1\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 8]\n " color=yellow style=filled] + + + 1 -> 3 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/conditional_operation/ConditionalOperation.dot b/infer/tests/codetoanalyze/objc/frontend/conditional_operation/ConditionalOperation.dot new file mode 100644 index 000000000..c5301db39 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/conditional_operation/ConditionalOperation.dot @@ -0,0 +1,45 @@ +digraph iCFG { +11 [label="11: ConditinalStmt Branch \n NULLIFY(&b,false); [line 20]\n DECLARE_LOCALS(&SIL_temp_conditional___7); [line 20]\n *&SIL_temp_conditional___7:int =1 [line 20]\n APPLY_ABSTRACTION; [line 20]\n " shape="box"] + + + 11 -> 7 ; +10 [label="10: ConditinalStmt Branch \n n$4=*&b:signed char [line 20]\n DECLARE_LOCALS(&SIL_temp_conditional___7); [line 20]\n *&SIL_temp_conditional___7:int =n$4 [line 20]\n REMOVE_TEMPS(n$4); [line 20]\n NULLIFY(&b,false); [line 20]\n APPLY_ABSTRACTION; [line 20]\n " shape="box"] + + + 10 -> 7 ; +9 [label="9: Prune (false branch) \n n$3=*&b:signed char [line 20]\n PRUNE((n$3 == 0), false); [line 20]\n REMOVE_TEMPS(n$3); [line 20]\n " shape="invhouse"] + + + 9 -> 11 ; +8 [label="8: Prune (true branch) \n n$3=*&b:signed char [line 20]\n PRUNE((n$3 != 0), true); [line 20]\n REMOVE_TEMPS(n$3); [line 20]\n " shape="invhouse"] + + + 8 -> 10 ; +7 [label="7: + \n " ] + + + 7 -> 6 ; +6 [label="6: Return Stmt \n n$2=*&self:class A * [line 20]\n n$5=*&SIL_temp_conditional___7:int [line 20]\n NULLIFY(&SIL_temp_conditional___7,true); [line 20]\n n$1=_fun_A_test4:(n$2:class A *,n$5:int ) virtual [line 20]\n *&return:int =n$1 [line 20]\n REMOVE_TEMPS(n$1,n$2,n$5); [line 20]\n NULLIFY(&self,false); [line 20]\n APPLY_ABSTRACTION; [line 20]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: Exit A_test5: \n " color=yellow style=filled] + + +4 [label="4: Start A_test5:\nFormals: self:class A * b:signed char \nLocals: \n DECLARE_LOCALS(&return); [line 19]\n " color=yellow style=filled] + + + 4 -> 8 ; + 4 -> 9 ; +3 [label="3: Return Stmt \n n$0=*&x:int [line 16]\n *&return:int =n$0 [line 16]\n REMOVE_TEMPS(n$0); [line 16]\n NULLIFY(&x,false); [line 16]\n APPLY_ABSTRACTION; [line 16]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit A_test4: \n " color=yellow style=filled] + + +1 [label="1: Start A_test4:\nFormals: self:class A * x:int \nLocals: \n DECLARE_LOCALS(&return); [line 15]\n NULLIFY(&self,false); [line 15]\n " color=yellow style=filled] + + + 1 -> 3 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/conditional_operation/ConditionalOperation.m b/infer/tests/codetoanalyze/objc/frontend/conditional_operation/ConditionalOperation.m new file mode 100644 index 000000000..471fa0a0c --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/conditional_operation/ConditionalOperation.m @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface A : NSObject + +-(int) test4:(int) x; +@end + +@implementation A + +-(int) test4:(int) x { + return x; +} + +-(int) test5:(BOOL) b { + return [self test4 : (b ? b : 1)]; +} + +@end diff --git a/infer/tests/codetoanalyze/objc/frontend/exceptions/ExceptionExample.dot b/infer/tests/codetoanalyze/objc/frontend/exceptions/ExceptionExample.dot new file mode 100644 index 000000000..6f4da841f --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/exceptions/ExceptionExample.dot @@ -0,0 +1,45 @@ +digraph iCFG { +11 [label="11: DeclStmt \n n$9=_fun___objc_alloc_no_fail(sizeof(class NSString ):class NSString *) [line 27]\n *&s:class NSString *=n$9 [line 27]\n REMOVE_TEMPS(n$9); [line 27]\n " shape="box"] + + + 11 -> 8 ; + 11 -> 9 ; +10 [label="10: Return Stmt \n NULLIFY(&s,false); [line 29]\n n$6=_fun_NSString_stringWithUTF8String:(\"Something is not right exception\":char *) [line 29]\n n$7=_fun_NSString_stringWithUTF8String:(\"Can't perform this operation because of this or that\":char *) [line 30]\n n$5=_fun_NSException_exceptionWithName:reason:userInfo:(n$6:class NSString *,n$7:class NSString *,0:class NSDictionary *) [line 29]\n *&return:void =n$5 [line 29]\n REMOVE_TEMPS(n$5,n$6,n$7); [line 29]\n APPLY_ABSTRACTION; [line 29]\n " shape="box"] + + + 10 -> 6 ; +9 [label="9: Prune (false branch) \n n$4=*&s:class NSString * [line 28]\n PRUNE((n$4 == 0), false); [line 28]\n REMOVE_TEMPS(n$4); [line 28]\n " shape="invhouse"] + + + 9 -> 7 ; +8 [label="8: Prune (true branch) \n n$4=*&s:class NSString * [line 28]\n PRUNE((n$4 != 0), true); [line 28]\n REMOVE_TEMPS(n$4); [line 28]\n " shape="invhouse"] + + + 8 -> 10 ; +7 [label="7: + \n NULLIFY(&s,false); [line 28]\n " ] + + + 7 -> 6 ; +6 [label="6: Exit ExceptionExample_test1 \n " color=yellow style=filled] + + +5 [label="5: Start ExceptionExample_test1\nFormals: self:class ExceptionExample *\nLocals: s:class NSString * \n DECLARE_LOCALS(&return,&s); [line 26]\n NULLIFY(&s,false); [line 26]\n NULLIFY(&self,false); [line 26]\n " color=yellow style=filled] + + + 5 -> 11 ; +4 [label="4: DeclStmt \n n$3=_fun___objc_alloc_no_fail(sizeof(class NSString ):class NSString *) [line 17]\n *&s:class NSString *=n$3 [line 17]\n REMOVE_TEMPS(n$3); [line 17]\n NULLIFY(&s,false); [line 17]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: Message Call: description \n n$1=*&self:class ExceptionExample * [line 22]\n n$0=_fun_ExceptionExample_description(n$1:class ExceptionExample *) virtual [line 22]\n REMOVE_TEMPS(n$0,n$1); [line 22]\n NULLIFY(&self,false); [line 22]\n APPLY_ABSTRACTION; [line 22]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit ExceptionExample_test \n " color=yellow style=filled] + + +1 [label="1: Start ExceptionExample_test\nFormals: self:class ExceptionExample *\nLocals: s:class NSString * \n DECLARE_LOCALS(&return,&s); [line 15]\n NULLIFY(&s,false); [line 15]\n " color=yellow style=filled] + + + 1 -> 4 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/exceptions/ExceptionExample.m b/infer/tests/codetoanalyze/objc/frontend/exceptions/ExceptionExample.m new file mode 100644 index 000000000..f335aa148 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/exceptions/ExceptionExample.m @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface ExceptionExample : NSObject + +@end + + +@implementation ExceptionExample + +-(void) test { + @try { + NSString *s = [NSString alloc]; + } + @catch (NSException *exception) { + } + @finally { + [self description]; + } +} + +-(void) test1 { + NSString *s = [NSString alloc]; + if (s) { + @throw [NSException exceptionWithName:@"Something is not right exception" + reason:@"Can't perform this operation because of this or that" + userInfo:nil]; + } + +} + +@end diff --git a/infer/tests/codetoanalyze/objc/frontend/predefined_expr/PredefinedExprExample.dot b/infer/tests/codetoanalyze/objc/frontend/predefined_expr/PredefinedExprExample.dot new file mode 100644 index 000000000..75bc91a83 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/predefined_expr/PredefinedExprExample.dot @@ -0,0 +1,35 @@ +digraph iCFG { +9 [label="9: Call _fun_NSLog \n n$2=_fun_NSString_stringWithUTF8String:(\"%s\":char *) [line 23]\n _fun_NSLog(n$2:struct objc_object *,\"\":char *) [line 23]\n REMOVE_TEMPS(n$2); [line 23]\n APPLY_ABSTRACTION; [line 23]\n " shape="box"] + + + 9 -> 8 ; +8 [label="8: Exit A_testFunct \n " color=yellow style=filled] + + +7 [label="7: Start A_testFunct\nFormals: self:class A *\nLocals: \n DECLARE_LOCALS(&return); [line 22]\n NULLIFY(&self,false); [line 22]\n " color=yellow style=filled] + + + 7 -> 9 ; +6 [label="6: Call _fun_NSLog \n n$1=_fun_NSString_stringWithUTF8String:(\"%s\":char *) [line 19]\n _fun_NSLog(n$1:struct objc_object *,\"\":char *) [line 19]\n REMOVE_TEMPS(n$1); [line 19]\n APPLY_ABSTRACTION; [line 19]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: Exit A_testFunction \n " color=yellow style=filled] + + +4 [label="4: Start A_testFunction\nFormals: self:class A *\nLocals: \n DECLARE_LOCALS(&return); [line 18]\n NULLIFY(&self,false); [line 18]\n " color=yellow style=filled] + + + 4 -> 6 ; +3 [label="3: Call _fun_NSLog \n n$0=_fun_NSString_stringWithUTF8String:(\"%s\":char *) [line 15]\n _fun_NSLog(n$0:struct objc_object *,\"\":char *) [line 15]\n REMOVE_TEMPS(n$0); [line 15]\n APPLY_ABSTRACTION; [line 15]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit A_testPrettyFunction \n " color=yellow style=filled] + + +1 [label="1: Start A_testPrettyFunction\nFormals: self:class A *\nLocals: \n DECLARE_LOCALS(&return); [line 14]\n NULLIFY(&self,false); [line 14]\n " color=yellow style=filled] + + + 1 -> 3 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/predefined_expr/PredefinedExprExample.m b/infer/tests/codetoanalyze/objc/frontend/predefined_expr/PredefinedExprExample.m new file mode 100644 index 000000000..4cb4ef7ca --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/predefined_expr/PredefinedExprExample.m @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface A : NSObject + +@end + +@implementation A + +- (void) testPrettyFunction { + NSLog(@"%s", __PRETTY_FUNCTION__); +} + +- (void) testFunction { + NSLog(@"%s", __FUNCTION__); +} + +- (void) testFunct { + NSLog(@"%s", __func__); +} + +@end diff --git a/infer/tests/codetoanalyze/objc/frontend/property/Car.h b/infer/tests/codetoanalyze/objc/frontend/property/Car.h new file mode 100644 index 000000000..1ff61ffe1 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/property/Car.h @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface Car : NSObject + +@property BOOL running; + +@end diff --git a/infer/tests/codetoanalyze/objc/frontend/property/Car.m b/infer/tests/codetoanalyze/objc/frontend/property/Car.m new file mode 100644 index 000000000..280d78d4e --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/property/Car.m @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import "Car.h" + +@implementation Car + +@synthesize running = _running; + +@end diff --git a/infer/tests/codetoanalyze/objc/frontend/property/PropertyAttributes.dot b/infer/tests/codetoanalyze/objc/frontend/property/PropertyAttributes.dot new file mode 100644 index 000000000..d6dc3c9a9 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/property/PropertyAttributes.dot @@ -0,0 +1,139 @@ +digraph iCFG { +36 [label="36: DeclStmt \n n$37=_fun___objc_alloc_no_fail(sizeof(class A ):class A *) [line 35]\n n$35=_fun_A_init(n$37:class A *) virtual [line 35]\n *&a:class A *=n$35 [line 35]\n REMOVE_TEMPS(n$35,n$37); [line 35]\n " shape="box"] + + + 36 -> 35 ; +35 [label="35: Message Call: setLast_name: \n n$33=*&a:class A * [line 36]\n n$34=*&a2:class A * [line 36]\n _fun_A_setLast_name:(n$33:class A *,n$34:class A *) virtual [line 36]\n REMOVE_TEMPS(n$33,n$34); [line 36]\n NULLIFY(&a2,false); [line 36]\n " shape="box"] + + + 35 -> 34 ; +34 [label="34: Message Call: release \n n$32=*&a:class A * [line 37]\n _fun___objc_release(n$32:class A *) [line 37]\n REMOVE_TEMPS(n$32); [line 37]\n NULLIFY(&a,false); [line 37]\n " shape="box"] + + + 34 -> 33 ; +33 [label="33: Return Stmt \n *&return:int =0 [line 38]\n APPLY_ABSTRACTION; [line 38]\n " shape="box"] + + + 33 -> 32 ; +32 [label="32: Exit test \n " color=yellow style=filled] + + +31 [label="31: Start test\nFormals: a2:class A *\nLocals: a:class A * \n DECLARE_LOCALS(&return,&a); [line 34]\n NULLIFY(&a,false); [line 34]\n " color=yellow style=filled] + + + 31 -> 36 ; +30 [label="30: BinaryOperatorStmt: Assign \n n$30=*&self:class A * [line -1]\n n$31=*&last_name:class A * [line -1]\n *n$30._last_name:class A *=n$31 [line -1]\n REMOVE_TEMPS(n$30,n$31); [line -1]\n NULLIFY(&last_name,false); [line -1]\n NULLIFY(&self,false); [line -1]\n APPLY_ABSTRACTION; [line -1]\n " shape="box"] + + + 30 -> 29 ; +29 [label="29: Exit A_setLast_name: \n " color=yellow style=filled] + + +28 [label="28: Start A_setLast_name:\nFormals: self:class A * last_name:class A *\nLocals: \n DECLARE_LOCALS(&return); [line -1]\n " color=yellow style=filled] + + + 28 -> 30 ; +27 [label="27: Return Stmt \n n$28=*&self:class A * [line -1]\n n$29=*n$28._last_name:class A * [line -1]\n *&return:class A *=n$29 [line -1]\n REMOVE_TEMPS(n$28,n$29); [line -1]\n NULLIFY(&self,false); [line -1]\n APPLY_ABSTRACTION; [line -1]\n " shape="box"] + + + 27 -> 26 ; +26 [label="26: Exit A_last_name \n " color=yellow style=filled] + + +25 [label="25: Start A_last_name\nFormals: self:class A *\nLocals: \n DECLARE_LOCALS(&return); [line -1]\n " color=yellow style=filled] + + + 25 -> 27 ; +24 [label="24: Message Call: retain \n n$27=*&name:class A * [line -1]\n n$26=_fun___objc_retain(n$27:class A *) [line -1]\n REMOVE_TEMPS(n$26,n$27); [line -1]\n " shape="box"] + + + 24 -> 23 ; +23 [label="23: Message Call: release \n n$24=*&self:class A * [line -1]\n n$25=*n$24._name:class A * [line -1]\n n$23=_fun___objc_release(n$25:class A *) [line -1]\n REMOVE_TEMPS(n$23,n$24,n$25); [line -1]\n " shape="box"] + + + 23 -> 22 ; +22 [label="22: BinaryOperatorStmt: Assign \n n$21=*&self:class A * [line -1]\n n$22=*&name:class A * [line -1]\n *n$21._name:class A *=n$22 [line -1]\n REMOVE_TEMPS(n$21,n$22); [line -1]\n NULLIFY(&name,false); [line -1]\n NULLIFY(&self,false); [line -1]\n APPLY_ABSTRACTION; [line -1]\n " shape="box"] + + + 22 -> 21 ; +21 [label="21: Exit A_setName: \n " color=yellow style=filled] + + +20 [label="20: Start A_setName:\nFormals: self:class A * name:class A *\nLocals: \n DECLARE_LOCALS(&return); [line -1]\n " color=yellow style=filled] + + + 20 -> 24 ; +19 [label="19: Return Stmt \n n$19=*&self:class A * [line -1]\n n$20=*n$19._name:class A * [line -1]\n *&return:class A *=n$20 [line -1]\n REMOVE_TEMPS(n$19,n$20); [line -1]\n NULLIFY(&self,false); [line -1]\n APPLY_ABSTRACTION; [line -1]\n " shape="box"] + + + 19 -> 18 ; +18 [label="18: Exit A_name \n " color=yellow style=filled] + + +17 [label="17: Start A_name\nFormals: self:class A *\nLocals: \n DECLARE_LOCALS(&return); [line -1]\n " color=yellow style=filled] + + + 17 -> 19 ; +16 [label="16: BinaryOperatorStmt: Assign \n n$16=*&self:class A * [line -1]\n n$18=*&child:class A * [line -1]\n n$17=_fun_A_copy(n$18:class A *) virtual [line -1]\n *n$16._child:class A *=n$17 [line -1]\n REMOVE_TEMPS(n$16,n$17,n$18); [line -1]\n NULLIFY(&child,false); [line -1]\n NULLIFY(&self,false); [line -1]\n APPLY_ABSTRACTION; [line -1]\n " shape="box"] + + + 16 -> 15 ; +15 [label="15: Exit A_setChild: \n " color=yellow style=filled] + + +14 [label="14: Start A_setChild:\nFormals: self:class A * child:class A *\nLocals: \n DECLARE_LOCALS(&return); [line -1]\n " color=yellow style=filled] + + + 14 -> 16 ; +13 [label="13: Return Stmt \n n$14=*&self:class A * [line -1]\n n$15=*n$14._child:class A * [line -1]\n *&return:class A *=n$15 [line -1]\n REMOVE_TEMPS(n$14,n$15); [line -1]\n NULLIFY(&self,false); [line -1]\n APPLY_ABSTRACTION; [line -1]\n " shape="box"] + + + 13 -> 12 ; +12 [label="12: Exit A_child \n " color=yellow style=filled] + + +11 [label="11: Start A_child\nFormals: self:class A *\nLocals: \n DECLARE_LOCALS(&return); [line -1]\n " color=yellow style=filled] + + + 11 -> 13 ; +10 [label="10: DeclStmt \n n$13=_fun___objc_alloc_no_fail(sizeof(class A ):class A *) [line 23]\n n$11=_fun_A_init(n$13:class A *) virtual [line 23]\n *&other:class A *=n$11 [line 23]\n REMOVE_TEMPS(n$11,n$13); [line 23]\n " shape="box"] + + + 10 -> 5 ; + 10 -> 6 ; +9 [label="9: BinaryOperatorStmt: Assign \n n$8=*&other:class A * [line 25]\n n$9=*&self:class A * [line 25]\n n$10=*n$9._name:class A * [line 25]\n *n$8._name:class A *=n$10 [line 25]\n REMOVE_TEMPS(n$8,n$9,n$10); [line 25]\n " shape="box"] + + + 9 -> 8 ; +8 [label="8: BinaryOperatorStmt: Assign \n n$5=*&other:class A * [line 26]\n n$6=*&self:class A * [line 26]\n n$7=*n$6._last_name:class A * [line 26]\n *n$5._last_name:class A *=n$7 [line 26]\n REMOVE_TEMPS(n$5,n$6,n$7); [line 26]\n " shape="box"] + + + 8 -> 7 ; +7 [label="7: BinaryOperatorStmt: Assign \n n$2=*&other:class A * [line 27]\n n$3=*&self:class A * [line 27]\n n$4=*n$3._child:class A * [line 27]\n *n$2._child:class A *=n$4 [line 27]\n REMOVE_TEMPS(n$2,n$3,n$4); [line 27]\n NULLIFY(&self,false); [line 27]\n APPLY_ABSTRACTION; [line 27]\n " shape="box"] + + + 7 -> 4 ; +6 [label="6: Prune (false branch) \n n$1=*&other:class A * [line 24]\n PRUNE((n$1 == 0), false); [line 24]\n REMOVE_TEMPS(n$1); [line 24]\n APPLY_ABSTRACTION; [line 24]\n " shape="invhouse"] + + + 6 -> 4 ; +5 [label="5: Prune (true branch) \n n$1=*&other:class A * [line 24]\n PRUNE((n$1 != 0), true); [line 24]\n REMOVE_TEMPS(n$1); [line 24]\n " shape="invhouse"] + + + 5 -> 9 ; +4 [label="4: + \n " ] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n NULLIFY(&self,false); [line 29]\n n$0=*&other:class A * [line 29]\n *&return:class A *=n$0 [line 29]\n REMOVE_TEMPS(n$0); [line 29]\n NULLIFY(&other,false); [line 29]\n APPLY_ABSTRACTION; [line 29]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit A_copy \n " color=yellow style=filled] + + +1 [label="1: Start A_copy\nFormals: self:class A *\nLocals: other:class A * \n DECLARE_LOCALS(&return,&other); [line 22]\n NULLIFY(&other,false); [line 22]\n " color=yellow style=filled] + + + 1 -> 10 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/property/PropertyAttributes.m b/infer/tests/codetoanalyze/objc/frontend/property/PropertyAttributes.m new file mode 100644 index 000000000..390f3b1f8 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/property/PropertyAttributes.m @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface A : NSObject + +@property (nonatomic, copy) A *child; + +@property (nonatomic, retain) A *name; + +@property (nonatomic, unsafe_unretained) A *last_name; + +- (A*) copy; + +@end + +@implementation A + +- (A*) copy { + A *other = [[A alloc] init]; + if (other) { + other->_name = self->_name; + other->_last_name = self->_last_name; + other->_child = self->_child; + } + return other; +} + +@end + +int test(A* a2) { + A *a = [[A alloc] init]; + a.last_name = a2; + [a release]; + return 0; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/property/PropertyCustomAccessor.dot b/infer/tests/codetoanalyze/objc/frontend/property/PropertyCustomAccessor.dot new file mode 100644 index 000000000..b35f114a8 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/property/PropertyCustomAccessor.dot @@ -0,0 +1,24 @@ +digraph iCFG { +6 [label="6: BinaryOperatorStmt: Assign \n n$2=*&self:class ASDisplayNode * [line -1]\n n$3=*&opaque:signed char [line -1]\n *n$2._opaque:signed char =n$3 [line -1]\n REMOVE_TEMPS(n$2,n$3); [line -1]\n NULLIFY(&opaque,false); [line -1]\n NULLIFY(&self,false); [line -1]\n APPLY_ABSTRACTION; [line -1]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: Exit ASDisplayNode_setOpaque: \n " color=yellow style=filled] + + +4 [label="4: Start ASDisplayNode_setOpaque:\nFormals: self:class ASDisplayNode * opaque:signed char \nLocals: \n DECLARE_LOCALS(&return); [line -1]\n " color=yellow style=filled] + + + 4 -> 6 ; +3 [label="3: Return Stmt \n n$0=*&self:class ASDisplayNode * [line -1]\n n$1=*n$0._opaque:signed char [line -1]\n *&return:signed char =n$1 [line -1]\n REMOVE_TEMPS(n$0,n$1); [line -1]\n NULLIFY(&self,false); [line -1]\n APPLY_ABSTRACTION; [line -1]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit ASDisplayNode_isOpaque \n " color=yellow style=filled] + + +1 [label="1: Start ASDisplayNode_isOpaque\nFormals: self:class ASDisplayNode *\nLocals: \n DECLARE_LOCALS(&return); [line -1]\n " color=yellow style=filled] + + + 1 -> 3 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/property/PropertyCustomAccessor.m b/infer/tests/codetoanalyze/objc/frontend/property/PropertyCustomAccessor.m new file mode 100644 index 000000000..d1dca3a4d --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/property/PropertyCustomAccessor.m @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface ASDisplayNode : NSObject + +@property (atomic, getter=isOpaque) BOOL opaque; + +@end + + +@implementation ASDisplayNode + + +@end diff --git a/infer/tests/codetoanalyze/objc/frontend/property/PropertyImplSetter.dot b/infer/tests/codetoanalyze/objc/frontend/property/PropertyImplSetter.dot new file mode 100644 index 000000000..a69ed546d --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/property/PropertyImplSetter.dot @@ -0,0 +1,24 @@ +digraph iCFG { +6 [label="6: Return Stmt \n n$1=*&self:class PropertyImplSetter * [line -1]\n n$2=*n$1._maximumFileSize:int [line -1]\n *&return:int =n$2 [line -1]\n REMOVE_TEMPS(n$1,n$2); [line -1]\n NULLIFY(&self,false); [line -1]\n APPLY_ABSTRACTION; [line -1]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: Exit PropertyImplSetter_maximumFileSize \n " color=yellow style=filled] + + +4 [label="4: Start PropertyImplSetter_maximumFileSize\nFormals: self:class PropertyImplSetter *\nLocals: \n DECLARE_LOCALS(&return); [line -1]\n " color=yellow style=filled] + + + 4 -> 6 ; +3 [label="3: BinaryOperatorStmt: Assign \n n$0=*&self:class PropertyImplSetter * [line 12]\n *n$0._maximumFileSize:int =0 [line 12]\n REMOVE_TEMPS(n$0); [line 12]\n NULLIFY(&self,false); [line 12]\n APPLY_ABSTRACTION; [line 12]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit PropertyImplSetter_setMaximumFileSize: \n " color=yellow style=filled] + + +1 [label="1: Start PropertyImplSetter_setMaximumFileSize:\nFormals: self:class PropertyImplSetter * newMaximumFileSize:int \nLocals: \n DECLARE_LOCALS(&return); [line 10]\n NULLIFY(&newMaximumFileSize,false); [line 10]\n " color=yellow style=filled] + + + 1 -> 3 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/property/PropertyImplSetter.h b/infer/tests/codetoanalyze/objc/frontend/property/PropertyImplSetter.h new file mode 100644 index 000000000..83d67b802 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/property/PropertyImplSetter.h @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface PropertyImplSetter : NSObject + +@property (nonatomic) int maximumFileSize; + +@end diff --git a/infer/tests/codetoanalyze/objc/frontend/property/PropertyImplSetter.m b/infer/tests/codetoanalyze/objc/frontend/property/PropertyImplSetter.m new file mode 100644 index 000000000..c6ba77a6f --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/property/PropertyImplSetter.m @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import "PropertyImplSetter.h" + +@implementation PropertyImplSetter + +- (void)setMaximumFileSize:(int)newMaximumFileSize +{ + _maximumFileSize = 0; +} + +@end diff --git a/infer/tests/codetoanalyze/objc/frontend/property/Property_getter.dot b/infer/tests/codetoanalyze/objc/frontend/property/Property_getter.dot new file mode 100644 index 000000000..249215249 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/property/Property_getter.dot @@ -0,0 +1,35 @@ +digraph iCFG { +9 [label="9: BinaryOperatorStmt: Assign \n n$4=*&self:class A * [line -1]\n n$5=*&x:int [line -1]\n *n$4._x:int =n$5 [line -1]\n REMOVE_TEMPS(n$4,n$5); [line -1]\n NULLIFY(&self,false); [line -1]\n NULLIFY(&x,false); [line -1]\n APPLY_ABSTRACTION; [line -1]\n " shape="box"] + + + 9 -> 8 ; +8 [label="8: Exit A_setX: \n " color=yellow style=filled] + + +7 [label="7: Start A_setX:\nFormals: self:class A * x:int \nLocals: \n DECLARE_LOCALS(&return); [line -1]\n " color=yellow style=filled] + + + 7 -> 9 ; +6 [label="6: Return Stmt \n n$2=*&self:class A * [line -1]\n n$3=*n$2._x:int [line -1]\n *&return:int =n$3 [line -1]\n REMOVE_TEMPS(n$2,n$3); [line -1]\n NULLIFY(&self,false); [line -1]\n APPLY_ABSTRACTION; [line -1]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: Exit A_x \n " color=yellow style=filled] + + +4 [label="4: Start A_x\nFormals: self:class A *\nLocals: \n DECLARE_LOCALS(&return); [line -1]\n " color=yellow style=filled] + + + 4 -> 6 ; +3 [label="3: Return Stmt \n n$1=*&target:class A * [line 15]\n n$0=_fun_A_x(n$1:class A *) virtual [line 15]\n *&return:int =n$0 [line 15]\n REMOVE_TEMPS(n$0,n$1); [line 15]\n NULLIFY(&target,false); [line 15]\n APPLY_ABSTRACTION; [line 15]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit A_addTarget: \n " color=yellow style=filled] + + +1 [label="1: Start A_addTarget:\nFormals: self:class A * target:class A *\nLocals: \n DECLARE_LOCALS(&return); [line 14]\n NULLIFY(&self,false); [line 14]\n " color=yellow style=filled] + + + 1 -> 3 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/property/Property_getter.m b/infer/tests/codetoanalyze/objc/frontend/property/Property_getter.m new file mode 100644 index 000000000..d194e57b5 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/property/Property_getter.m @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface A : NSObject +@property int x; +@end + +@implementation A + +- (int)addTarget:(A*)target { + return target.x; +} + +@end diff --git a/infer/tests/codetoanalyze/objc/frontend/property/aclass.dot b/infer/tests/codetoanalyze/objc/frontend/property/aclass.dot new file mode 100644 index 000000000..9e7d8d8f7 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/property/aclass.dot @@ -0,0 +1,32 @@ +digraph iCFG { +8 [label="8: Message Call: retain \n n$8=*&aDynValue:class NSObject * [line -1]\n n$7=_fun___objc_retain(n$8:class NSObject *) [line -1]\n REMOVE_TEMPS(n$7,n$8); [line -1]\n " shape="box"] + + + 8 -> 7 ; +7 [label="7: Message Call: release \n n$5=*&self:class AClass * [line -1]\n n$6=*n$5.aDynValue:class NSObject * [line -1]\n n$4=_fun___objc_release(n$6:class NSObject *) [line -1]\n REMOVE_TEMPS(n$4,n$5,n$6); [line -1]\n " shape="box"] + + + 7 -> 6 ; +6 [label="6: BinaryOperatorStmt: Assign \n n$2=*&self:class AClass * [line -1]\n n$3=*&aDynValue:class NSObject * [line -1]\n *n$2.aDynValue:class NSObject *=n$3 [line -1]\n REMOVE_TEMPS(n$2,n$3); [line -1]\n NULLIFY(&aDynValue,false); [line -1]\n NULLIFY(&self,false); [line -1]\n APPLY_ABSTRACTION; [line -1]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: Exit AClass_setADynValue: \n " color=yellow style=filled] + + +4 [label="4: Start AClass_setADynValue:\nFormals: self:class AClass * aDynValue:class NSObject *\nLocals: \n DECLARE_LOCALS(&return); [line -1]\n " color=yellow style=filled] + + + 4 -> 8 ; +3 [label="3: Return Stmt \n n$0=*&self:class AClass * [line -1]\n n$1=*n$0.aDynValue:class NSObject * [line -1]\n *&return:class NSObject *=n$1 [line -1]\n REMOVE_TEMPS(n$0,n$1); [line -1]\n NULLIFY(&self,false); [line -1]\n APPLY_ABSTRACTION; [line -1]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit AClass_aDynValue \n " color=yellow style=filled] + + +1 [label="1: Start AClass_aDynValue\nFormals: self:class AClass *\nLocals: \n DECLARE_LOCALS(&return); [line -1]\n " color=yellow style=filled] + + + 1 -> 3 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/property/aclass.m b/infer/tests/codetoanalyze/objc/frontend/property/aclass.m new file mode 100644 index 000000000..9d97c040a --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/property/aclass.m @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface AClass : NSObject + @property (nonatomic, strong) NSObject* aDynValue; +@end + + +@implementation AClass + +@dynamic aDynValue; + +@end diff --git a/infer/tests/codetoanalyze/objc/frontend/property/main_car.dot b/infer/tests/codetoanalyze/objc/frontend/property/main_car.dot new file mode 100644 index 000000000..aea29b0d8 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/property/main_car.dot @@ -0,0 +1,25 @@ +digraph iCFG { +6 [label="6: DeclStmt \n n$6=_fun___objc_alloc_no_fail(sizeof(class Car ):class Car *) [line 9]\n n$4=_fun_Car_init(n$6:class Car *) virtual [line 9]\n *&honda:class Car *=n$4 [line 9]\n REMOVE_TEMPS(n$4,n$6); [line 9]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: Message Call: setRunning: \n n$3=*&honda:class Car * [line 10]\n _fun_Car_setRunning:(n$3:class Car *,1:signed char ) virtual [line 10]\n REMOVE_TEMPS(n$3); [line 10]\n " shape="box"] + + + 5 -> 4 ; +4 [label="4: Call _fun_NSLog \n n$0=_fun_NSString_stringWithUTF8String:(\"%d\":char *) [line 11]\n n$2=*&honda:class Car * [line 11]\n n$1=_fun_Car_running(n$2:class Car *) virtual [line 11]\n _fun_NSLog(n$0:struct objc_object *,n$1:int ) [line 11]\n REMOVE_TEMPS(n$0,n$1,n$2); [line 11]\n NULLIFY(&honda,false); [line 11]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n *&return:int =0 [line 12]\n APPLY_ABSTRACTION; [line 12]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: honda:class Car * \n DECLARE_LOCALS(&return,&honda); [line 8]\n NULLIFY(&honda,false); [line 8]\n " color=yellow style=filled] + + + 1 -> 6 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/property/main_car.m b/infer/tests/codetoanalyze/objc/frontend/property/main_car.m new file mode 100644 index 000000000..676bbebc6 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/property/main_car.m @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import "Car.h" + +int main() { + Car *honda = [[Car alloc] init]; + honda.running = YES; + NSLog(@"%d", honda.running); + return 0; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/property_in_protocol/MyProtocol.h b/infer/tests/codetoanalyze/objc/frontend/property_in_protocol/MyProtocol.h new file mode 100644 index 000000000..90ab27254 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/property_in_protocol/MyProtocol.h @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@protocol MyProtocol + +@property int numberOfFiles; + +@end diff --git a/infer/tests/codetoanalyze/objc/frontend/property_in_protocol/Test.dot b/infer/tests/codetoanalyze/objc/frontend/property_in_protocol/Test.dot new file mode 100644 index 000000000..27c271e6d --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/property_in_protocol/Test.dot @@ -0,0 +1,24 @@ +digraph iCFG { +6 [label="6: BinaryOperatorStmt: Assign \n n$2=*&self:class Test * [line -1]\n n$3=*&numberOfFiles:int [line -1]\n *n$2.numberOfFiles:int =n$3 [line -1]\n REMOVE_TEMPS(n$2,n$3); [line -1]\n NULLIFY(&numberOfFiles,false); [line -1]\n NULLIFY(&self,false); [line -1]\n APPLY_ABSTRACTION; [line -1]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: Exit Test_setNumberOfFiles: \n " color=yellow style=filled] + + +4 [label="4: Start Test_setNumberOfFiles:\nFormals: self:class Test * numberOfFiles:int \nLocals: \n DECLARE_LOCALS(&return); [line -1]\n " color=yellow style=filled] + + + 4 -> 6 ; +3 [label="3: Return Stmt \n n$0=*&self:class Test * [line -1]\n n$1=*n$0.numberOfFiles:int [line -1]\n *&return:int =n$1 [line -1]\n REMOVE_TEMPS(n$0,n$1); [line -1]\n NULLIFY(&self,false); [line -1]\n APPLY_ABSTRACTION; [line -1]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit Test_numberOfFiles \n " color=yellow style=filled] + + +1 [label="1: Start Test_numberOfFiles\nFormals: self:class Test *\nLocals: \n DECLARE_LOCALS(&return); [line -1]\n " color=yellow style=filled] + + + 1 -> 3 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/property_in_protocol/Test.h b/infer/tests/codetoanalyze/objc/frontend/property_in_protocol/Test.h new file mode 100644 index 000000000..4a754419d --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/property_in_protocol/Test.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import +#import "MyProtocol.h" + +@interface Test : NSObject { + + int numberOfFiles; + +} + +@end diff --git a/infer/tests/codetoanalyze/objc/frontend/property_in_protocol/Test.m b/infer/tests/codetoanalyze/objc/frontend/property_in_protocol/Test.m new file mode 100644 index 000000000..c14f4e2e7 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/property_in_protocol/Test.m @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import "Test.h" + +@implementation Test + +@synthesize numberOfFiles; + +@end diff --git a/infer/tests/codetoanalyze/objc/frontend/protocol/protocol.dot b/infer/tests/codetoanalyze/objc/frontend/protocol/protocol.dot new file mode 100644 index 000000000..9e8fa057c --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/protocol/protocol.dot @@ -0,0 +1,30 @@ +digraph iCFG { +7 [label="7: Return Stmt \n APPLY_ABSTRACTION; [line 23]\n " shape="box"] + + + 7 -> 2 ; +6 [label="6: Prune (false branch) \n PRUNE((n$0 == 0), false); [line 22]\n REMOVE_TEMPS(n$0,n$1); [line 22]\n " shape="invhouse"] + + + 6 -> 3 ; +5 [label="5: Prune (true branch) \n PRUNE((n$0 != 0), true); [line 22]\n REMOVE_TEMPS(n$0,n$1); [line 22]\n " shape="invhouse"] + + + 5 -> 7 ; +4 [label="4: Message Call: conformsToProtocol: \n n$1=*&self:class Bla * [line 22]\n n$0=_fun_Bla_conformsToProtocol:(n$1:class Bla *,\"Foo\":class Protocol *) virtual [line 22]\n NULLIFY(&self,false); [line 22]\n " shape="box"] + + + 4 -> 5 ; + 4 -> 6 ; +3 [label="3: + \n " ] + + + 3 -> 2 ; +2 [label="2: Exit Bla_fooMethod \n " color=yellow style=filled] + + +1 [label="1: Start Bla_fooMethod\nFormals: self:class Bla *\nLocals: \n DECLARE_LOCALS(&return); [line 21]\n " color=yellow style=filled] + + + 1 -> 4 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/protocol/protocol.m b/infer/tests/codetoanalyze/objc/frontend/protocol/protocol.m new file mode 100644 index 000000000..171b90da7 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/protocol/protocol.m @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@protocol Foo +- (void)fooMethod; + + @property (retain) NSString *foo; +@end + + +@interface Bla: NSObject +- (void)fooMethod; +@end + +@implementation Bla: NSObject + +- (void)fooMethod { + if ([self conformsToProtocol:@protocol(Foo)]) { + return; + } +} +@end diff --git a/infer/tests/codetoanalyze/objc/frontend/returnstmt/void_return.dot b/infer/tests/codetoanalyze/objc/frontend/returnstmt/void_return.dot new file mode 100644 index 000000000..160d478bd --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/returnstmt/void_return.dot @@ -0,0 +1,59 @@ +digraph iCFG { +14 [label="14: DeclStmt \n *&i:int =0 [line 15]\n " shape="box"] + + + 14 -> 13 ; +13 [label="13: DeclStmt \n *&j:int =0 [line 16]\n " shape="box"] + + + 13 -> 9 ; +12 [label="12: Return Stmt \n NULLIFY(&i,false); [line 18]\n NULLIFY(&j,false); [line 18]\n APPLY_ABSTRACTION; [line 18]\n " shape="box"] + + + 12 -> 2 ; +11 [label="11: Prune (false branch) \n PRUNE(((n$2 == 0) == 0), false); [line 17]\n REMOVE_TEMPS(n$2); [line 17]\n " shape="invhouse"] + + + 11 -> 8 ; +10 [label="10: Prune (true branch) \n PRUNE(((n$2 == 0) != 0), true); [line 17]\n REMOVE_TEMPS(n$2); [line 17]\n " shape="invhouse"] + + + 10 -> 12 ; +9 [label="9: BinaryOperatorStmt: EQ \n n$2=*&i:int [line 17]\n " shape="box"] + + + 9 -> 10 ; + 9 -> 11 ; +8 [label="8: + \n " ] + + + 8 -> 4 ; +7 [label="7: UnaryOperator \n n$1=*&i:int [line 22]\n *&i:int =(n$1 + 1) [line 22]\n REMOVE_TEMPS(n$1); [line 22]\n NULLIFY(&i,false); [line 22]\n APPLY_ABSTRACTION; [line 22]\n " shape="box"] + + + 7 -> 3 ; +6 [label="6: Prune (false branch) \n PRUNE(((n$0 == 0) == 0), false); [line 21]\n REMOVE_TEMPS(n$0); [line 21]\n APPLY_ABSTRACTION; [line 21]\n " shape="invhouse"] + + + 6 -> 3 ; +5 [label="5: Prune (true branch) \n PRUNE(((n$0 == 0) != 0), true); [line 21]\n REMOVE_TEMPS(n$0); [line 21]\n " shape="invhouse"] + + + 5 -> 7 ; +4 [label="4: BinaryOperatorStmt: EQ \n n$0=*&j:int [line 21]\n NULLIFY(&j,false); [line 21]\n " shape="box"] + + + 4 -> 5 ; + 4 -> 6 ; +3 [label="3: + \n NULLIFY(&i,false); [line 21]\n " ] + + + 3 -> 2 ; +2 [label="2: Exit MyClass_aMethod \n " color=yellow style=filled] + + +1 [label="1: Start MyClass_aMethod\nFormals: self:class MyClass *\nLocals: i:int j:int \n DECLARE_LOCALS(&return,&i,&j); [line 14]\n NULLIFY(&i,false); [line 14]\n NULLIFY(&j,false); [line 14]\n NULLIFY(&self,false); [line 14]\n " color=yellow style=filled] + + + 1 -> 14 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/returnstmt/void_return.m b/infer/tests/codetoanalyze/objc/frontend/returnstmt/void_return.m new file mode 100644 index 000000000..d7a9e0884 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/returnstmt/void_return.m @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface MyClass : NSObject + +@end + +@implementation MyClass + +- (void) aMethod { + int i = 0; + int j = 0; + if (i == 0) { + return; + } + + if(j == 0) { + i++; + } +} + +@end diff --git a/infer/tests/codetoanalyze/objc/frontend/self_static/Self.dot b/infer/tests/codetoanalyze/objc/frontend/self_static/Self.dot new file mode 100644 index 000000000..694063b21 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/self_static/Self.dot @@ -0,0 +1,191 @@ +digraph iCFG { +51 [label="51: Return Stmt \n *&return:int =0 [line 95]\n APPLY_ABSTRACTION; [line 95]\n " shape="box"] + + + 51 -> 45 ; +50 [label="50: Return Stmt \n *&return:int =1 [line 94]\n APPLY_ABSTRACTION; [line 94]\n " shape="box"] + + + 50 -> 45 ; +49 [label="49: Prune (false branch) \n PRUNE(((sizeof(class A ) != n$23) == 0), false); [line 94]\n REMOVE_TEMPS(n$23); [line 94]\n " shape="invhouse"] + + + 49 -> 51 ; +48 [label="48: Prune (true branch) \n PRUNE(((sizeof(class A ) != n$23) != 0), true); [line 94]\n REMOVE_TEMPS(n$23); [line 94]\n " shape="invhouse"] + + + 48 -> 50 ; +47 [label="47: BinaryOperatorStmt: NE \n n$23=*&c:struct objc_class * [line 94]\n NULLIFY(&c,false); [line 94]\n " shape="box"] + + + 47 -> 48 ; + 47 -> 49 ; +46 [label="46: + \n NULLIFY(&c,false); [line 94]\n " ] + + + 46 -> 45 ; +45 [label="45: Exit A_used_in_binary_op: \n " color=yellow style=filled] + + +44 [label="44: Start A_used_in_binary_op:\nFormals: c:struct objc_class *\nLocals: \n DECLARE_LOCALS(&return); [line 92]\n " color=yellow style=filled] + + + 44 -> 47 ; +43 [label="43: Return Stmt \n n$22=_fun_NSStringFromClass(sizeof(class A ):class A ) [line 89]\n *&return:class NSString *=n$22 [line 89]\n REMOVE_TEMPS(n$22); [line 89]\n APPLY_ABSTRACTION; [line 89]\n " shape="box"] + + + 43 -> 42 ; +42 [label="42: Exit A_loggerName \n " color=yellow style=filled] + + +41 [label="41: Start A_loggerName\nFormals: self:class A *\nLocals: \n DECLARE_LOCALS(&return); [line 87]\n NULLIFY(&self,false); [line 87]\n " color=yellow style=filled] + + + 41 -> 43 ; +40 [label="40: Message Call: init \n n$19=*&self:class A * [line 84]\n n$18=_fun_C_init(n$19:class A *) [line 84]\n REMOVE_TEMPS(n$18,n$19); [line 84]\n NULLIFY(&self,false); [line 84]\n APPLY_ABSTRACTION; [line 84]\n " shape="box"] + + + 40 -> 39 ; +39 [label="39: Exit A_init \n " color=yellow style=filled] + + +38 [label="38: Start A_init\nFormals: self:class A *\nLocals: \n DECLARE_LOCALS(&return); [line 83]\n " color=yellow style=filled] + + + 38 -> 40 ; +37 [label="37: Message Call: test_class \n _fun_C_test_class() [line 80]\n APPLY_ABSTRACTION; [line 80]\n " shape="box"] + + + 37 -> 36 ; +36 [label="36: Exit A_calling_super \n " color=yellow style=filled] + + +35 [label="35: Start A_calling_super\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 79]\n " color=yellow style=filled] + + + 35 -> 37 ; +34 [label="34: Return Stmt \n n$15=*&object:class B * [line 76]\n n$14=_fun_B_isC:(n$15:class B *,sizeof(class A ):class A ) virtual [line 76]\n *&return:signed char =n$14 [line 76]\n REMOVE_TEMPS(n$14,n$15); [line 76]\n NULLIFY(&object,false); [line 76]\n APPLY_ABSTRACTION; [line 76]\n " shape="box"] + + + 34 -> 33 ; +33 [label="33: Exit A_use_class_in_other_ways: \n " color=yellow style=filled] + + +32 [label="32: Start A_use_class_in_other_ways:\nFormals: self:class A * object:class B *\nLocals: \n DECLARE_LOCALS(&return); [line 75]\n NULLIFY(&self,false); [line 75]\n " color=yellow style=filled] + + + 32 -> 34 ; +31 [label="31: DeclStmt \n n$12=_fun___objc_alloc_no_fail(sizeof(class B ):class B *) [line 71]\n n$13=_fun_B_init(n$12:class B *) virtual [line 71]\n *&b:class B *=n$13 [line 71]\n REMOVE_TEMPS(n$12,n$13); [line 71]\n NULLIFY(&b,false); [line 71]\n " shape="box"] + + + 31 -> 30 ; +30 [label="30: Message Call: b_m \n _fun_B_b_m() [line 72]\n APPLY_ABSTRACTION; [line 72]\n " shape="box"] + + + 30 -> 29 ; +29 [label="29: Exit A_t \n " color=yellow style=filled] + + +28 [label="28: Start A_t\nFormals: self:class A *\nLocals: b:class B * \n DECLARE_LOCALS(&return,&b); [line 70]\n NULLIFY(&b,false); [line 70]\n NULLIFY(&self,false); [line 70]\n " color=yellow style=filled] + + + 28 -> 31 ; +27 [label="27: Message Call: test_class \n _fun_A_test_class() [line 67]\n APPLY_ABSTRACTION; [line 67]\n " shape="box"] + + + 27 -> 26 ; +26 [label="26: Exit A_call_class_instance_with_class_name \n " color=yellow style=filled] + + +25 [label="25: Start A_call_class_instance_with_class_name\nFormals: self:class A *\nLocals: \n DECLARE_LOCALS(&return); [line 66]\n NULLIFY(&self,false); [line 66]\n " color=yellow style=filled] + + + 25 -> 27 ; +24 [label="24: Message Call: test_class \n _fun_A_test_class() [line 63]\n APPLY_ABSTRACTION; [line 63]\n " shape="box"] + + + 24 -> 23 ; +23 [label="23: Exit A_call_class_instance \n " color=yellow style=filled] + + +22 [label="22: Start A_call_class_instance\nFormals: self:class A *\nLocals: \n DECLARE_LOCALS(&return); [line 62]\n NULLIFY(&self,false); [line 62]\n " color=yellow style=filled] + + + 22 -> 24 ; +21 [label="21: Call alloc \n n$6=_fun___objc_alloc_no_fail(sizeof(class A ):class A *) [line 59]\n REMOVE_TEMPS(n$6); [line 59]\n APPLY_ABSTRACTION; [line 59]\n " shape="box"] + + + 21 -> 20 ; +20 [label="20: Exit A_call_alloc_instance \n " color=yellow style=filled] + + +19 [label="19: Start A_call_alloc_instance\nFormals: self:class A *\nLocals: \n DECLARE_LOCALS(&return); [line 58]\n NULLIFY(&self,false); [line 58]\n " color=yellow style=filled] + + + 19 -> 21 ; +18 [label="18: Call alloc \n n$2=_fun___objc_alloc_no_fail(sizeof(class A ):class A *) [line 55]\n REMOVE_TEMPS(n$2); [line 55]\n APPLY_ABSTRACTION; [line 55]\n " shape="box"] + + + 18 -> 17 ; +17 [label="17: Exit A_call_alloc_class \n " color=yellow style=filled] + + +16 [label="16: Start A_call_alloc_class\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 54]\n " color=yellow style=filled] + + + 16 -> 18 ; +15 [label="15: Message Call: test_class \n _fun_A_test_class() [line 51]\n APPLY_ABSTRACTION; [line 51]\n " shape="box"] + + + 15 -> 14 ; +14 [label="14: Exit A_call_test_class \n " color=yellow style=filled] + + +13 [label="13: Start A_call_test_class\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 50]\n " color=yellow style=filled] + + + 13 -> 15 ; +12 [label="12: Exit A_test_class \n " color=yellow style=filled] + + +11 [label="11: Start A_test_class\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 47]\n " color=yellow style=filled] + + + 11 -> 12 ; +10 [label="10: Message Call: test \n n$0=*&self:class A * [line 44]\n _fun_A_test(n$0:class A *) virtual [line 44]\n REMOVE_TEMPS(n$0); [line 44]\n NULLIFY(&self,false); [line 44]\n APPLY_ABSTRACTION; [line 44]\n " shape="box"] + + + 10 -> 9 ; +9 [label="9: Exit A_call_test \n " color=yellow style=filled] + + +8 [label="8: Start A_call_test\nFormals: self:class A *\nLocals: \n DECLARE_LOCALS(&return); [line 43]\n " color=yellow style=filled] + + + 8 -> 10 ; +7 [label="7: Exit A_test \n " color=yellow style=filled] + + +6 [label="6: Start A_test\nFormals: self:class A *\nLocals: \n DECLARE_LOCALS(&return); [line 38]\n NULLIFY(&self,false); [line 38]\n " color=yellow style=filled] + + + 6 -> 7 ; +5 [label="5: Return Stmt \n *&return:signed char =1 [line 19]\n APPLY_ABSTRACTION; [line 19]\n " shape="box"] + + + 5 -> 4 ; +4 [label="4: Exit B_isC: \n " color=yellow style=filled] + + +3 [label="3: Start B_isC:\nFormals: self:class B * aClass:struct objc_class *\nLocals: \n DECLARE_LOCALS(&return); [line 18]\n NULLIFY(&aClass,false); [line 18]\n NULLIFY(&self,false); [line 18]\n " color=yellow style=filled] + + + 3 -> 5 ; +2 [label="2: Exit B_b_m \n " color=yellow style=filled] + + +1 [label="1: Start B_b_m\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 16]\n " color=yellow style=filled] + + + 1 -> 2 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/self_static/Self.m b/infer/tests/codetoanalyze/objc/frontend/self_static/Self.m new file mode 100644 index 000000000..65c22bfc0 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/self_static/Self.m @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface B : NSObject + +- (BOOL)isC:(Class)aClass; + +@end + +@implementation B : NSObject + ++(void) b_m {} + +- (BOOL)isC:(Class)aClass { + return TRUE; +} + +@end + +@interface C : NSObject + +-(void) test; + ++(void) test_class; + +@end + +@interface A : C + +@end + +@implementation A + +-(void) test { + +} + + +-(void) call_test { + [self test]; +} + ++(void) test_class { +} + ++(void) call_test_class { + [self test_class]; +} + ++(void) call_alloc_class { + [self alloc]; +} + +-(void) call_alloc_instance { + [[self class] alloc]; +} + +-(void) call_class_instance { + [[self class] test_class]; +} + +-(void) call_class_instance_with_class_name { + [A test_class]; +} + +-(void) t { + B *b = [B new]; + [[b class] b_m]; +} + +-(BOOL) use_class_in_other_ways:(B*) object { + return [object isC:[self class]]; +} + ++(void) calling_super { + [super test_class]; +} + +-(void) init { + [super init]; +} + +- (NSString *)loggerName +{ + return NSStringFromClass([self class]); +} + ++ (int)used_in_binary_op:(Class) c +{ + if (self != c) {return 1;} + else return 0; +} +@end diff --git a/infer/tests/codetoanalyze/objc/frontend/self_static/static.dot b/infer/tests/codetoanalyze/objc/frontend/self_static/static.dot new file mode 100644 index 000000000..7d8fae3b0 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/self_static/static.dot @@ -0,0 +1,57 @@ +digraph iCFG { +15 [label="15: Message Call: getX \n n$3=*&self:class MyClass * [line 33]\n n$2=_fun_MyClass_getX(n$3:class MyClass *) virtual [line 33]\n REMOVE_TEMPS(n$2,n$3); [line 33]\n NULLIFY(&self,false); [line 33]\n APPLY_ABSTRACTION; [line 33]\n " shape="box"] + + + 15 -> 14 ; +14 [label="14: Exit MyClass_anInstanceMethod2 \n " color=yellow style=filled] + + +13 [label="13: Start MyClass_anInstanceMethod2\nFormals: self:class MyClass *\nLocals: \n DECLARE_LOCALS(&return); [line 32]\n " color=yellow style=filled] + + + 13 -> 15 ; +12 [label="12: Return Stmt \n *&return:int =0 [line 29]\n APPLY_ABSTRACTION; [line 29]\n " shape="box"] + + + 12 -> 11 ; +11 [label="11: Exit MyClass_getX \n " color=yellow style=filled] + + +10 [label="10: Start MyClass_getX\nFormals: self:class MyClass *\nLocals: \n DECLARE_LOCALS(&return); [line 28]\n NULLIFY(&self,false); [line 28]\n " color=yellow style=filled] + + + 10 -> 12 ; +9 [label="9: Message Call: aClassMethod \n _fun_MyClass_aClassMethod() [line 25]\n APPLY_ABSTRACTION; [line 25]\n " shape="box"] + + + 9 -> 8 ; +8 [label="8: Exit MyClass_aClassMethod2 \n " color=yellow style=filled] + + +7 [label="7: Start MyClass_aClassMethod2\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 24]\n " color=yellow style=filled] + + + 7 -> 9 ; +6 [label="6: Message Call: aClassMethod \n _fun_MyClass_aClassMethod() [line 21]\n APPLY_ABSTRACTION; [line 21]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: Exit MyClass_anInstanceMethod \n " color=yellow style=filled] + + +4 [label="4: Start MyClass_anInstanceMethod\nFormals: self:class MyClass *\nLocals: \n DECLARE_LOCALS(&return); [line 20]\n NULLIFY(&self,false); [line 20]\n " color=yellow style=filled] + + + 4 -> 6 ; +3 [label="3: DeclStmt \n n$1=_fun___objc_alloc_no_fail(sizeof(class MyClass ):class MyClass *) [line 17]\n *&myClass:class MyClass *=n$1 [line 17]\n REMOVE_TEMPS(n$1); [line 17]\n NULLIFY(&myClass,false); [line 17]\n APPLY_ABSTRACTION; [line 17]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit MyClass_aClassMethod \n " color=yellow style=filled] + + +1 [label="1: Start MyClass_aClassMethod\nFormals: \nLocals: myClass:class MyClass * \n DECLARE_LOCALS(&return,&myClass); [line 16]\n NULLIFY(&myClass,false); [line 16]\n " color=yellow style=filled] + + + 1 -> 3 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/self_static/static.m b/infer/tests/codetoanalyze/objc/frontend/self_static/static.m new file mode 100644 index 000000000..303e2741c --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/self_static/static.m @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface MyClass : NSObject ++ (void)aClassMethod; +- (void)anInstanceMethod; ++ (void)aClassMethod2; +- (int) getX; +@end + +@implementation MyClass ++ (void)aClassMethod { + MyClass *myClass = [self alloc]; +} + +- (void)anInstanceMethod { + [MyClass aClassMethod]; +} + ++ (void)aClassMethod2 { + [self aClassMethod]; +} + +- (int) getX { + return 0; +} + +- (void)anInstanceMethod2 { + [self getX]; +} +@end diff --git a/infer/tests/codetoanalyze/objc/frontend/strings/global_string_literal.dot b/infer/tests/codetoanalyze/objc/frontend/strings/global_string_literal.dot new file mode 100644 index 000000000..3ff968df6 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/strings/global_string_literal.dot @@ -0,0 +1,13 @@ +digraph iCFG { +3 [label="3: Return Stmt \n *&return:int =0 [line 11]\n APPLY_ABSTRACTION; [line 11]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 10]\n " color=yellow style=filled] + + + 1 -> 3 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/strings/global_string_literal.m b/infer/tests/codetoanalyze/objc/frontend/strings/global_string_literal.m new file mode 100644 index 000000000..775802fb3 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/strings/global_string_literal.m @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +NSString *lastName = @"Rodriguez"; + +int main () { + return 0; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/strings/string_literal.dot b/infer/tests/codetoanalyze/objc/frontend/strings/string_literal.dot new file mode 100644 index 000000000..424568027 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/strings/string_literal.dot @@ -0,0 +1,17 @@ +digraph iCFG { +4 [label="4: DeclStmt \n n$0=_fun_NSString_stringWithUTF8String:(\"Rodriguez\":char *) [line 9]\n *&lastName:class NSString *=n$0 [line 9]\n REMOVE_TEMPS(n$0); [line 9]\n NULLIFY(&lastName,false); [line 9]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n *&return:int =0 [line 10]\n APPLY_ABSTRACTION; [line 10]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: lastName:class NSString * \n DECLARE_LOCALS(&return,&lastName); [line 8]\n NULLIFY(&lastName,false); [line 8]\n " color=yellow style=filled] + + + 1 -> 4 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/strings/string_literal.m b/infer/tests/codetoanalyze/objc/frontend/strings/string_literal.m new file mode 100644 index 000000000..620fa4515 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/strings/string_literal.m @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +int main () { + NSString *lastName = @"Rodriguez"; + return 0; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/subclass/A.h b/infer/tests/codetoanalyze/objc/frontend/subclass/A.h new file mode 100644 index 000000000..97776fa8b --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/subclass/A.h @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface A : NSObject { + int x; +} + +@end diff --git a/infer/tests/codetoanalyze/objc/frontend/subclass/A.m b/infer/tests/codetoanalyze/objc/frontend/subclass/A.m new file mode 100644 index 000000000..1b525e121 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/subclass/A.m @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@implementation A + +-(instancetype) init +{ + if ([super self]) { + self->x = 10; + } + return self; +} + +@end diff --git a/infer/tests/codetoanalyze/objc/frontend/subclass/MyClass.dot b/infer/tests/codetoanalyze/objc/frontend/subclass/MyClass.dot new file mode 100644 index 000000000..c5ad45f65 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/subclass/MyClass.dot @@ -0,0 +1,13 @@ +digraph iCFG { +3 [label="3: Return Stmt \n *&return:int =1 [line 11]\n APPLY_ABSTRACTION; [line 11]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit MyClass_myNumber \n " color=yellow style=filled] + + +1 [label="1: Start MyClass_myNumber\nFormals: self:class MyClass *\nLocals: \n DECLARE_LOCALS(&return); [line 10]\n NULLIFY(&self,false); [line 10]\n " color=yellow style=filled] + + + 1 -> 3 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/subclass/MyClass.h b/infer/tests/codetoanalyze/objc/frontend/subclass/MyClass.h new file mode 100644 index 000000000..a876c3133 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/subclass/MyClass.h @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface MyClass : NSObject { +} +- (int)myNumber; +@end diff --git a/infer/tests/codetoanalyze/objc/frontend/subclass/MyClass.m b/infer/tests/codetoanalyze/objc/frontend/subclass/MyClass.m new file mode 100644 index 000000000..f49828874 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/subclass/MyClass.m @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import "MyClass.h" + +@implementation MyClass : NSObject { +} +- (int)myNumber { + return 1; +} +@end diff --git a/infer/tests/codetoanalyze/objc/frontend/subclass/MySubClass.dot b/infer/tests/codetoanalyze/objc/frontend/subclass/MySubClass.dot new file mode 100644 index 000000000..f05d6997a --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/subclass/MySubClass.dot @@ -0,0 +1,17 @@ +digraph iCFG { +4 [label="4: DeclStmt \n n$2=*&self:class MySubclass * [line 13]\n n$1=_fun_MyClass_myNumber(n$2:class MySubclass *) [line 13]\n *&subclassNumber:int =(n$1 + 1) [line 13]\n REMOVE_TEMPS(n$1,n$2); [line 13]\n NULLIFY(&self,false); [line 13]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n n$0=*&subclassNumber:int [line 14]\n *&return:int =n$0 [line 14]\n REMOVE_TEMPS(n$0); [line 14]\n NULLIFY(&subclassNumber,false); [line 14]\n APPLY_ABSTRACTION; [line 14]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit MySubclass_myNumber \n " color=yellow style=filled] + + +1 [label="1: Start MySubclass_myNumber\nFormals: self:class MySubclass *\nLocals: subclassNumber:int \n DECLARE_LOCALS(&return,&subclassNumber); [line 11]\n NULLIFY(&subclassNumber,false); [line 11]\n " color=yellow style=filled] + + + 1 -> 4 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/subclass/MySubClass.h b/infer/tests/codetoanalyze/objc/frontend/subclass/MySubClass.h new file mode 100644 index 000000000..3b52328fe --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/subclass/MySubClass.h @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import "MyClass.h" + +@interface MySubclass : MyClass { +} +- (int)myNumber; +@end + diff --git a/infer/tests/codetoanalyze/objc/frontend/subclass/MySubClass.m b/infer/tests/codetoanalyze/objc/frontend/subclass/MySubClass.m new file mode 100644 index 000000000..7ba5f5b8f --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/subclass/MySubClass.m @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import "MySubClass.h" + +@implementation MySubclass : MyClass { +} + +- (int)myNumber { + +int subclassNumber = [super myNumber] + 1; + return subclassNumber; +} + +@end diff --git a/infer/tests/codetoanalyze/objc/frontend/subclass/main.c b/infer/tests/codetoanalyze/objc/frontend/subclass/main.c new file mode 100644 index 000000000..6eb6d6b35 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/subclass/main.c @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import "A.h" + +int main() { + A* a = [[A alloc] init]; + return 0; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/subclass/main.dot b/infer/tests/codetoanalyze/objc/frontend/subclass/main.dot new file mode 100644 index 000000000..ed9d5e5c9 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/subclass/main.dot @@ -0,0 +1,17 @@ +digraph iCFG { +4 [label="4: DeclStmt \n n$2=_fun___objc_alloc_no_fail(sizeof(class A ):class A *) [line 9]\n n$0=_fun_A_init(n$2:class A *) virtual [line 9]\n *&a:class A *=n$0 [line 9]\n REMOVE_TEMPS(n$0,n$2); [line 9]\n NULLIFY(&a,false); [line 9]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n *&return:int =0 [line 10]\n APPLY_ABSTRACTION; [line 10]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: a:class A * \n DECLARE_LOCALS(&return,&a); [line 8]\n NULLIFY(&a,false); [line 8]\n " color=yellow style=filled] + + + 1 -> 4 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/types/attributes.dot b/infer/tests/codetoanalyze/objc/frontend/types/attributes.dot new file mode 100644 index 000000000..8f4b1d90d --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/types/attributes.dot @@ -0,0 +1,57 @@ +digraph iCFG { +14 [label="14: DeclStmt \n *&aWeakRef:class A __weak *=0 [line 18]\n " shape="box"] + + + 14 -> 13 ; +13 [label="13: DeclStmt \n _fun___objc_retain(0:class A *) [line 19]\n *&aStrongRef:class A *=0 [line 19]\n NULLIFY(&aStrongRef,false); [line 19]\n " shape="box"] + + + 13 -> 12 ; +12 [label="12: DeclStmt \n *&anUnsafeUnretRef:class A __unsafe_unretained *=0 [line 20]\n " shape="box"] + + + 12 -> 11 ; +11 [label="11: DeclStmt \n _fun___objc_retain(0:class A __autoreleasing *) [line 21]\n _fun___set_autorelease_attribute(0:class A __autoreleasing *) [line 21]\n *&anAutoRelRef:class A __autoreleasing *=0 [line 21]\n " shape="box"] + + + 11 -> 10 ; +10 [label="10: DeclStmt \n _fun___objc_retain(0:class A *) [line 22]\n *&aStdRef:class A *=0 [line 22]\n " shape="box"] + + + 10 -> 9 ; +9 [label="9: BinaryOperatorStmt: Assign \n n$10=_fun___objc_alloc_no_fail(sizeof(class A ):class A *) [line 25]\n *&aStrongRef:class A *=n$10 [line 25]\n REMOVE_TEMPS(n$10); [line 25]\n " shape="box"] + + + 9 -> 8 ; +8 [label="8: BinaryOperatorStmt: Assign \n n$7=*&aStrongRef:class A * [line 27]\n _fun___objc_retain(n$7:class A *) [line 27]\n n$8=*&aStdRef:class A * [line 27]\n *&aStdRef:class A *=n$7 [line 27]\n _fun___objc_release(n$8:class A *) [line 27]\n REMOVE_TEMPS(n$7,n$8); [line 27]\n " shape="box"] + + + 8 -> 7 ; +7 [label="7: BinaryOperatorStmt: Assign \n _fun___objc_retain(0:class A *) [line 29]\n n$6=*&aStrongRef:class A * [line 29]\n *&aStrongRef:class A *=0 [line 29]\n _fun___objc_release(n$6:class A *) [line 29]\n REMOVE_TEMPS(n$6); [line 29]\n NULLIFY(&aStrongRef,false); [line 29]\n " shape="box"] + + + 7 -> 6 ; +6 [label="6: BinaryOperatorStmt: Assign \n n$4=*&aStdRef:class A * [line 31]\n _fun___objc_retain(n$4:class A *) [line 31]\n n$5=*&aWeakRef:class A * [line 31]\n *&aWeakRef:class A *=n$4 [line 31]\n _fun___objc_release(n$5:class A *) [line 31]\n REMOVE_TEMPS(n$4,n$5); [line 31]\n NULLIFY(&aWeakRef,false); [line 31]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: BinaryOperatorStmt: Assign \n n$2=*&aStdRef:class A * [line 33]\n _fun___objc_retain(n$2:class A *) [line 33]\n n$3=*&anAutoRelRef:class A * [line 33]\n *&anAutoRelRef:class A *=n$2 [line 33]\n _fun___objc_release(n$3:class A *) [line 33]\n REMOVE_TEMPS(n$2,n$3); [line 33]\n NULLIFY(&anAutoRelRef,false); [line 33]\n " shape="box"] + + + 5 -> 4 ; +4 [label="4: BinaryOperatorStmt: Assign \n n$0=*&aStdRef:class A * [line 35]\n _fun___objc_retain(n$0:class A *) [line 35]\n n$1=*&anUnsafeUnretRef:class A * [line 35]\n *&anUnsafeUnretRef:class A *=n$0 [line 35]\n _fun___objc_release(n$1:class A *) [line 35]\n REMOVE_TEMPS(n$0,n$1); [line 35]\n NULLIFY(&aStdRef,false); [line 35]\n NULLIFY(&anUnsafeUnretRef,false); [line 35]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: Return Stmt \n *&return:int =0 [line 39]\n APPLY_ABSTRACTION; [line 39]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: aWeakRef:class A __weak * aStrongRef:class A * anUnsafeUnretRef:class A __unsafe_unretained * anAutoRelRef:class A __autoreleasing * aStdRef:class A * \n DECLARE_LOCALS(&return,&aWeakRef,&aStrongRef,&anUnsafeUnretRef,&anAutoRelRef,&aStdRef); [line 16]\n NULLIFY(&aStdRef,false); [line 16]\n NULLIFY(&aStrongRef,false); [line 16]\n NULLIFY(&aWeakRef,false); [line 16]\n NULLIFY(&anAutoRelRef,false); [line 16]\n NULLIFY(&anUnsafeUnretRef,false); [line 16]\n " color=yellow style=filled] + + + 1 -> 14 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/types/attributes.m b/infer/tests/codetoanalyze/objc/frontend/types/attributes.m new file mode 100644 index 000000000..b730c03e1 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/types/attributes.m @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface A : NSObject + +@end + +@implementation A + +@end + +int main () { + + A * __weak aWeakRef =0; + A * __strong aStrongRef =0; + A * __unsafe_unretained anUnsafeUnretRef =0; + A * __autoreleasing anAutoRelRef =0; + A * aStdRef =0; + + // interaction with __strong + aStrongRef=[A alloc]; + // counter =1 + aStdRef =aStrongRef; + // counter = 2 + aStrongRef=0; + // counter =1 + aWeakRef = aStdRef; + // counter =1 + anAutoRelRef = aStdRef; + //counter=2 + anUnsafeUnretRef = aStdRef; + //counter=2 + + + return 0; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/types/testloop.dot b/infer/tests/codetoanalyze/objc/frontend/types/testloop.dot new file mode 100644 index 000000000..f57db662f --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/types/testloop.dot @@ -0,0 +1,13 @@ +digraph iCFG { +3 [label="3: Return Stmt \n n$0=*&#GB$__iPhoneVideoAdLayout:struct FBVideoAdLayout [line 46]\n *&return:struct FBVideoAdLayout =n$0 [line 46]\n REMOVE_TEMPS(n$0); [line 46]\n APPLY_ABSTRACTION; [line 46]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit FBScrollViewDelegateProxy_layoutToUse \n " color=yellow style=filled] + + +1 [label="1: Start FBScrollViewDelegateProxy_layoutToUse\nFormals: \nLocals: \n DECLARE_LOCALS(&return); [line 44]\n " color=yellow style=filled] + + + 1 -> 3 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/types/testloop.m b/infer/tests/codetoanalyze/objc/frontend/types/testloop.m new file mode 100644 index 000000000..f91ed1125 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/types/testloop.m @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface FBScrollViewDelegateProxy : NSObject +@end + +@implementation FBScrollViewDelegateProxy + +typedef struct +{ + float placeHolderWidth; + float placeHolderHeight; + float contentLeftSidePadding; + float contentRightSidePadding; + float additionalPlaceholderOffset; + float contentGap; +} FBVideoAdLayout; + + +static const FBVideoAdLayout __iPadVideoAdLayout = +{ + .placeHolderWidth = 554, + .placeHolderHeight = 350, + .contentLeftSidePadding = 140, + .contentRightSidePadding = 60, + .additionalPlaceholderOffset = 40, + .contentGap = 11, +}; + +static const FBVideoAdLayout __iPhoneVideoAdLayout = +{ + .placeHolderWidth = 244, + .placeHolderHeight = 175, + .contentLeftSidePadding = 20, + .contentRightSidePadding = 20, + .additionalPlaceholderOffset = 0, + .contentGap = 7, +}; + ++ (FBVideoAdLayout)layoutToUse +{ + return __iPhoneVideoAdLayout; +} + +@end diff --git a/infer/tests/codetoanalyze/objc/frontend/types/void_call.dot b/infer/tests/codetoanalyze/objc/frontend/types/void_call.dot new file mode 100644 index 000000000..0be650b76 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/types/void_call.dot @@ -0,0 +1,94 @@ +digraph iCFG { +24 [label="24: DeclStmt \n *&x:int =1 [line 39]\n " shape="box"] + + + 24 -> 23 ; +23 [label="23: Call _fun_foo1 \n n$14=*&x:int [line 40]\n _fun_foo1(n$14:int ) [line 40]\n REMOVE_TEMPS(n$14); [line 40]\n " shape="box"] + + + 23 -> 22 ; +22 [label="22: BinaryOperatorStmt: Assign \n n$12=*&x:int [line 42]\n n$13=_fun_bar1(n$12:int ) [line 42]\n *&x:int =n$13 [line 42]\n REMOVE_TEMPS(n$12,n$13); [line 42]\n " shape="box"] + + + 22 -> 21 ; +21 [label="21: DeclStmt \n n$11=_fun___objc_alloc_no_fail(sizeof(class AClass ):class AClass *) [line 44]\n *&o:class AClass *=n$11 [line 44]\n REMOVE_TEMPS(n$11); [line 44]\n " shape="box"] + + + 21 -> 17 ; + 21 -> 18 ; +20 [label="20: Message Call: foo: \n n$8=*&o:class AClass * [line 48]\n n$9=*&x:int [line 48]\n _fun_AClass_foo:(n$8:class AClass *,n$9:int ) virtual [line 48]\n REMOVE_TEMPS(n$8,n$9); [line 48]\n " shape="box"] + + + 20 -> 19 ; +19 [label="19: BinaryOperatorStmt: Assign \n n$6=*&o:class AClass * [line 49]\n n$7=*&x:int [line 49]\n n$5=_fun_AClass_bar:(n$6:class AClass *,n$7:int ) virtual [line 49]\n *&x:int =n$5 [line 49]\n REMOVE_TEMPS(n$5,n$6,n$7); [line 49]\n NULLIFY(&o,false); [line 49]\n NULLIFY(&x,false); [line 49]\n APPLY_ABSTRACTION; [line 49]\n " shape="box"] + + + 19 -> 16 ; +18 [label="18: Prune (false branch) \n n$4=*&o:class AClass * [line 46]\n PRUNE((n$4 == 0), false); [line 46]\n REMOVE_TEMPS(n$4); [line 46]\n APPLY_ABSTRACTION; [line 46]\n " shape="invhouse"] + + + 18 -> 16 ; +17 [label="17: Prune (true branch) \n n$4=*&o:class AClass * [line 46]\n PRUNE((n$4 != 0), true); [line 46]\n REMOVE_TEMPS(n$4); [line 46]\n " shape="invhouse"] + + + 17 -> 20 ; +16 [label="16: + \n " ] + + + 16 -> 15 ; +15 [label="15: Return Stmt \n NULLIFY(&o,false); [line 53]\n NULLIFY(&x,false); [line 53]\n *&return:int =0 [line 53]\n APPLY_ABSTRACTION; [line 53]\n " shape="box"] + + + 15 -> 14 ; +14 [label="14: Exit main \n " color=yellow style=filled] + + +13 [label="13: Start main\nFormals: \nLocals: x:int o:class AClass * \n DECLARE_LOCALS(&return,&x,&o); [line 37]\n NULLIFY(&o,false); [line 37]\n NULLIFY(&x,false); [line 37]\n " color=yellow style=filled] + + + 13 -> 24 ; +12 [label="12: Return Stmt \n n$3=*&a:int [line 34]\n *&a:int =(n$3 + 1) [line 34]\n *&return:int =n$3 [line 34]\n REMOVE_TEMPS(n$3); [line 34]\n NULLIFY(&a,false); [line 34]\n APPLY_ABSTRACTION; [line 34]\n " shape="box"] + + + 12 -> 11 ; +11 [label="11: Exit bar1 \n " color=yellow style=filled] + + +10 [label="10: Start bar1\nFormals: a:int \nLocals: \n DECLARE_LOCALS(&return); [line 32]\n " color=yellow style=filled] + + + 10 -> 12 ; +9 [label="9: UnaryOperator \n n$2=*&a:int [line 29]\n *&a:int =(n$2 + 1) [line 29]\n REMOVE_TEMPS(n$2); [line 29]\n NULLIFY(&a,false); [line 29]\n APPLY_ABSTRACTION; [line 29]\n " shape="box"] + + + 9 -> 8 ; +8 [label="8: Exit foo1 \n " color=yellow style=filled] + + +7 [label="7: Start foo1\nFormals: a:int \nLocals: \n DECLARE_LOCALS(&return); [line 28]\n " color=yellow style=filled] + + + 7 -> 9 ; +6 [label="6: Return Stmt \n n$1=*&a:int [line 21]\n *&a:int =(n$1 + 1) [line 21]\n *&return:int =n$1 [line 21]\n REMOVE_TEMPS(n$1); [line 21]\n NULLIFY(&a,false); [line 21]\n APPLY_ABSTRACTION; [line 21]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: Exit AClass_bar: \n " color=yellow style=filled] + + +4 [label="4: Start AClass_bar:\nFormals: self:class AClass * a:int \nLocals: \n DECLARE_LOCALS(&return); [line 20]\n NULLIFY(&self,false); [line 20]\n " color=yellow style=filled] + + + 4 -> 6 ; +3 [label="3: UnaryOperator \n n$0=*&a:int [line 18]\n *&a:int =(n$0 + 1) [line 18]\n REMOVE_TEMPS(n$0); [line 18]\n NULLIFY(&a,false); [line 18]\n APPLY_ABSTRACTION; [line 18]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit AClass_foo: \n " color=yellow style=filled] + + +1 [label="1: Start AClass_foo:\nFormals: self:class AClass * a:int \nLocals: \n DECLARE_LOCALS(&return); [line 17]\n NULLIFY(&self,false); [line 17]\n " color=yellow style=filled] + + + 1 -> 3 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/types/void_call.m b/infer/tests/codetoanalyze/objc/frontend/types/void_call.m new file mode 100644 index 000000000..18db563f1 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/types/void_call.m @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface AClass : NSObject { +} +- (void) foo: (int)a; +- (int) bar: (int)a; +@end + + +@implementation AClass + +- (void) foo: (int)a { + a++; +} +- (int) bar: (int)a { + return a++; +} + +@end + + + +void foo1(int a) { + a++; +} + +int bar1(int a) { + + return a++; +} + +int main() { + + int x=1; + foo1(x); + + x=bar1(x); + + AClass* o =[AClass alloc]; + + if (o) { + + [o foo:x]; + x=[o bar:x]; + + } + + return 0; + +} diff --git a/infer/tests/codetoanalyze/objc/frontend/vardecl/aclass.dot b/infer/tests/codetoanalyze/objc/frontend/vardecl/aclass.dot new file mode 100644 index 000000000..a2438a41b --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/vardecl/aclass.dot @@ -0,0 +1,13 @@ +digraph iCFG { +3 [label="3: Return Stmt \n n$0=*&#GB$aVariable:class NSObject * [line 19]\n *&return:class NSObject *=n$0 [line 19]\n REMOVE_TEMPS(n$0); [line 19]\n APPLY_ABSTRACTION; [line 19]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit AClass_sharedInstance \n " color=yellow style=filled] + + +1 [label="1: Start AClass_sharedInstance\nFormals: self:class AClass *\nLocals: \n DECLARE_LOCALS(&return); [line 17]\n NULLIFY(&self,false); [line 17]\n " color=yellow style=filled] + + + 1 -> 3 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/vardecl/aclass.m b/infer/tests/codetoanalyze/objc/frontend/vardecl/aclass.m new file mode 100644 index 000000000..0f1f64302 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/vardecl/aclass.m @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface AClass : NSObject { +} + - (NSObject *)sharedInstance; +@end + + +@implementation AClass +static NSObject *aVariable; + +- (NSObject *)sharedInstance +{ + return aVariable; +} + +@end diff --git a/infer/tests/codetoanalyze/objc/frontend/vardecl/aclass_2.dot b/infer/tests/codetoanalyze/objc/frontend/vardecl/aclass_2.dot new file mode 100644 index 000000000..a2438a41b --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/vardecl/aclass_2.dot @@ -0,0 +1,13 @@ +digraph iCFG { +3 [label="3: Return Stmt \n n$0=*&#GB$aVariable:class NSObject * [line 19]\n *&return:class NSObject *=n$0 [line 19]\n REMOVE_TEMPS(n$0); [line 19]\n APPLY_ABSTRACTION; [line 19]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit AClass_sharedInstance \n " color=yellow style=filled] + + +1 [label="1: Start AClass_sharedInstance\nFormals: self:class AClass *\nLocals: \n DECLARE_LOCALS(&return); [line 17]\n NULLIFY(&self,false); [line 17]\n " color=yellow style=filled] + + + 1 -> 3 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/vardecl/aclass_2.m b/infer/tests/codetoanalyze/objc/frontend/vardecl/aclass_2.m new file mode 100644 index 000000000..2afc1cec4 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/vardecl/aclass_2.m @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +@interface AClass : NSObject { +} + - (NSObject *)sharedInstance; +@end + + +@implementation AClass +NSObject *aVariable; + +- (NSObject *)sharedInstance +{ + return aVariable; +} + +@end diff --git a/infer/tests/codetoanalyze/objc/frontend/vardecl/initlist.dot b/infer/tests/codetoanalyze/objc/frontend/vardecl/initlist.dot new file mode 100644 index 000000000..25c2263da --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/vardecl/initlist.dot @@ -0,0 +1,32 @@ +digraph iCFG { +8 [label="8: DeclStmt \n n$8=_fun___objc_alloc_no_fail(sizeof(class C ):class C *) [line 18]\n *&c1:class C *=n$8 [line 18]\n REMOVE_TEMPS(n$8); [line 18]\n " shape="box"] + + + 8 -> 7 ; +7 [label="7: DeclStmt \n n$6=_fun___objc_alloc_no_fail(sizeof(class C ):class C *) [line 19]\n *&c2:class C *=n$6 [line 19]\n REMOVE_TEMPS(n$6); [line 19]\n " shape="box"] + + + 7 -> 6 ; +6 [label="6: InitListExp \n n$2=*&c1:class C * [line 20]\n n$1=_fun_C_init(n$2:class C *) virtual [line 20]\n n$3=*&c1:class C * [line 20]\n n$4=*&c2:class C * [line 20]\n *&a[0]:C *=n$1 [line 20]\n _fun___objc_retain(n$3:C *) [line 20]\n *&a[1]:C *=n$3 [line 20]\n _fun___objc_retain(n$4:C *) [line 20]\n *&a[2]:C *=n$4 [line 20]\n REMOVE_TEMPS(n$1,n$2,n$3,n$4); [line 20]\n NULLIFY(&c1,false); [line 20]\n NULLIFY(&c2,false); [line 20]\n APPLY_ABSTRACTION; [line 20]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: Exit test \n " color=yellow style=filled] + + +4 [label="4: Start test\nFormals: \nLocals: c1:class C * c2:class C * a:C *[3] \n DECLARE_LOCALS(&return,&c1,&c2,&a); [line 17]\n NULLIFY(&a,false); [line 17]\n NULLIFY(&c1,false); [line 17]\n NULLIFY(&c2,false); [line 17]\n " color=yellow style=filled] + + + 4 -> 8 ; +3 [label="3: InitListExp \n n$0=*&z:int [line 10]\n *&a[0][0]:int =(n$0 + 1) [line 10]\n *&a[0][1]:int =2 [line 10]\n *&a[0][2]:int =3 [line 10]\n *&a[1][0]:int =5 [line 10]\n *&a[1][1]:int =6 [line 10]\n *&a[1][2]:int =7 [line 10]\n REMOVE_TEMPS(n$0); [line 10]\n NULLIFY(&z,false); [line 10]\n APPLY_ABSTRACTION; [line 10]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: z:int a:int [2][3] \n DECLARE_LOCALS(&return,&z,&a); [line 8]\n NULLIFY(&a,false); [line 8]\n " color=yellow style=filled] + + + 1 -> 3 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/vardecl/initlist.m b/infer/tests/codetoanalyze/objc/frontend/vardecl/initlist.m new file mode 100644 index 000000000..d0b22f6aa --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/vardecl/initlist.m @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +int main() { + int z; + int a[2][3] = {{z+1, 2, 3}, {5,6,7}}; +} + +@interface C : NSObject + +@end + +int test() { + C *c1 = [C alloc]; + C *c2 = [C alloc]; + C* a[3] = {[c1 init], c1, c2}; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/vardecl/last_af.dot b/infer/tests/codetoanalyze/objc/frontend/vardecl/last_af.dot new file mode 100644 index 000000000..363517436 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/vardecl/last_af.dot @@ -0,0 +1,17 @@ +digraph iCFG { +4 [label="4: DeclStmt \n *&a:int =0 [line 7]\n " shape="box"] + + + 4 -> 3 ; +3 [label="3: DeclStmt \n n$0=*&a:int [line 7]\n *&b:int =(n$0 + 2) [line 7]\n REMOVE_TEMPS(n$0); [line 7]\n NULLIFY(&a,false); [line 7]\n NULLIFY(&b,false); [line 7]\n APPLY_ABSTRACTION; [line 7]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit main \n " color=yellow style=filled] + + +1 [label="1: Start main\nFormals: \nLocals: a:int b:int \n DECLARE_LOCALS(&return,&a,&b); [line 6]\n NULLIFY(&a,false); [line 6]\n NULLIFY(&b,false); [line 6]\n " color=yellow style=filled] + + + 1 -> 4 ; +} diff --git a/infer/tests/codetoanalyze/objc/frontend/vardecl/last_af.m b/infer/tests/codetoanalyze/objc/frontend/vardecl/last_af.m new file mode 100644 index 000000000..f02aef637 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/frontend/vardecl/last_af.m @@ -0,0 +1,8 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +int main() { + int a = 0, b = a + 2; +} diff --git a/infer/tests/codetoanalyze/objc/warnings/ParameterNotNullableExample.m b/infer/tests/codetoanalyze/objc/warnings/ParameterNotNullableExample.m new file mode 100644 index 000000000..2f6f62e07 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/warnings/ParameterNotNullableExample.m @@ -0,0 +1,62 @@ +/* +* Copyright (c) 2015- Facebook. +* All rights reserved. +*/ +#import + +typedef struct { + int queue; +} FBAudioRecordState; + +@interface FBAudioRecorder : NSObject + +@property (nonatomic, assign) id delegate; +@property (nonatomic, assign) FBAudioRecordState *recordState; +@property (nonatomic, assign) FBAudioRecorder *recorder; + +@end + + +@implementation FBAudioRecorder { + int x; +} + +-(int) FBAudioInputCallbackSimple:(FBAudioRecorder *)rec +{ + FBAudioRecordState *recordState = rec.recordState; + return recordState->queue; +} + +-(int) FBAudioInputCallbackSimpleAliasing:(FBAudioRecorder *)userdata +{ + FBAudioRecorder *recorder = userdata; + FBAudioRecordState *recordState = recorder.recordState; + return recordState->queue; +} + +-(int) FBAudioInputCallbackField +{ + FBAudioRecordState *recordState = _recorder->_recorder.recordState; + return recordState->queue; +} + +-(int) FBAudioInputCallbackChain:(FBAudioRecorder *)rec +{ + FBAudioRecordState *recordState = rec.recorder.recordState; + return recordState->queue; +} + +-(int) test +{ + FBAudioRecorder *rec = nil; + FBAudioRecordState *recordState = rec.recordState; + return recordState->queue; +} + +-(instancetype) init { + self = [super init]; + self->x = 0; + return self; +} + +@end diff --git a/infer/tests/codetoanalyze/objcpp/frontend/funcoverloading/af_test.dot b/infer/tests/codetoanalyze/objcpp/frontend/funcoverloading/af_test.dot new file mode 100644 index 000000000..06a30faf3 --- /dev/null +++ b/infer/tests/codetoanalyze/objcpp/frontend/funcoverloading/af_test.dot @@ -0,0 +1,24 @@ +digraph iCFG { +6 [label="6: Return Stmt \n n$0=*&v:int [line 15]\n *&return:int =n$0 [line 15]\n REMOVE_TEMPS(n$0); [line 15]\n NULLIFY(&v,false); [line 15]\n APPLY_ABSTRACTION; [line 15]\n " shape="box"] + + + 6 -> 5 ; +5 [label="5: Exit POPSelectValueType \n " color=yellow style=filled] + + +4 [label="4: Start POPSelectValueType\nFormals: v:int \nLocals: \n DECLARE_LOCALS(&return); [line 13]\n " color=yellow style=filled] + + + 4 -> 6 ; +3 [label="3: Return Stmt \n *&return:int =1 [line 10]\n APPLY_ABSTRACTION; [line 10]\n " shape="box"] + + + 3 -> 2 ; +2 [label="2: Exit POPSelectValueType \n " color=yellow style=filled] + + +1 [label="1: Start POPSelectValueType\nFormals: obj:struct objc_object *\nLocals: \n DECLARE_LOCALS(&return); [line 8]\n NULLIFY(&obj,false); [line 8]\n " color=yellow style=filled] + + + 1 -> 3 ; +} diff --git a/infer/tests/codetoanalyze/objcpp/frontend/funcoverloading/af_test.mm b/infer/tests/codetoanalyze/objcpp/frontend/funcoverloading/af_test.mm new file mode 100644 index 000000000..9354baf91 --- /dev/null +++ b/infer/tests/codetoanalyze/objcpp/frontend/funcoverloading/af_test.mm @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2014 - Facebook. + * All rights reserved. + */ + +#import + +int POPSelectValueType(id obj) +{ + return 1; +} + +int POPSelectValueType(int v) +{ + return v; +} diff --git a/infer/tests/endtoend/BUCK b/infer/tests/endtoend/BUCK new file mode 100644 index 000000000..c1a2615dc --- /dev/null +++ b/infer/tests/endtoend/BUCK @@ -0,0 +1,102 @@ +tests_dependencies = [ + '//infer/lib/java/android:android', + '//dependencies/java/guava:guava', + '//dependencies/java/junit:hamcrest', + '//dependencies/java/jackson:jackson', + '//dependencies/java/jsr-305:jsr-305', + '//dependencies/java/junit:junit', + '//dependencies/java/opencsv:opencsv', + '//infer/tests/utils:utils', + '//infer/tests/codetoanalyze/java/checkers:checkers', + '//infer/tests/codetoanalyze/java/eradicate:eradicate', + '//infer/tests/codetoanalyze/java/infer:infer', + '//infer/tests/codetoanalyze/java/tracing:tracing', +] + +integration_tests = [ + '//infer/tests:integration_tests', + '//infer/tests:objc_tests', + '//infer/tests:c_tests', + '//infer/tests:cpp_tests', + '//infer/tests:objcpp_tests', +] + +# ############### ObjC tests endtoend ######################## +objc_test_sources = glob(['objc/**/*.java']) +objc_endtoend_test_deps = [] +for test_source in objc_test_sources: + target_name = test_source.replace("/", "_")[:-len(".java")] + objc_endtoend_test_deps.append(target_name) + + java_test( + name=target_name, + srcs=[test_source], + deps=tests_dependencies, + visibility=integration_tests, + source='7', + target='7', + ) + +java_test( + name='objc_endtoend_tests', + deps=[':' + x for x in objc_endtoend_test_deps], + visibility=integration_tests, +) + +# ############### Cpp endtoend tests ######################## +objc_test_sources = glob(['cpp/**/*.java']) +objc_endtoend_test_deps = [] +for test_source in objc_test_sources: + target_name = test_source.replace("/", "_")[:-len(".java")] + objc_endtoend_test_deps.append(target_name) + + java_test( + name=target_name, + srcs=[test_source], + deps=tests_dependencies, + visibility=integration_tests, + source='7', + target='7', + ) + +java_test( + name='cpp_endtoend_tests', + deps=[':' + x for x in objc_endtoend_test_deps], + visibility=integration_tests, +) + +# ############### ObjCpp endtoend tests ######################## +objc_test_sources = glob(['objcpp/**/*.java']) +objc_endtoend_test_deps = [] +for test_source in objc_test_sources: + target_name = test_source.replace("/", "_")[:-len(".java")] + objc_endtoend_test_deps.append(target_name) + + java_test( + name=target_name, + srcs=[test_source], + deps=tests_dependencies, + visibility=integration_tests, + source='7', + target='7', + ) + +java_test( + name='objcpp_endtoend_tests', + deps=[':' + x for x in objc_endtoend_test_deps], + visibility=integration_tests, +) + +# ############### Java endtoend tests ######################## +java_test( + name='java_endtoend_tests', + deps=[ + '//infer/tests/endtoend/java/infer:infer', + '//infer/tests/endtoend/java/eradicate:eradicate', + '//infer/tests/endtoend/java/checkers:checkers', + '//infer/tests/endtoend/java/harness:harness', + '//infer/tests/endtoend/java/tracing:tracing', + '//infer/tests/endtoend/java/comparison:comparison', + ], + visibility=integration_tests, +) diff --git a/infer/tests/endtoend/c/AngelismTest.java b/infer/tests/endtoend/c/AngelismTest.java new file mode 100644 index 000000000..d31788ed5 --- /dev/null +++ b/infer/tests/endtoend/c/AngelismTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.c; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsLineNumbers.containsLines; +import static utils.matchers.ResultContainsExactly.containsExactly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class AngelismTest { + + public static final String SOURCE_FILE = + "null_dereference/angelism.c"; + + public static final String NULL_DEREFERENCE = "NULL_DEREFERENCE"; + + private static InferResults inferResults; + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferResults = InferResults.loadCInferResults( + AngelismTest.class, + SOURCE_FILE); + } + + @Test + public void angelismTest() throws InterruptedException, IOException, InferException { + String[] procedures = { + "bake", + }; + assertThat( + "Results should contain null pointer dereference error", + inferResults, + containsExactly( + NULL_DEREFERENCE, + SOURCE_FILE, + procedures + ) + ); + } + +} diff --git a/infer/tests/endtoend/c/ArrayOutOfBoundsTest.java b/infer/tests/endtoend/c/ArrayOutOfBoundsTest.java new file mode 100644 index 000000000..e5ad1e5e1 --- /dev/null +++ b/infer/tests/endtoend/c/ArrayOutOfBoundsTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.c; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsExactly.containsExactly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class ArrayOutOfBoundsTest { + + public static final String SOURCE_FILE = + "arithmetic/array_out_of_bounds.c"; + + public static final String ARRAY_OUT_OF_BOUNDS_L1 = + "ARRAY_OUT_OF_BOUNDS_L1"; + + private static InferResults inferResults; + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferResults = InferResults.loadCInferResults( + ArrayOutOfBoundsTest.class, + SOURCE_FILE + ); + } + + @Test + public void whenInferRunsOnBoundErrorThenArrayOutOfBoundsIsFound() + throws InterruptedException, IOException, InferException { + String[] procedures = {"bound_error", "bound_error_nested"}; + assertThat( + "Results should contain array bound errors", + inferResults, + containsExactly( + ARRAY_OUT_OF_BOUNDS_L1, + SOURCE_FILE, + procedures + ) + ); + } + +} diff --git a/infer/tests/endtoend/c/AssertTest.java b/infer/tests/endtoend/c/AssertTest.java new file mode 100644 index 000000000..baf913b49 --- /dev/null +++ b/infer/tests/endtoend/c/AssertTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.c; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class AssertTest { + + public static final String source_file = + "infer/tests/codetoanalyze/c/frontend/conditional_operator/assert_example.c"; + + private static ImmutableList inferCmd; + + public static final String NULL_DEREFERENCE = "NULL_DEREFERENCE"; + + @ClassRule + public static DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmd = InferRunner.createCInferCommand(folder, source_file); + } + + @Test + public void whenInferRunsOnAssertExampleThenNPENotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferC(inferCmd); + assertThat( + "Results should not contain a null dereference error", + inferResults, + doesNotContain( + NULL_DEREFERENCE, + source_file, + "test")); + } + +} diff --git a/infer/tests/endtoend/c/AssertionFailureTest.java b/infer/tests/endtoend/c/AssertionFailureTest.java new file mode 100644 index 000000000..92f27c3a9 --- /dev/null +++ b/infer/tests/endtoend/c/AssertionFailureTest.java @@ -0,0 +1,53 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package endtoend.c; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsExactly.containsExactly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class AssertionFailureTest { + + public static final String SOURCE_FILE = + "assertions/assertion_failure.c"; + + public static final String ASSERTION_FAILURE = "Assertion_failure"; + + private static InferResults inferResults; + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferResults = InferResults.loadCInferResults(AssertionFailureTest.class, SOURCE_FILE); + } + + @Test + public void whenRunsOnAssertionFailureThenAssertionFailureIsFound() + throws InterruptedException, IOException, InferException { + String[] methods = { + "simple_assertion_failure", + "should_report_assertion_failure", + "assertion_failure_with_heap", + "assignemt_before_check", + }; + assertThat( + "Results should contain " + ASSERTION_FAILURE, + inferResults, + containsExactly( + ASSERTION_FAILURE, + SOURCE_FILE, + methods + ) + ); + } + +} diff --git a/infer/tests/endtoend/c/BUCK b/infer/tests/endtoend/c/BUCK new file mode 100644 index 000000000..3a01c217b --- /dev/null +++ b/infer/tests/endtoend/c/BUCK @@ -0,0 +1,16 @@ +java_test( + name='infer', + srcs=glob(['*.java']), + deps=[ + '//dependencies/java/guava:guava', + '//dependencies/java/junit:hamcrest', + '//dependencies/java/junit:junit', + '//infer/tests/utils:utils', + ], + resources=[ + '//infer/tests/codetoanalyze/c/errors:analyze', + ], + visibility=[ + 'PUBLIC', + ], +) diff --git a/infer/tests/endtoend/c/DivideByZeroTest.java b/infer/tests/endtoend/c/DivideByZeroTest.java new file mode 100644 index 000000000..2158a071f --- /dev/null +++ b/infer/tests/endtoend/c/DivideByZeroTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.c; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsExactly.containsExactly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class DivideByZeroTest { + + public static final String SOURCE_FILE = + "arithmetic/divide_by_zero.c"; + + public static final String DIVIDE_BY_ZERO = "DIVIDE_BY_ZERO"; + + private static InferResults inferResults; + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferResults = InferResults.loadCInferResults(DivideByZeroTest.class, SOURCE_FILE); + } + + @Test + public void whenInferRunsOnDivideByZeroThenDivideByZeroIsFound() + throws InterruptedException, IOException, InferException { + String[] procedures = {"divide_by_zero"}; + assertThat( + "Results should contain divide by zero error", + inferResults, + containsExactly( + DIVIDE_BY_ZERO, + SOURCE_FILE, + procedures + ) + ); + } + + +} diff --git a/infer/tests/endtoend/c/InitListExprTest.java b/infer/tests/endtoend/c/InitListExprTest.java new file mode 100644 index 000000000..81accb0ff --- /dev/null +++ b/infer/tests/endtoend/c/InitListExprTest.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.c; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class InitListExprTest { + + public static final String initlistexpr_file = + "infer/tests/" + + "codetoanalyze/c/errors/initialization/initlistexpr.c"; + + private static ImmutableList inferCmd; + + public static final String DIVIDE_BY_ZERO = "DIVIDE_BY_ZERO"; + + @ClassRule + public static DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmd = InferRunner.createCInferCommand(folder, initlistexpr_file); + } + + @Test + public void whenInferRunsOnInitListExprThenDivideByZeroIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferC(inferCmd); + assertThat( + "Results should contain divide by zero error", + inferResults, + contains( + DIVIDE_BY_ZERO, + initlistexpr_file, + "divide_by_zero" + ) + ); + } + +} diff --git a/infer/tests/endtoend/c/LocalVarsTest.java b/infer/tests/endtoend/c/LocalVarsTest.java new file mode 100644 index 000000000..d3a7c02e6 --- /dev/null +++ b/infer/tests/endtoend/c/LocalVarsTest.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.c; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; +import static utils.matchers.ResultContainsOnlyTheseErrors.containsOnly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class LocalVarsTest { + + public static final String local_vars_file = + "local_vars/local_vars.c"; + + public static final String DIVIDE_BY_ZERO = "DIVIDE_BY_ZERO"; + + private static InferResults inferResults; + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferResults = InferResults.loadCInferResults(LocalVarsTest.class, local_vars_file); + } + + @Test + public void whenInferRunsOnLocal_vars_mThenDivideByZeroIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain divide by zero error", + inferResults, + contains( + DIVIDE_BY_ZERO, + local_vars_file, + "m" + ) + ); + } + + @Test + public void whenInferRunsOnLocal_vars_mmThenDivideByZeroIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain divide by zero error", + inferResults, + contains( + DIVIDE_BY_ZERO, + local_vars_file, + "mm" + ) + ); + } + + @Test + public void whenInferRunsOnLocal_vars_tThenDivideByZeroIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain divide by zero error", + inferResults, + contains( + DIVIDE_BY_ZERO, + local_vars_file, + "t" + ) + ); + } + + + @Test + public void whenInferRunsOnNullPointerDereferenceThenOnlyTheExpectedErrorsAreFound() + throws InterruptedException, IOException, InferException { + String[] expectedProcedures = {"m", "mm", "t"}; + assertThat( + "No unexpected errors should be found", inferResults, + containsOnly( + DIVIDE_BY_ZERO, + local_vars_file, + expectedProcedures)); + } + +} diff --git a/infer/tests/endtoend/c/MemoryLeakTest.java b/infer/tests/endtoend/c/MemoryLeakTest.java new file mode 100644 index 000000000..9711a2ff4 --- /dev/null +++ b/infer/tests/endtoend/c/MemoryLeakTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2015- Facebook. + * All rights reserved. + */ + +package endtoend.c; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsExactly.containsExactly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class MemoryLeakTest { + + public static final String SOURCE_FILE = "memory_leaks/test.c"; + + public static final String MEMORY_LEAK = "MEMORY_LEAK"; + + private static InferResults inferResults; + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferResults = InferResults.loadCInferResults( + MemoryLeakTest.class, + SOURCE_FILE); + } + + @Test + public void memoryLeakTest() throws InterruptedException, IOException, InferException { + String[] procedures = { + "simple_leak", + "uses_allocator", + "common_realloc_leak", + }; + assertThat( + "Results should contain the expected memory leak errors", + inferResults, + containsExactly( + MEMORY_LEAK, + SOURCE_FILE, + procedures + ) + ); + } + + + +} diff --git a/infer/tests/endtoend/c/NullDereferenceTest.java b/infer/tests/endtoend/c/NullDereferenceTest.java new file mode 100644 index 000000000..73968d11b --- /dev/null +++ b/infer/tests/endtoend/c/NullDereferenceTest.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.c; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsLineNumbers.containsLines; +import static utils.matchers.ResultContainsExactly.containsExactly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class NullDereferenceTest { + + public static final String SOURCE_FILE = + "null_dereference/null_pointer_dereference.c"; + + public static final String NULL_DEREFERENCE = "NULL_DEREFERENCE"; + + private static InferResults inferResults; + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferResults = InferResults.loadCInferResults( + NullDereferenceTest.class, + SOURCE_FILE); + } + + @Test + public void nullDereferenceTest() throws InterruptedException, IOException, InferException { + String[] procedures = { + "basic_null_dereference", + "simple_null_pointer", + "null_pointer_interproc", + "null_pointer_with_function_pointer", + "no_check_for_null_after_malloc", + "no_check_for_null_after_realloc", + "potentially_null_pointer_passed_as_argument", + "function_call_can_return_null_pointer", + }; + assertThat( + "Results should contain null pointer dereference error", + inferResults, + containsExactly( + NULL_DEREFERENCE, + SOURCE_FILE, + procedures + ) + ); + } + + @Test + public void whenInferRunsOnSimpleNpe_interprocThenCorrectLineIsReported() + throws InterruptedException, IOException, InferException { + int[] lines = {16, 30}; + assertThat( + "Results should contain null pointer dereference error", + inferResults, + containsLines(lines)); + } + +} diff --git a/infer/tests/endtoend/c/ResourceLeakTest.java b/infer/tests/endtoend/c/ResourceLeakTest.java new file mode 100644 index 000000000..99865f088 --- /dev/null +++ b/infer/tests/endtoend/c/ResourceLeakTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.c; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class ResourceLeakTest { + + public static final String SOURCE_FILE = + "resource_leaks/leak.c"; + + public static final String RESOURCE_LEAK = "RESOURCE_LEAK"; + + private static InferResults inferResults; + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferResults = InferResults.loadCInferResults(ResourceLeakTest.class, SOURCE_FILE); + } + + @Test + public void whenInferRunsOnFileNotClosedThenLeakFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak", + inferResults, + contains( + RESOURCE_LEAK, + SOURCE_FILE, + "fileNotClosed" + ) + ); + } + + @Test + public void whenInferRunsOnFileClosedThenLeakNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak", + inferResults, + doesNotContain( + RESOURCE_LEAK, + SOURCE_FILE, + "fileClosed")); + } + +} diff --git a/infer/tests/endtoend/java/checkers/BUCK b/infer/tests/endtoend/java/checkers/BUCK new file mode 100644 index 000000000..5135029aa --- /dev/null +++ b/infer/tests/endtoend/java/checkers/BUCK @@ -0,0 +1,16 @@ +java_test( + name='checkers', + srcs=glob(['*.java']), + deps=[ + '//dependencies/java/guava:guava', + '//dependencies/java/junit:hamcrest', + '//dependencies/java/junit:junit', + '//infer/tests/utils:utils', + ], + resources=[ + '//infer/tests/codetoanalyze/java/checkers:analyze', + ], + visibility=[ + 'PUBLIC', + ], +) diff --git a/infer/tests/endtoend/java/checkers/ImmutableCastTest.java b/infer/tests/endtoend/java/checkers/ImmutableCastTest.java new file mode 100644 index 000000000..b22df1266 --- /dev/null +++ b/infer/tests/endtoend/java/checkers/ImmutableCastTest.java @@ -0,0 +1,51 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package endtoend.java.checkers; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsExactly.containsExactly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class ImmutableCastTest { + + + public static final String SOURCE_FILE = + "infer/tests/codetoanalyze/java/checkers/ImmutableCast.java"; + + public static final String IMMUTABLE_CAST = "CHECKERS_IMMUTABLE_CAST"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = + InferResults.loadCheckersResults(ImmutableCastTest.class, SOURCE_FILE); + } + + @Test + public void matchErrors() + throws IOException, InterruptedException, InferException { + String[] methods = { + "badCast", + "badCastFromField", + }; + assertThat( + "Results should contain " + IMMUTABLE_CAST, + inferResults, + containsExactly( + IMMUTABLE_CAST, + SOURCE_FILE, + methods)); + } + +} diff --git a/infer/tests/endtoend/java/comparison/ArrayIndexOutOfBoundsExceptionTest.java b/infer/tests/endtoend/java/comparison/ArrayIndexOutOfBoundsExceptionTest.java new file mode 100644 index 000000000..2b5d09531 --- /dev/null +++ b/infer/tests/endtoend/java/comparison/ArrayIndexOutOfBoundsExceptionTest.java @@ -0,0 +1,48 @@ +// Copyright (c) 2015-Present Facebook. All rights reserved. + +package endtoend.java.comparison; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsExactly.containsExactly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class ArrayIndexOutOfBoundsExceptionTest { + + public static final String SOURCE_FILE = + "infer/tests/codetoanalyze/java/infer/ArrayOutOfBounds.java"; + + public static final String ARRAY_OUT_OF_BOUNDS = "java.lang.ArrayIndexOutOfBoundsException"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = InferResults.loadTracingComparisonResults( + ArrayIndexOutOfBoundsExceptionTest.class, + SOURCE_FILE); + } + + @Test + public void whenInferRunsOnArrayOutOfBoundsThenErrorIsFound() + throws IOException, InterruptedException, InferException { + String[] methods = { + "arrayOutOfBounds", + }; + assertThat( + "Results should contain out of bounds error.", inferResults, + containsExactly( + ARRAY_OUT_OF_BOUNDS, + SOURCE_FILE, + methods + ) + ); + } + +} diff --git a/infer/tests/endtoend/java/comparison/BUCK b/infer/tests/endtoend/java/comparison/BUCK new file mode 100644 index 000000000..3462ed5b2 --- /dev/null +++ b/infer/tests/endtoend/java/comparison/BUCK @@ -0,0 +1,16 @@ +java_test( + name='comparison', + srcs=glob(['*.java']), + deps=[ + '//dependencies/java/guava:guava', + '//dependencies/java/junit:hamcrest', + '//dependencies/java/junit:junit', + '//infer/tests/utils:utils', + ], + resources=[ + '//infer/tests/codetoanalyze/java/infer:tracing', + ], + visibility=[ + 'PUBLIC', + ], +) diff --git a/infer/tests/endtoend/java/comparison/ClassCastExceptionTest.java b/infer/tests/endtoend/java/comparison/ClassCastExceptionTest.java new file mode 100644 index 000000000..ecc35c058 --- /dev/null +++ b/infer/tests/endtoend/java/comparison/ClassCastExceptionTest.java @@ -0,0 +1,49 @@ +// Copyright (c) 2015-Present Facebook. All rights reserved. + +package endtoend.java.comparison; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsExactly.containsExactly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class ClassCastExceptionTest { + + public static final String SOURCE_FILE = + "infer/tests/codetoanalyze/java/infer/ClassCastExceptions.java"; + + public static final String CLASS_CAST_EXCEPTION = + "java.lang.ClassCastException"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = InferResults.loadTracingComparisonResults( + ClassCastExceptionTest.class, + SOURCE_FILE); + } + + @Test + public void whenEradicateRunsOnConstructorThenFieldNotInitializedIsFound() + throws IOException, InterruptedException, InferException { + String[] methods = {"classCastException", "classCastExceptionImplementsInterface"}; + assertThat( + "Results should contain " + CLASS_CAST_EXCEPTION, + inferResults, + containsExactly( + CLASS_CAST_EXCEPTION, + SOURCE_FILE, + methods + ) + ); + } + + +} diff --git a/infer/tests/endtoend/java/comparison/NullPointerExceptionTest.java b/infer/tests/endtoend/java/comparison/NullPointerExceptionTest.java new file mode 100644 index 000000000..0ee109c0d --- /dev/null +++ b/infer/tests/endtoend/java/comparison/NullPointerExceptionTest.java @@ -0,0 +1,59 @@ +// Copyright (c) 2015-Present Facebook. All rights reserved. + +package endtoend.java.comparison; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsTheseErrors.contains; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class NullPointerExceptionTest { + + public static final String SOURCE_FILE = + "infer/tests/codetoanalyze/java/infer/NullPointerExceptions.java"; + + public static final String NPE = + "java.lang.NullPointerException"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = InferResults.loadTracingComparisonResults( + NullPointerExceptionTest.class, + SOURCE_FILE); + } + + @Test + public void errorsFoundByInferExpectedToBeFoundInTracingMode() + throws IOException, InterruptedException, InferException { + String[] methods = { + "nullPointerException", + "nullPointerExceptionInterProc", + "nullPointerExceptionWithExceptionHandling", + "nullPointerExceptionWithArray", + "nullPointerExceptionWithNullObjectParameter", + "nullPointerExceptionWithNullArrayParameter", + "nullPointerExceptionFromFaillingResourceConstructor", + "nullPointerExceptionFromFailingFileOutputStreamConstructor", + "nullPointerExceptionUnlessFrameFails", + }; + assertThat( + "Results should contain " + NPE, + inferResults, + contains( + NPE, + SOURCE_FILE, + methods + ) + ); + } + + +} diff --git a/infer/tests/endtoend/java/eradicate/ActivityFieldNotInitializedTest.java b/infer/tests/endtoend/java/eradicate/ActivityFieldNotInitializedTest.java new file mode 100644 index 000000000..25d082e0f --- /dev/null +++ b/infer/tests/endtoend/java/eradicate/ActivityFieldNotInitializedTest.java @@ -0,0 +1,50 @@ +/* + * Copyright 2014-present Facebook, Inc. + */ + +package endtoend.java.eradicate; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsExactly.containsExactly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class ActivityFieldNotInitializedTest { + + public static final String SOURCE_FILE = + "infer/tests/codetoanalyze/java/eradicate/ActivityFieldNotInitialized.java"; + + public static final String FIELD_NOT_INITIALIZED = + "ERADICATE_FIELD_NOT_INITIALIZED"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = InferResults.loadEradicateResults( + ActivityFieldNotInitializedTest.class, + SOURCE_FILE); + } + + @Test + public void matchErrors() + throws IOException, InterruptedException, InferException { + String[] methods = { + "", + }; + assertThat( + "Results should contain " + FIELD_NOT_INITIALIZED, + inferResults, + containsExactly( + FIELD_NOT_INITIALIZED, + SOURCE_FILE, + methods)); + } + +} diff --git a/infer/tests/endtoend/java/eradicate/BUCK b/infer/tests/endtoend/java/eradicate/BUCK new file mode 100644 index 000000000..ef98770b0 --- /dev/null +++ b/infer/tests/endtoend/java/eradicate/BUCK @@ -0,0 +1,16 @@ +java_test( + name='eradicate', + srcs=glob(['*.java']), + deps=[ + '//dependencies/java/guava:guava', + '//dependencies/java/junit:hamcrest', + '//dependencies/java/junit:junit', + '//infer/tests/utils:utils', + ], + resources=[ + '//infer/tests/codetoanalyze/java/eradicate:analyze', + ], + visibility=[ + 'PUBLIC', + ], +) diff --git a/infer/tests/endtoend/java/eradicate/FieldNotInitializedTest.java b/infer/tests/endtoend/java/eradicate/FieldNotInitializedTest.java new file mode 100644 index 000000000..b1aadc0f3 --- /dev/null +++ b/infer/tests/endtoend/java/eradicate/FieldNotInitializedTest.java @@ -0,0 +1,50 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package endtoend.java.eradicate; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsExactly.containsExactly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class FieldNotInitializedTest { + + public static final String SOURCE_FILE = + "infer/tests/codetoanalyze/java/eradicate/FieldNotInitialized.java"; + + public static final String FIELD_NOT_INITIALIZED = + "ERADICATE_FIELD_NOT_INITIALIZED"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = + InferResults.loadEradicateResults(FieldNotInitializedTest.class, SOURCE_FILE); + } + + @Test + public void matchErrors() + throws IOException, InterruptedException, InferException { + String[] methods = { + "", + }; + assertThat( + "Results should contain " + FIELD_NOT_INITIALIZED, + inferResults, + containsExactly( + FIELD_NOT_INITIALIZED, + SOURCE_FILE, + methods)); + } + +} diff --git a/infer/tests/endtoend/java/eradicate/FieldNotNullableTest.java b/infer/tests/endtoend/java/eradicate/FieldNotNullableTest.java new file mode 100644 index 000000000..da032111a --- /dev/null +++ b/infer/tests/endtoend/java/eradicate/FieldNotNullableTest.java @@ -0,0 +1,57 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package endtoend.java.eradicate; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsExactly.containsExactly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class FieldNotNullableTest { + + public static final String SOURCE_FILE = + "infer/tests/codetoanalyze/java/eradicate/FieldNotNullable.java"; + + public static final String FIELD_NOT_NULLABLE = + "ERADICATE_FIELD_NOT_NULLABLE"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = + InferResults.loadEradicateResults(FieldNotNullableTest.class, SOURCE_FILE); + } + + @Test + public void matchErrors() + throws IOException, InterruptedException, InferException { + String[] methods = { + "setYNull", + "setYNullable", + "", + "FlatBAD1", + "FlatBAD2", + "NestedBAD1", + "NestedBAD2", + "NestedBAD3", + }; + assertThat( + "Results should contain " + FIELD_NOT_NULLABLE, + inferResults, + containsExactly( + FIELD_NOT_NULLABLE, + SOURCE_FILE, + methods)); + } + +} diff --git a/infer/tests/endtoend/java/eradicate/InconsistentSubclassAnnotationTest.java b/infer/tests/endtoend/java/eradicate/InconsistentSubclassAnnotationTest.java new file mode 100644 index 000000000..304366512 --- /dev/null +++ b/infer/tests/endtoend/java/eradicate/InconsistentSubclassAnnotationTest.java @@ -0,0 +1,70 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package endtoend.java.eradicate; + + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ErrorPattern.createPatterns; +import static utils.matchers.ResultContainsExactly.containsExactly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; +import java.util.List; + +import utils.InferException; +import utils.InferResults; +import utils.matchers.ErrorPattern; + +public class InconsistentSubclassAnnotationTest { + + public static final String SOURCE_FILE = + "infer/tests/codetoanalyze/java/eradicate/InconsistentSubclassAnnotation.java"; + + public static final String ERADICATE_INCONSISTENT_SUBCLASS_RETURN_ANNOTATION = + "ERADICATE_INCONSISTENT_SUBCLASS_RETURN_ANNOTATION"; + + public static final String ERADICATE_INCONSISTENT_SUBCLASS_PARAMETER_ANNOTATION = + "ERADICATE_INCONSISTENT_SUBCLASS_PARAMETER_ANNOTATION"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = InferResults.loadEradicateResults( + InconsistentSubclassAnnotationTest.class, + SOURCE_FILE); + } + + @Test + public void matchErrors() + throws IOException, InterruptedException, InferException { + + + String[] returnMethods = {"foo", "baz"}; + List errorPatterns = createPatterns( + ERADICATE_INCONSISTENT_SUBCLASS_RETURN_ANNOTATION, + SOURCE_FILE, + returnMethods); + + String[] parameterMethods = {"deref"}; + errorPatterns.addAll( + createPatterns( + ERADICATE_INCONSISTENT_SUBCLASS_PARAMETER_ANNOTATION, + SOURCE_FILE, + parameterMethods + ) + ); + + assertThat( + "Results should contain ", + inferResults, + containsExactly(errorPatterns) + ); + } + +} diff --git a/infer/tests/endtoend/java/eradicate/LibraryCallsTest.java b/infer/tests/endtoend/java/eradicate/LibraryCallsTest.java new file mode 100644 index 000000000..9ece80e3d --- /dev/null +++ b/infer/tests/endtoend/java/eradicate/LibraryCallsTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2013-present Facebook, Inc. + */ + +package endtoend.java.eradicate; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsExactly.containsExactly; + + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class LibraryCallsTest { + + public static final String SOURCE_FILE = + "infer/tests/codetoanalyze/java/eradicate/LibraryCalls.java"; + + public static final String NULL_METHOD_CALL = + "ERADICATE_NULL_METHOD_CALL"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = + InferResults.loadEradicateResults(LibraryCallsTest.class, SOURCE_FILE); + } + + @Test + public void matchErrors() + throws IOException, InterruptedException, InferException { + String[] methods = { + "badReferenceDereference", + "badWeakReferenceDereference", + "badPhantomReferenceDereference", + "badSoftReferenceDereference", + "badAtomicReferenceDereference", + }; + assertThat( + "Results should contain " + NULL_METHOD_CALL, + inferResults, + containsExactly( + NULL_METHOD_CALL, + SOURCE_FILE, + methods)); + } + +} diff --git a/infer/tests/endtoend/java/eradicate/NullFieldAccessTest.java b/infer/tests/endtoend/java/eradicate/NullFieldAccessTest.java new file mode 100644 index 000000000..bb82de24d --- /dev/null +++ b/infer/tests/endtoend/java/eradicate/NullFieldAccessTest.java @@ -0,0 +1,55 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package endtoend.java.eradicate; + + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsExactly.containsExactly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class NullFieldAccessTest { + + public static final String SOURCE_FILE = + "infer/tests/codetoanalyze/java/eradicate/NullFieldAccess.java"; + + public static final String NULL_FIELD_ACCESS = + "ERADICATE_NULL_FIELD_ACCESS"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = + InferResults.loadEradicateResults(NullFieldAccessTest.class, SOURCE_FILE); + } + + @Test + public void matchErrors() + throws IOException, InterruptedException, InferException { + String[] methods = { + "useX", + "useZ", + "useInterface", + "arrayLength", + "arrayAccess", + }; + assertThat( + "Results should contain " + NULL_FIELD_ACCESS, + inferResults, + containsExactly( + NULL_FIELD_ACCESS, + SOURCE_FILE, + methods)); + } + +} diff --git a/infer/tests/endtoend/java/eradicate/NullMethodCallTest.java b/infer/tests/endtoend/java/eradicate/NullMethodCallTest.java new file mode 100644 index 000000000..8a4e9b7a4 --- /dev/null +++ b/infer/tests/endtoend/java/eradicate/NullMethodCallTest.java @@ -0,0 +1,54 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package endtoend.java.eradicate; + + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsExactly.containsExactly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class NullMethodCallTest { + + public static final String SOURCE_FILE = + "infer/tests/codetoanalyze/java/eradicate/NullMethodCall.java"; + + public static final String NULL_METHOD_CALL = "ERADICATE_NULL_METHOD_CALL"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = + InferResults.loadEradicateResults(NullMethodCallTest.class, SOURCE_FILE); + } + + @Test + public void matchErrors() + throws IOException, InterruptedException, InferException { + String[] methods = { + "callOnNull", + "outerField", + "outerPrivateField", + "testFieldAssignmentIfThenElse", + "testExceptionPerInstruction", + }; + assertThat( + "Results should contain " + NULL_METHOD_CALL, + inferResults, + containsExactly( + NULL_METHOD_CALL, + SOURCE_FILE, + methods)); + } + +} diff --git a/infer/tests/endtoend/java/eradicate/ParameterNotNullableTest.java b/infer/tests/endtoend/java/eradicate/ParameterNotNullableTest.java new file mode 100644 index 000000000..c2f24bc1c --- /dev/null +++ b/infer/tests/endtoend/java/eradicate/ParameterNotNullableTest.java @@ -0,0 +1,51 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package endtoend.java.eradicate; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsExactly.containsExactly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class ParameterNotNullableTest { + + public static final String SOURCE_FILE = + "infer/tests/codetoanalyze/java/eradicate/ParameterNotNullable.java"; + + public static final String PARAMETER_NOT_NULLABLE = + "ERADICATE_PARAMETER_NOT_NULLABLE"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = + InferResults.loadEradicateResults(ParameterNotNullableTest.class, SOURCE_FILE); + } + + @Test + public void matchErrors() + throws IOException, InterruptedException, InferException { + String[] methods = { + "callNull", + "callNullable", + }; + assertThat( + "Results should contain " + PARAMETER_NOT_NULLABLE, + inferResults, + containsExactly( + PARAMETER_NOT_NULLABLE, + SOURCE_FILE, + methods)); + } + +} diff --git a/infer/tests/endtoend/java/eradicate/ReturnNotNullableTest.java b/infer/tests/endtoend/java/eradicate/ReturnNotNullableTest.java new file mode 100644 index 000000000..bd376f3a8 --- /dev/null +++ b/infer/tests/endtoend/java/eradicate/ReturnNotNullableTest.java @@ -0,0 +1,84 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package endtoend.java.eradicate; + + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ErrorPattern.createPatterns; +import static utils.matchers.ResultContainsExactly.containsExactly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; +import java.util.List; + +import utils.InferException; +import utils.InferResults; +import utils.matchers.ErrorPattern; + +public class ReturnNotNullableTest { + + + public static final String SOURCE_FILE = + "infer/tests/codetoanalyze/java/eradicate/ReturnNotNullable.java"; + + public static final String RETURN_NOT_NULLABLE = + "ERADICATE_RETURN_NOT_NULLABLE"; + public static final String CONDITION_REDUNDANT_NONNULL = + "ERADICATE_CONDITION_REDUNDANT_NONNULL"; + public static final String RETURN_OVER_ANNOTATED = + "ERADICATE_RETURN_OVER_ANNOTATED"; + + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = + InferResults.loadEradicateResults(ReturnNotNullableTest.class, SOURCE_FILE); + } + + boolean returnOverAnnotatedEnabled = true; + + @Test + public void matchErrors() + throws IOException, InterruptedException, InferException { + + + String[] nullableMethods = {"returnNull", "returnNullable"}; + List errorPatterns = createPatterns( + RETURN_NOT_NULLABLE, + SOURCE_FILE, + nullableMethods); + + String[] redundantMethods = {"redundantEq", "redundantNeq"}; + errorPatterns.addAll( + createPatterns( + CONDITION_REDUNDANT_NONNULL, + SOURCE_FILE, + redundantMethods + ) + ); + + if (returnOverAnnotatedEnabled) { + errorPatterns.addAll( + createPatterns( + RETURN_OVER_ANNOTATED, + SOURCE_FILE, + redundantMethods + ) + ); + } + + assertThat( + "Results should contain ", + inferResults, + containsExactly(errorPatterns) + ); + } + +} diff --git a/infer/tests/endtoend/java/eradicate/SuppressedFieldNotInitializedTest.java b/infer/tests/endtoend/java/eradicate/SuppressedFieldNotInitializedTest.java new file mode 100644 index 000000000..c34e280b7 --- /dev/null +++ b/infer/tests/endtoend/java/eradicate/SuppressedFieldNotInitializedTest.java @@ -0,0 +1,50 @@ +/* + * Copyright 2013-present Facebook, Inc. + */ + +package endtoend.java.eradicate; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; +import static utils.matchers.ResultContainsExactly.containsExactly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class SuppressedFieldNotInitializedTest { + + public static final String SOURCE_FILE = + "infer/tests/codetoanalyze/java/eradicate/SuppressedFieldNotInitializedExample.java"; + + public static final String FIELD_NOT_INITIALIZED = + "ERADICATE_FIELD_NOT_INITIALIZED"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = InferResults.loadEradicateResults( + SuppressedFieldNotInitializedTest.class, + SOURCE_FILE); + } + + @Test + public void matchErrors() + throws IOException, InterruptedException, InferException { + String[] methods = { + }; + assertThat( + "Results should contain " + FIELD_NOT_INITIALIZED, + inferResults, + containsExactly( + FIELD_NOT_INITIALIZED, + SOURCE_FILE, + methods)); + } + +} diff --git a/infer/tests/endtoend/java/harness/BUCK b/infer/tests/endtoend/java/harness/BUCK new file mode 100644 index 000000000..b422c24e5 --- /dev/null +++ b/infer/tests/endtoend/java/harness/BUCK @@ -0,0 +1,13 @@ +java_test( + name='harness', + srcs=glob(['*.java']), + deps=[ + '//dependencies/java/guava:guava', + '//dependencies/java/junit:hamcrest', + '//dependencies/java/junit:junit', + '//infer/tests/utils:utils', + ], + visibility=[ + 'PUBLIC', + ], +) diff --git a/infer/tests/endtoend/java/harness/CallbackTest.java b/infer/tests/endtoend/java/harness/CallbackTest.java new file mode 100644 index 000000000..df7b6b054 --- /dev/null +++ b/infer/tests/endtoend/java/harness/CallbackTest.java @@ -0,0 +1,57 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package endtoend.java.harness; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorNoFilename.contains; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class CallbackTest { + + public static final String CallbackActivity = + "infer/tests/codetoanalyze/java/harness/CallbackActivity.java"; + + public static final String NULL_DEREFERENCE = "NULL_DEREFERENCE"; + + @ClassRule + public static DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + private static ImmutableList inferCmd; + + + @BeforeClass + public static void runInfer() throws IOException { + inferCmd = InferRunner.createJavaInferHarnessCommand(folder, CallbackActivity); + } + + + @Test + public void harnessRevealsNpe() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferJava(inferCmd); + assertThat( + "Results should contain NPE", + inferResults, + contains( + NULL_DEREFERENCE, + "java.harness.CallbackActivity.InferGeneratedHarness" + ) + ); + } + +} diff --git a/infer/tests/endtoend/java/harness/FindViewByIdTest.java b/infer/tests/endtoend/java/harness/FindViewByIdTest.java new file mode 100644 index 000000000..d8dd4156d --- /dev/null +++ b/infer/tests/endtoend/java/harness/FindViewByIdTest.java @@ -0,0 +1,63 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package endtoend.java.harness; + + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorNoFilename.contains; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class FindViewByIdTest { + + public static final String FindViewByIdActivity = + "infer/tests/codetoanalyze/java/harness/FindViewByIdActivity.java"; + + public static final String MyView = + "infer/tests/codetoanalyze/java/harness/MyView.java"; + + public static final String NULL_DEREFERENCE = "NULL_DEREFERENCE"; + + @ClassRule + public static DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + private static ImmutableList inferCmd; + + + @BeforeClass + public static void runInfer() throws IOException { + inferCmd = InferRunner.createJavaInferAngelicHarnessCommand( + folder, + ImmutableList.of(FindViewByIdActivity, MyView)); + } + + + @Test + public void harnessRevealsNpe() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferJava(inferCmd); + assertThat( + "Results should contain NPE", + inferResults, + contains( + NULL_DEREFERENCE, + "java.harness.FindViewByIdActivity.InferGeneratedHarness" + ) + ); + } + +} diff --git a/infer/tests/endtoend/java/harness/InhabitTest.java b/infer/tests/endtoend/java/harness/InhabitTest.java new file mode 100644 index 000000000..1e9e6ffe7 --- /dev/null +++ b/infer/tests/endtoend/java/harness/InhabitTest.java @@ -0,0 +1,57 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package endtoend.java.harness; + + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorNoFilename.contains; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class InhabitTest { + + public static final String TrickyParamsActivity = + "infer/tests/codetoanalyze/java/harness/TrickyParamsActivity.java"; + + public static final String NULL_DEREFERENCE = "NULL_DEREFERENCE"; + + @ClassRule + public static DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + private static ImmutableList inferCmd; + + + @BeforeClass + public static void runInfer() throws IOException { + inferCmd = InferRunner.createJavaInferHarnessCommand(folder, TrickyParamsActivity); + } + + @Test + public void canInhabitTrickyParams() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferJava(inferCmd); + assertThat( + "Results should contain NPE", + inferResults, + contains( + NULL_DEREFERENCE, + "java.harness.TrickyParamsActivity.InferGeneratedHarness" + ) + ); + } + +} diff --git a/infer/tests/endtoend/java/harness/LifecycleTest.java b/infer/tests/endtoend/java/harness/LifecycleTest.java new file mode 100644 index 000000000..7171b9142 --- /dev/null +++ b/infer/tests/endtoend/java/harness/LifecycleTest.java @@ -0,0 +1,57 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package endtoend.java.harness; + + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorNoFilename.contains; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class LifecycleTest { + + public static final String BasicHarnessActivity = + "infer/tests/codetoanalyze/java/harness/BasicHarnessActivity.java"; + + public static final String NULL_DEREFERENCE = "NULL_DEREFERENCE"; + + @ClassRule + public static DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + private static ImmutableList inferCmd; + + + @BeforeClass + public static void runInfer() throws IOException { + inferCmd = InferRunner.createJavaInferHarnessCommand(folder, BasicHarnessActivity); + } + + @Test + public void harnessRevealsNpe() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferJava(inferCmd); + assertThat( + "Results should contain NPE", + inferResults, + contains( + NULL_DEREFERENCE, + "java.harness.BasicHarnessActivity.InferGeneratedHarness" + ) + ); + } + +} diff --git a/infer/tests/endtoend/java/harness/SuperclassTest.java b/infer/tests/endtoend/java/harness/SuperclassTest.java new file mode 100644 index 000000000..1e6b93171 --- /dev/null +++ b/infer/tests/endtoend/java/harness/SuperclassTest.java @@ -0,0 +1,63 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package endtoend.java.harness; + + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorNoFilename.contains; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class SuperclassTest { + + public static final String SuperclassActivity = + "infer/tests/codetoanalyze/java/harness/SuperclassActivity.java"; + + public static final String SubclassActivity = + "infer/tests/codetoanalyze/java/harness/SubclassActivity.java"; + + public static final String NULL_DEREFERENCE = "NULL_DEREFERENCE"; + + @ClassRule + public static DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + private static ImmutableList inferCmd; + + + @BeforeClass + public static void runInfer() throws IOException { + inferCmd = InferRunner.createJavaInferHarnessCommand( + folder, + ImmutableList.of(SuperclassActivity, SubclassActivity)); + } + + + @Test + public void harnessRevealsNpe() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferJava(inferCmd); + assertThat( + "Results should contain NPE", + inferResults, + contains( + NULL_DEREFERENCE, + "java.harness.SubclassActivity.InferGeneratedHarness" + ) + ); + } + +} diff --git a/infer/tests/endtoend/java/infer/AnalysisStopsTest.java b/infer/tests/endtoend/java/infer/AnalysisStopsTest.java new file mode 100644 index 000000000..1702ec36b --- /dev/null +++ b/infer/tests/endtoend/java/infer/AnalysisStopsTest.java @@ -0,0 +1,411 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.java.infer; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; +import static utils.matchers.ResultContainsNumberOfErrorsInMethod.containsNumberOfErrors; +import static utils.matchers.ResultContainsZeroErrorsInMethod.containsZeroErrors; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class AnalysisStopsTest { + + public static final String AnalysisStops = + "infer/tests/codetoanalyze/java/infer/AnalysisStops.java"; + + public static final String DIVIDE_BY_ZERO = "DIVIDE_BY_ZERO"; + public static final String NULL_DEREFERENCE = "NULL_DEREFERENCE"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = InferResults.loadInferResults(AnalysisStopsTest.class, AnalysisStops); + } + + @Test + public void skipPtrDerefDoesntCauseLocalFalseNegative() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain error", + inferResults, + contains( + DIVIDE_BY_ZERO, + AnalysisStops, + "skipPointerDerefMayCauseLocalFalseNegative" + ) + ); + } + + @Test + public void skipPtrDerefDoesntCauseCalleeFalseNegative() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain error", + inferResults, + contains( + DIVIDE_BY_ZERO, + AnalysisStops, + "skipPointerDerefMayCauseCalleeFalseNegative" + ) + ); + } + + @Test + public void skipPtrDerefDoesntCauseCalleeFalsePositive() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain no errors", + inferResults, + containsZeroErrors( + AnalysisStops, + "skipPointerDerefMayCauseCalleeFalsePositive")); + } + + @Test + public void skipPtrDerefDoesntCauseInterprocFalseNegative() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain error", + inferResults, + contains( + DIVIDE_BY_ZERO, + AnalysisStops, + "skipPointerDerefMayCauseInterprocFalseNegative" + ) + ); + } + + @Test + public void castUndefinedObjDoesntCauseFalseNegative() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain error", + inferResults, + contains( + DIVIDE_BY_ZERO, + AnalysisStops, + "castFailureOnUndefinedObjMayCauseFalseNegative" + ) + ); + } + + @Test + public void callOnCastUndefinedObjDoesntCauseFalseNegative() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain error", + inferResults, + contains( + DIVIDE_BY_ZERO, + AnalysisStops, + "callOnCastUndefinedObjMayCauseFalseNegative" + ) + ); + } + + @Test + public void callOnUndefinedObjDoesntCauseFalseNegative() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain error", + inferResults, + contains( + DIVIDE_BY_ZERO, + AnalysisStops, + "callOnUndefinedObjMayCauseFalseNegative" + ) + ); + } + + @Test + public void callOnUndefinedObjDoesntCauseFalsePositive() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain no errors", + inferResults, + containsZeroErrors( + AnalysisStops, + "callOnUndefinedObjMayCauseFalsePositive")); + } + + @Test + public void fieldWriteOnUndefinedObjDoesntCauseFalseNegative() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain error", + inferResults, + contains( + DIVIDE_BY_ZERO, + AnalysisStops, + "fieldWriteOnUndefinedObjMayCauseFalseNegative" + ) + ); + } + + @Test + public void fieldWriteOnUndefinedObjDoesntCauseFalsePositive() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain no errors", + inferResults, + containsZeroErrors( + AnalysisStops, + "fieldWriteOnUndefinedObjMayCauseFalsePositive")); + } + + @Test + public void fieldReadOnUndefinedObjDoesntCauseFalseNegative() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain error", + inferResults, + contains( + DIVIDE_BY_ZERO, + AnalysisStops, + "fieldReadOnUndefinedObjMayCauseFalseNegative" + ) + ); + } + + @Test + public void fieldReadOnUndefinedObjDoesntCauseFalsePositive() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain no errors", + inferResults, + containsZeroErrors( + AnalysisStops, + "fieldReadOnUndefinedObjMayCauseFalsePositive")); + } + + @Test + public void recursiveAngelicTypesDontCauseFalseNegative() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain error", + inferResults, + contains( + DIVIDE_BY_ZERO, + AnalysisStops, + "recursiveAngelicTypesMayCauseFalseNegative" + ) + ); + } + + @Test + public void recursiveAngelicTypesDontCauseFalsePositive() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain no errors", + inferResults, + containsZeroErrors( + AnalysisStops, + "recursiveAngelicTypeMayCauseFalsePositive")); + } + + @Test + public void infiniteMaterializationDoesntCauseFalseNegative() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain error", + inferResults, + contains( + DIVIDE_BY_ZERO, + AnalysisStops, + "infiniteMaterializationMayCauseFalseNegative" + ) + ); + } + + @Test + public void infiniteMaterializationDoesntCauseFalsePositive() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain no errors", + inferResults, + containsZeroErrors( + AnalysisStops, + "infiniteMaterializationMayCauseFalsePositive")); + } + + @Test + public void primitiveFieldOfAngelicObjDoesntCauseFalseNegative() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain error", + inferResults, + containsNumberOfErrors( + DIVIDE_BY_ZERO, + AnalysisStops, + "primitiveFieldOfAngelicObjMayCauseFalseNegative", + 2 + ) + ); + } + + @Test + public void primitiveFieldOfAngelicObjDoesntCauseFalsePositive() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain no errors", + inferResults, + containsZeroErrors( + AnalysisStops, + "primitiveFieldOfAngelicObjMayCauseFalsePositive")); + } + + @Test + public void heapFieldOfAngelicObjDoesntCauseFalseNegative() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain error", + inferResults, + contains( + DIVIDE_BY_ZERO, + AnalysisStops, + "heapFieldOfAngelicObjMayCauseFalseNegative" + ) + ); + } + + @Test + public void heapFieldOfAngelicObjDoesntCauseFalsePositive() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain no errors", + inferResults, + containsZeroErrors( + AnalysisStops, + "heapFieldOfAngelicObjMayCauseFalsePositive")); + } + + @Test + public void fieldReadAfterCastDoesntCauseFalseNegative() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain error", + inferResults, + contains( + DIVIDE_BY_ZERO, + AnalysisStops, + "fieldReadAferCastMayCauseFalseNegative" + ) + ); + } + + @Test + public void fieldReadInCalleeDoesntCauseFalsePositive() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain no errors", + inferResults, + containsZeroErrors( + AnalysisStops, + "fieldReadInCalleeMayCauseFalsePositive")); + } + + // these do not work yet + @Test + public void fieldReadInCalleeDoesntCauseFalseNegative() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain error", + inferResults, + contains( + NULL_DEREFERENCE, + AnalysisStops, + "fieldReadInCalleeMayCauseFalseNegative" + ) + ); + } + + @Test + public void fieldReadInCalleeWithAngelicObjFieldDoesntCauseFalsePositive() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain no errors", + inferResults, + containsZeroErrors( + AnalysisStops, + "fieldReadInCalleeWithAngelicObjFieldMayCauseFalsePositive")); + } + + @Test + public void fieldReadInCalleeWithAngelicObjFieldDoesntCauseFalseNegative() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain error", + inferResults, + contains( + NULL_DEREFERENCE, + AnalysisStops, + "fieldReadInCalleeWithAngelicObjFieldMayCauseFalseNegative" + ) + ); + } + + @Test + public void accessPathInCalleeDoesntCauseFalsePositive() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain no errors", + inferResults, + containsZeroErrors( + AnalysisStops, + "accessPathInCalleeMayCauseFalseNegative")); + } + + @Test + public void accessPathInCalleeDoesntCauseFalseNegative() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain error", + inferResults, + contains( + NULL_DEREFERENCE, + AnalysisStops, + "accessPathInCalleeMayCauseFalseNegative" + ) + ); + } + + @Test + public void skipFunctionInLoopShouldNotCauseFalseNegative() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain error", + inferResults, + contains( + NULL_DEREFERENCE, + AnalysisStops, + "skipFunctionInLoopMayCauseFalseNegative" + ) + ); + } + + @Test + public void specInferenceFailureShouldNotCauseFalseNegative() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain error", + inferResults, + contains( + DIVIDE_BY_ZERO, + AnalysisStops, + "specInferenceMayFailAndCauseFalseNegative" + ) + ); + } + +} diff --git a/infer/tests/endtoend/java/infer/ArrayOutOfBoundsTest.java b/infer/tests/endtoend/java/infer/ArrayOutOfBoundsTest.java new file mode 100644 index 000000000..bbb78c9fd --- /dev/null +++ b/infer/tests/endtoend/java/infer/ArrayOutOfBoundsTest.java @@ -0,0 +1,51 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package endtoend.java.infer; + + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsExactly.containsExactly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class ArrayOutOfBoundsTest { + + + public static final String SOURCE_FILE = + "infer/tests/codetoanalyze/java/infer/ArrayOutOfBounds.java"; + + public static final String ARRAY_OUT_OF_BOUNDS_L1 = "ARRAY_OUT_OF_BOUNDS_L1"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = InferResults.loadInferResults(ArrayOutOfBoundsTest.class, SOURCE_FILE); + } + + @Test + public void whenInferRunsOnArrayOutOfBoundsThenErrorIsFound() + throws IOException, InterruptedException, InferException { + String[] methods = { + "arrayOutOfBounds", + }; + assertThat( + "Results should contain out of bounds error.", inferResults, + containsExactly( + ARRAY_OUT_OF_BOUNDS_L1, + SOURCE_FILE, + methods + ) + ); + } + +} diff --git a/infer/tests/endtoend/java/infer/AutoGeneratedTest.java b/infer/tests/endtoend/java/infer/AutoGeneratedTest.java new file mode 100644 index 000000000..661df57d9 --- /dev/null +++ b/infer/tests/endtoend/java/infer/AutoGeneratedTest.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2015- Facebook. + * All rights reserved. + */ + +package endtoend.java.infer; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsExactly.containsExactly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class AutoGeneratedTest { + + public static final String SOURCE_FILE = + "infer/tests/codetoanalyze/java/infer/AutoGenerated.java"; + + public static final String NULL_DEREFERENCE = "NULL_DEREFERENCE"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = InferResults.loadInferResults( + AutoGeneratedTest.class, + SOURCE_FILE); + } + + @Test + public void matchErrors() + throws IOException, InterruptedException, InferException { + String[] methods = { + }; + assertThat( + "Results should contain no errors", + inferResults, + containsExactly( + NULL_DEREFERENCE, + SOURCE_FILE, + methods + ) + ); + } + +} diff --git a/infer/tests/endtoend/java/infer/BUCK b/infer/tests/endtoend/java/infer/BUCK new file mode 100644 index 000000000..d18e118a7 --- /dev/null +++ b/infer/tests/endtoend/java/infer/BUCK @@ -0,0 +1,16 @@ +java_test( + name='infer', + srcs=glob(['*.java']), + deps=[ + '//dependencies/java/guava:guava', + '//dependencies/java/junit:hamcrest', + '//dependencies/java/junit:junit', + '//infer/tests/utils:utils', + ], + resources=[ + '//infer/tests/codetoanalyze/java/infer:analyze', + ], + visibility=[ + 'PUBLIC', + ], +) diff --git a/infer/tests/endtoend/java/infer/ClassCastExceptionsTest.java b/infer/tests/endtoend/java/infer/ClassCastExceptionsTest.java new file mode 100644 index 000000000..328d88215 --- /dev/null +++ b/infer/tests/endtoend/java/infer/ClassCastExceptionsTest.java @@ -0,0 +1,96 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ +package endtoend.java.infer; + + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; +import static utils.matchers.ResultContainsOnlyTheseErrors.containsOnly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class ClassCastExceptionsTest { + + public static final String ClassCastExceptions = + "infer/tests/codetoanalyze/java/infer/ClassCastExceptions.java"; + + public static final String CLASS_CAST_EXCEPTION = "CLASS_CAST_EXCEPTION"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = InferResults.loadInferResults( + ClassCastExceptionsTest.class, + ClassCastExceptions); + } + + @Test + public void whenInferRunsOnClassCastExceptionThenCCEFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain class cast exception.", + inferResults, + contains( + CLASS_CAST_EXCEPTION, + ClassCastExceptions, + "classCastException" + ) + ); + } + + @Test + public void whenInferRunsOnClassCastExceptionImplementsIThenCCEErrorFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain class cast exception.", + inferResults, + contains( + CLASS_CAST_EXCEPTION, + ClassCastExceptions, + "classCastExceptionImplementsInterface" + ) + ); + } + + @Test + public void whenInferRunsOnOpenHttpURLConnectionThenCCEErrorNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain class cast exception.", + inferResults, + doesNotContain( + CLASS_CAST_EXCEPTION, + ClassCastExceptions, + "openHttpURLConnection" + ) + ); + } + + @Test + public void whenInferIsRunCCEThenOnlyTheExpectedErrorsAreFound() + throws InterruptedException, IOException, InferException { + String[] expectedMethods = { + "classCastException", + "classCastExceptionImplementsInterface", + "openHttpURLConnection" + }; + assertThat( + "No unexpected errors should be found", + inferResults, + containsOnly( + CLASS_CAST_EXCEPTION, + ClassCastExceptions, + expectedMethods)); + } + +} diff --git a/infer/tests/endtoend/java/infer/CursorLeaksTest.java b/infer/tests/endtoend/java/infer/CursorLeaksTest.java new file mode 100644 index 000000000..129fe587d --- /dev/null +++ b/infer/tests/endtoend/java/infer/CursorLeaksTest.java @@ -0,0 +1,212 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package endtoend.java.infer; + + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; +import static utils.matchers.ResultContainsOnlyTheseErrors.containsOnly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class CursorLeaksTest { + + public static final String CursorLeaks = + "infer/tests/codetoanalyze/java/infer/CursorLeaks.java"; + + public static final String RESOURCE_LEAK = "RESOURCE_LEAK"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws IOException { + inferResults = InferResults.loadInferResults(CursorLeaksTest.class, CursorLeaks); + } + + @Test + public void whenInferRunsOnCursorClosedThenResourceLeakIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, CursorLeaks, + "cursorClosed")); + } + + @Test + public void whenInferRunsOnCursorNotClosedThenResourceLeakIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + CursorLeaks, + "cursorNotClosed" + ) + ); + } + + @Test + public void whenInferRunsOnGetImageCountHelperNotClosedThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + CursorLeaks, + "getImageCountHelperNotClosed" + ) + ); + } + + @Test + public void whenInferRunsOnGetImageCountHelperClosedThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, CursorLeaks, + "getImageCountHelperClosed")); + } + + @Test + public void whenInferRunsOnGetBucketCountNotClosedThenResourceLeakIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + CursorLeaks, + "getBucketCountNotClosed" + ) + ); + } + + @Test + public void whenInferRunsOnGetBucketCountClosedThenResourceLeakIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, CursorLeaks, + "getBucketCountClosed")); + } + + @Test + public void whenInferRunsOnQueryUVMLegacyDbNotClosedThenResourceLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + CursorLeaks, + "queryUVMLegacyDbNotClosed" + ) + ); + } + + @Test + public void whenInferRunsOnQueryUVMLegacyDbClosedThenResourceLeakIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, CursorLeaks, + "queryUVMLegacyDbClosed")); + } + + @Test + public void whenInferRunsOnCompleteDownloadNotClosedThenResourceLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + CursorLeaks, + "completeDownloadNotClosed" + ) + ); + } + + @Test + public void whenInferRunsOnCompleteDownloadClosedThenResourceLeakIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, CursorLeaks, + "completeDownloadClosed")); + } + + @Test + public void whenInferRunsOnLoadPrefsFromContentProvNotClosedThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + CursorLeaks, + "loadPrefsFromContentProviderNotClosed" + ) + ); + } + + @Test + public void whenInferRunsOnLoadPrefsFromContentProvClosedThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, CursorLeaks, + "loadPrefsFromContentProviderClosed")); + } + + + @Test + public void whenInferRunsOnResourceLeaksThenOnlyTheExpectedErrorsAreFound() + throws InterruptedException, IOException, InferException { + String[] expectedMethods = { + "cursorClosed", + "cursorNotClosed", + "getImageCountHelperClosed", + "getImageCountHelperNotClosed", + "getBucketCountNotClosed", + "getBucketCountClosed", + "queryUVMLegacyDbNotClosed", + "queryUVMLegacyDbClosed", + "completeDownloadClosed", + "completeDownloadNotClosed", + "loadPrefsFromContentProviderClosed", + "loadPrefsFromContentProviderNotClosed" + }; + assertThat( + "No unexpected errors should be found", inferResults, + containsOnly( + RESOURCE_LEAK, + CursorLeaks, + expectedMethods)); + } + +} diff --git a/infer/tests/endtoend/java/infer/DivideByZeroTest.java b/infer/tests/endtoend/java/infer/DivideByZeroTest.java new file mode 100644 index 000000000..ef6a6280e --- /dev/null +++ b/infer/tests/endtoend/java/infer/DivideByZeroTest.java @@ -0,0 +1,102 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package endtoend.java.infer; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; +import static utils.matchers.ResultContainsLineNumbers.containsLines; +import static utils.matchers.ResultContainsOnlyTheseErrors.containsOnly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class DivideByZeroTest { + + public static final String DivideByZero = + "infer/tests/codetoanalyze/java/infer/DivideByZero.java"; + + public static final String DIVIDE_BY_ZERO = "DIVIDE_BY_ZERO"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = InferResults.loadInferResults(DivideByZeroTest.class, DivideByZero); + } + + @Test + public void whenInferRunsOnDivideByZeroLocalThenDivideByZeroErrorFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain divide by zero error.", inferResults, + contains( + DIVIDE_BY_ZERO, + DivideByZero, + "divByZeroLocal" + ) + ); + } + + @Test + public void whenInferRunsOnCallDivideByZeroInterProcThenDivideByZeroErrorFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain divide by zero error.", + inferResults, + contains( + DIVIDE_BY_ZERO, + DivideByZero, + "callDivideByZeroInterProc" + ) + ); + } + + @Test + public void whenInferRunsOnDivideByZeroWithStaticFieldThenDivideByZeroErrorFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain divide by zero error.", + inferResults, + contains( + DIVIDE_BY_ZERO, + DivideByZero, + "divideByZeroWithStaticField" + ) + ); + } + + @Test + public void whenInferRunsOnDivideByZeroThenTheLineNumbersAreReportedCorr() + throws InterruptedException, IOException, InferException { + int[] lines = {24}; + assertThat( + "Result should contain correct line numbers.", + inferResults, + containsLines(lines)); + } + + @Test + public void whenInferRunsOnDivideByZeroThenOnlyTheExpectedErrorsAreFound() + throws InterruptedException, IOException, InferException { + String[] expectedMethods = { + "divByZeroLocal", + "callDivideByZeroInterProc", + "divideByZeroWithStaticField" + }; + assertThat( + "No unexpected errors should be found", inferResults, + containsOnly( + DIVIDE_BY_ZERO, + DivideByZero, + expectedMethods)); + } + +} diff --git a/infer/tests/endtoend/java/infer/FilterInputStreamLeaksTest.java b/infer/tests/endtoend/java/infer/FilterInputStreamLeaksTest.java new file mode 100644 index 000000000..239cfba02 --- /dev/null +++ b/infer/tests/endtoend/java/infer/FilterInputStreamLeaksTest.java @@ -0,0 +1,314 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ +//Class to tests resource leaks on the class FilterInputStream and its subclasses +package endtoend.java.infer; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; +import static utils.matchers.ResultContainsOnlyTheseErrors.containsOnly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class FilterInputStreamLeaksTest { + + public static final String FilterInputStreamLeaks = + "infer/tests/codetoanalyze/java/infer/FilterInputStreamLeaks.java"; + + public static final String RESOURCE_LEAK = "RESOURCE_LEAK"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = InferResults.loadInferResults( + FilterInputStreamLeaksTest.class, + FilterInputStreamLeaks); + } + + //BufferedInputStream tests + @Test + public void whenInferRunsOnBufferedInputStreamNotClosedAfterThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + FilterInputStreamLeaks, + "bufferedInputStreamNotClosedAfterRead" + ) + ); + } + + @Test + public void whenInferRunsOnBufferedInputStreamClosedAfterThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + FilterInputStreamLeaks, + "bufferedInputStreamClosedAfterReset")); + } + + //CheckedInputStream tests + @Test + public void whenInferRunsOnCheckedInputStreamNotClosedAfterThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + FilterInputStreamLeaks, + "checkedInputStreamNotClosedAfterRead" + ) + ); + } + + @Test + public void whenInferRunsOnCheckedInputStreamClosedAfterThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + FilterInputStreamLeaks, + "checkedInputStreamClosedAfterSkip")); + } + + //CipherInputStream tests + @Test + public void whenInferRunsOnCipherInputStreamNotClosedAfterThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + FilterInputStreamLeaks, + "cipherInputStreamNotClosedAfterSkip" + ) + ); + } + + @Test + public void whenInferRunsOnCipherInputStreamClosedAfterThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + FilterInputStreamLeaks, + "cipherInputStreamClosedAfterRead")); + } + + //DataInputStream tests + @Test + public void whenInferRunsOnDataInputStreamNotClosedAfterReadThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + FilterInputStreamLeaks, + "dataInputStreamNotClosedAfterRead" + ) + ); + } + + @Test + public void whenInferRunsOnDataInputStreamClosedAfterReadThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + FilterInputStreamLeaks, + "dataInputStreamClosedAfterReadBoolean")); + } + + //DeflaterInputStream tests + @Test + public void whenInferRunsOnDeflaterInputStreamNotClosedAfterThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + FilterInputStreamLeaks, + "deflaterInputStreamNotClosedAfterRead" + ) + ); + } + + @Test + public void whenInferRunsOnDeflaterInputStreamClosedAfterThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + FilterInputStreamLeaks, + "deflaterInputStreamClosedAfterReset")); + } + + //GZipInputStream tests + @Test + public void whenInferRunsOnGzipInputStreamNotClosedAfterReadThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + FilterInputStreamLeaks, + "gzipInputStreamNotClosedAfterRead" + ) + ); + } + + @Test + public void whenInferRunsOnGzipInputStreamClosedAfterReadThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + FilterInputStreamLeaks, + "gzipInputStreamClosedAfterRead")); + } + + //DigestInputStream tests + @Test + public void whenInferRunsOnDigestInputStreamNotClosedAfterThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + FilterInputStreamLeaks, + "digestInputStreamNotClosedAfterRead" + ) + ); + } + + @Test + public void whenInferRunsOnDigestInputStreamClosedAfterThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + FilterInputStreamLeaks, + "digestInputStreamClosedAfterRead")); + } + + //InflaterInputStream tests + @Test + public void whenInferRunsOnInflaterInputStreamNotClosedAfterThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + FilterInputStreamLeaks, + "inflaterInputStreamNotClosedAfterRead" + ) + ); + } + + @Test + public void whenInferRunsOnInflaterInputStreamClosedAfterThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + FilterInputStreamLeaks, + "inflaterInputStreamClosedAfterAvailable")); + } + + //PushbackInputStream tests + @Test + public void whenInferRunsOnInPushbackInputStreamNotClosedThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + FilterInputStreamLeaks, + "pushbackInputStreamNotClosedAfterRead" + ) + ); + } + + @Test + public void whenInferRunsOnPushbackInputStreamClosedAfterThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + FilterInputStreamLeaks, + "pushbackInputStreamClosedAfterReset")); + } + + @Test + public void whenInferRunsThenOnlyTheExpectedErrorsAreFound() + throws InterruptedException, IOException, InferException { + String[] expectedMethods = { + "bufferedInputStreamNotClosedAfterRead", + "bufferedInputStreamClosedAfterReset", + "checkedInputStreamNotClosedAfterRead", + "checkedInputStreamClosedAfterSkip", + "cipherInputStreamNotClosedAfterSkip", + "cipherInputStreamClosedAfterRead", + "dataInputStreamNotClosedAfterRead", + "dataInputStreamClosedAfterReadBoolean", + "deflaterInputStreamNotClosedAfterRead", + "deflaterInputStreamClosedAfterReset", + "gzipInputStreamNotClosedAfterRead", + "gzipInputStreamClosedAfterRead", + "digestInputStreamNotClosedAfterRead", + "digestInputStreamClosedAfterRead", + "inflaterInputStreamNotClosedAfterRead", + "inflaterInputStreamClosedAfterAvailable", + "progressMonitorInputStreamNotClosedAfterRead", + "progressMonitorInputStreamClosedAfterSkip", + "pushbackInputStreamNotClosedAfterRead", + "pushbackInputStreamClosedAfterReset", + + }; + assertThat( + "No unexpected errors should be found", inferResults, + containsOnly( + RESOURCE_LEAK, + FilterInputStreamLeaks, + expectedMethods)); + } + +} diff --git a/infer/tests/endtoend/java/infer/FilterOutputStreamLeaksTest.java b/infer/tests/endtoend/java/infer/FilterOutputStreamLeaksTest.java new file mode 100644 index 000000000..6a8ba3cbf --- /dev/null +++ b/infer/tests/endtoend/java/infer/FilterOutputStreamLeaksTest.java @@ -0,0 +1,349 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ +//Class to tests resource leaks on the class FilterOutputStream and its subclasses +package endtoend.java.infer; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; +import static utils.matchers.ResultContainsOnlyTheseErrors.containsOnly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class FilterOutputStreamLeaksTest { + + public static final String FilterOutputStreamLeaks = + "infer/tests/codetoanalyze/java/infer/FilterOutputStreamLeaks.java"; + + public static final String RESOURCE_LEAK = "RESOURCE_LEAK"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = InferResults.loadInferResults( + FilterOutputStreamLeaksTest.class, + FilterOutputStreamLeaks); + } + + //FilterOutputStream tests + @Test + public void whenInferRunsOnFilterOutputStreamNotClosedAfterThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + FilterOutputStreamLeaks, + "filterOutputStreamNotClosedAfterWrite" + ) + ); + } + + @Test + public void whenInferRunsOnFilterOutputStreamClosedAfterThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + FilterOutputStreamLeaks, + "filterOutputStreamClosedAfterWrite" + ) + ); + } + + //DataOutputStream tests + @Test + public void whenInferRunsOnDataOutputStreamNotClosedAfterThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + FilterOutputStreamLeaks, + "dataOutputStreamNotClosedAfterWrite" + ) + ); + } + + @Test + public void whenInferRunsOnDataOutputStreamClosedAfterThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + FilterOutputStreamLeaks, + "dataOutputStreamClosedAfterWrite" + ) + ); + } + + //BufferedOutputStream tests + @Test + public void whenInferRunsOnBufferedOutputStreamNotClosedAfterThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + FilterOutputStreamLeaks, + "bufferedOutputStreamNotClosedAfterWrite" + ) + ); + } + + @Test + public void whenInferRunsOnBufferedOutputStreamClosedAftThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + FilterOutputStreamLeaks, + "bufferedOutputStreamClosedAfterWrite" + ) + ); + } + + //CheckedOutputStream tests + @Test + public void whenInferRunsOnCheckedOutputStreamNotClosedAfterThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + FilterOutputStreamLeaks, + "checkedOutputStreamNotClosedAfterWrite" + ) + ); + } + + @Test + public void whenInferRunsOnCheckedOutputStreamClosedAfterThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + FilterOutputStreamLeaks, + "checkedOutputStreamClosedAfterWrite")); + } + + //CipherOutputStream tests + @Test + public void whenInferRunsOnCipherOutputStreamNotClosedAfterThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + FilterOutputStreamLeaks, + "cipherOutputStreamNotClosedAfterWrite" + ) + ); + } + + @Test + public void whenInferRunsOnCipherOutputStreamClosedAfterThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + FilterOutputStreamLeaks, + "cipherOutputStreamClosedAfterWrite")); + } + + //DeflaterOutputStream tests + @Test + public void whenInferRunsOnDeflaterOutputStreamNotClosedAfterThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + FilterOutputStreamLeaks, + "deflaterOutputStreamNotClosedAfterWrite" + ) + ); + } + + @Test + public void whenInferRunsOnDeflaterOutputStreamClosedAfterThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + FilterOutputStreamLeaks, + "deflaterOutputStreamClosedAfterWrite")); + } + + //GZipOutputStream tests + @Test + public void whenInferRunsOnGzipOutputStreamNotClosedAfterThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + FilterOutputStreamLeaks, + "gzipOutputStreamNotClosedAfterFlush" + ) + ); + } + + @Test + public void whenInferRunsOnGzipOutputStreamClosedAfterThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + FilterOutputStreamLeaks, + "gzipOutputStreamClosedAfterWrite")); + } + + //DigestOutputStream tests + @Test + public void whenInferRunsOnDigestOutputStreamNotClosedAfterThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + FilterOutputStreamLeaks, + "digestOutputStreamNotClosedAfterWrite" + ) + ); + } + + @Test + public void whenInferRunsOnDigestOutputStreamClosedAfterThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + FilterOutputStreamLeaks, + "digestOutputStreamClosedAfterWrite")); + } + + //InflaterOutputStream tests + @Test + public void whenInferRunsOnInflaterOutputStreamNotClosedAfterThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + FilterOutputStreamLeaks, + "inflaterOutputStreamNotClosedAfterWrite" + ) + ); + } + + @Test + public void whenInferRunsOnInflaterOutputStreamClosedAfterThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + FilterOutputStreamLeaks, + "inflaterOutputStreamClosedAfterWrite")); + } + + //PrintStream tests + @Test + public void whenInferRunsOnPrintStreamNotClosedAfterWriteThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + FilterOutputStreamLeaks, + "printStreamNotClosedAfterWrite" + ) + ); + } + + @Test + public void whenInferRunsOnPrintStreamClosedAfterWriteThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + FilterOutputStreamLeaks, + "printStreamClosedAfterWrite" + ) + ); + } + + + @Test + public void whenInferRunsOnResourceLeaksThenOnlyTheExpectedErrorsAreFound() + throws InterruptedException, IOException, InferException { + String[] expectedMethods = { + "filterOutputStreamNotClosedAfterWrite", + "filterOutputStreamClosedAfterWrite", + "dataOutputStreamNotClosedAfterWrite", + "dataOutputStreamClosedAfterWrite", + "bufferedOutputStreamNotClosedAfterWrite", + "bufferedOutputStreamClosedAfterWrite", + "checkedOutputStreamNotClosedAfterWrite", + "checkedOutputStreamClosedAfterWrite", + "cipherOutputStreamNotClosedAfterWrite", + "cipherOutputStreamClosedAfterWrite", + "deflaterOutputStreamNotClosedAfterWrite", + "deflaterOutputStreamClosedAfterWrite", + "digestOutputStreamNotClosedAfterWrite", + "digestOutputStreamClosedAfterWrite", + "inflaterOutputStreamNotClosedAfterWrite", + "inflaterOutputStreamClosedAfterWrite", + "gzipOutputStreamNotClosedAfterFlush", + "gzipOutputStreamClosedAfterWrite", + "printStreamNotClosedAfterWrite", + "printStreamClosedAfterWrite", + }; + assertThat( + "No unexpected errors should be found", inferResults, + containsOnly( + RESOURCE_LEAK, + FilterOutputStreamLeaks, + expectedMethods)); + } + +} diff --git a/infer/tests/endtoend/java/infer/HashMapModelTest.java b/infer/tests/endtoend/java/infer/HashMapModelTest.java new file mode 100644 index 000000000..e93c292f5 --- /dev/null +++ b/infer/tests/endtoend/java/infer/HashMapModelTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.java.infer; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; +import static utils.matchers.ResultContainsOnlyTheseErrors.containsOnly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class HashMapModelTest { + + public static final String HashMapModelTest = + "infer/tests/codetoanalyze/java/infer/HashMapModelTest.java"; + + public static final String NULL_DEREFERENCE = "NULL_DEREFERENCE"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = InferResults.loadInferResults( + HashMapModelTest.class, + HashMapModelTest); + } + + @Test + public void whenInferRunsOnPutIntegerTwiceThenGetTwiceThenNPEIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain null pointer exception error", + inferResults, + doesNotContain( + NULL_DEREFERENCE, + HashMapModelTest, + "putIntegerTwiceThenGetTwice") + ); + } + + @Test + public void whenInferRunsOnContainsIntegerTwiceThenGetTwiceThenNPEIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain null pointer exception error", + inferResults, + doesNotContain( + NULL_DEREFERENCE, + HashMapModelTest, + "containsIntegerTwiceThenGetTwice") + ); + } + + @Test + public void whenInferRunsOnGetOneIntegerWithoutCheckThenNPEIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain null pointer exception error", + inferResults, + contains( + NULL_DEREFERENCE, + HashMapModelTest, + "getOneIntegerWithoutCheck") + ); + } + + @Test + public void whenInferRunsOnGetTwoIntegersWithOneCheckThenNPEIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain null pointer exception error", + inferResults, + contains( + NULL_DEREFERENCE, + HashMapModelTest, + "getTwoIntegersWithOneCheck") + ); + } + + @Test + public void whenInferRunsOnGetTwoParameterIntegersWithOneCheckThenNPEIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain null pointer exception error", + inferResults, + contains( + NULL_DEREFERENCE, + HashMapModelTest, + "getTwoParameterIntegersWithOneCheck") + ); + } + +} diff --git a/infer/tests/endtoend/java/infer/JunitAssertionTest.java b/infer/tests/endtoend/java/infer/JunitAssertionTest.java new file mode 100644 index 000000000..9817ad926 --- /dev/null +++ b/infer/tests/endtoend/java/infer/JunitAssertionTest.java @@ -0,0 +1,57 @@ +package endtoend.java.infer; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class JunitAssertionTest { + + public static final String JunitAssertionFile = + "infer/tests/codetoanalyze/java/infer/JunitAssertion.java"; + + public static final String NULL_DEREFERENCE = "NULL_DEREFERENCE"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = InferResults.loadInferResults(JunitAssertionTest.class, JunitAssertionFile); + } + + @Test + public void inferShouldUseAssertedPredicate() + throws IOException, InterruptedException, InferException { + assertThat( + "Results should contain null pointer exception error", + inferResults, + doesNotContain( + NULL_DEREFERENCE, + JunitAssertionFile, + "consistentAssumtion" + ) + ); + } + + @Test + public void inferShouldUseAssertionInconsistency() + throws IOException, InterruptedException, InferException { + assertThat( + "Results should contain null pointer exception error", + inferResults, + contains( + NULL_DEREFERENCE, + JunitAssertionFile, + "inconsistentAssertion" + ) + ); + } + +} diff --git a/infer/tests/endtoend/java/infer/NullPointerExceptionTest.java b/infer/tests/endtoend/java/infer/NullPointerExceptionTest.java new file mode 100644 index 000000000..eee08c811 --- /dev/null +++ b/infer/tests/endtoend/java/infer/NullPointerExceptionTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.java.infer; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsExactly.containsExactly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class NullPointerExceptionTest { + + public static final String SOURCE_FILE = + "infer/tests/codetoanalyze/java/infer/NullPointerExceptions.java"; + + public static final String NULL_DEREFERENCE = "NULL_DEREFERENCE"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = InferResults.loadInferResults( + NullPointerExceptionTest.class, + SOURCE_FILE); + } + + @Test + public void matchErrors() + throws IOException, InterruptedException, InferException { + String[] methods = { + "nullPointerException", + "nullPointerExceptionInterProc", + "nullPointerExceptionWithExceptionHandling", + "nullPointerExceptionWithArray", + "nullPointerExceptionWithAChainOfFields", + "nullPointerExceptionWithNullObjectParameter", + "nullPointerExceptionWithNullArrayParameter", + "nullPointerExceptionFromFaillingResourceConstructor", + "nullPointerExceptionFromFailingFileOutputStreamConstructor", + "nullPointerExceptionUnlessFrameFails", + "hashmapNPE", + "NPEvalueOfFromHashmapBad", + "cursorFromContentResolverNPE", + "nullPointerExceptionInArrayLengthLoop", + "nullPointerExceptionCallArrayReadMethod", + "nullableFieldNPE", + "nullableParamNPE", + "badCheckShouldCauseNPE", + "nullPointerExceptionArrayLength" + }; + assertThat( + "Results should contain " + NULL_DEREFERENCE, + inferResults, + containsExactly( + NULL_DEREFERENCE, + SOURCE_FILE, + methods + ) + ); + } + +} diff --git a/infer/tests/endtoend/java/infer/ReaderLeaksTest.java b/infer/tests/endtoend/java/infer/ReaderLeaksTest.java new file mode 100644 index 000000000..cff241182 --- /dev/null +++ b/infer/tests/endtoend/java/infer/ReaderLeaksTest.java @@ -0,0 +1,299 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ +//Class to tests resource leaks on the class FilterOutputStream and its subclasses +package endtoend.java.infer; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; +import static utils.matchers.ResultContainsOnlyTheseErrors.containsOnly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; +import java.nio.file.Path; + +import utils.InferException; +import utils.InferResults; + +public class ReaderLeaksTest { + + Path t; + + public static final String ReaderLeaks = + "infer/tests/codetoanalyze/java/infer/ReaderLeaks.java"; + + public static final String RESOURCE_LEAK = "RESOURCE_LEAK"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = InferResults.loadInferResults(ReaderLeaksTest.class, ReaderLeaks); + } + + //Reader tests + @Test + public void whenInferRunsOnReaderNotClosedAfterReadThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ReaderLeaks, + "readerNotClosedAfterRead" + ) + ); + } + + @Test + public void whenInferRunsOnReaderClosedThenResourceLeakIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ReaderLeaks, + "readerClosed")); + } + + //BufferedReader tests + @Test + public void whenInferRunsOnBufferedReaderNotClosedAfterReadThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ReaderLeaks, + "bufferedReaderNotClosedAfterRead" + ) + ); + } + + @Test + public void whenInferRunsOnBufferedReaderClosedThenResourceLeakIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ReaderLeaks, + "bufferedReaderClosed")); + } + + //InputStreamReader tests + @Test + public void whenInferRunsOnInputStreamReadNotClosedAfterReadThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ReaderLeaks, + "inputStreamReaderNotClosedAfterRead" + ) + ); + } + + @Test + public void whenInferRunsOnInputStreamReaderClosedThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ReaderLeaks, + "inputStreamReaderClosed")); + } + + //FileReader tests + + @Test + public void whenInferRunsOnFileReaderNotClosedAfterReadThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ReaderLeaks, + "fileReaderNotClosedAfterRead" + ) + ); + } + + @Test + public void whenInferRunsOnFileReaderClosedThenResourceLeakIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ReaderLeaks, + "fileReaderClosed")); + } + + //PushbackReader tests + @Test + public void whenInferRunsOnPushbackReaderNotClosedAfterReadThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ReaderLeaks, + "pushbackReaderNotClosedAfterRead" + ) + ); + } + + @Test + public void whenInferRunsOnPushbackReaderClosedThenResourceLeakIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ReaderLeaks, + "pushbackReaderClosed")); + } + + //PipedReader tests + @Test + public void whenInferRunsOnPipedReaderNotClosedAfterThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ReaderLeaks, + "pipedReaderNotClosedAfterConstructedWithWriter" + ) + ); + } + + @Test + public void whenInferRunsOnPipedReaderNotClosedAfterConnectThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ReaderLeaks, + "pipedReaderNotClosedAfterConnect" + ) + ); + } + + @Test + public void whenInferRunsOnPipedReaderClosedThenResourceLeakIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ReaderLeaks, + "pipedReaderClosed")); + } + + @Test + public void whenInferRunsOnPipedReaderNotConnectedThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ReaderLeaks, + "pipedReaderNotConnected")); + } + + /** + * This potential false positive is up for discussion... + */ + @Test + public void whenInferRunsOnPipedReaderFalsePositiveThenResourceLeakIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ReaderLeaks, + "pipedReaderFalsePositive" + ) + ); + } + + @Test + public void whenInferRunsOnStrictLineReaderClosedThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ReaderLeaks, + "strictLineReaderClosed")); + } + + @Test + public void whenInferRunsOnStrictLineReaderLeakThenResourceLeakIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ReaderLeaks, + "strictLineReaderNoLeak")); + } + + + @Test + public void whenInferRunsOnReaderLeaksThenOnlyTheExpectedErrorsAreFound() + throws InterruptedException, IOException, InferException { + String[] expectedMethods = { + "readerNotClosedAfterRead", + "readerClosed", + "bufferedReaderNotClosedAfterRead", + "bufferedReaderClosed", + "inputStreamReaderNotClosedAfterRead", + "inputStreamReaderClosed", + "fileReaderNotClosedAfterRead", + "fileReaderClosed", + "pushbackReaderNotClosedAfterRead", + "pushbackReaderClosed", + "pipedReaderNotClosedAfterConstructedWithWriter", + "pipedReaderNotClosedAfterConnect", + "pipedReaderClosed", + "pipedReaderNotConnected", + "pipedReaderFalsePositive", + "strictLineReaderClosed", + "strictLineReaderNoLeak" + }; + assertThat( + "No unexpected errors should be found", inferResults, + containsOnly( + RESOURCE_LEAK, + ReaderLeaks, + expectedMethods)); + } + +} diff --git a/infer/tests/endtoend/java/infer/ResourceLeaksTest.java b/infer/tests/endtoend/java/infer/ResourceLeaksTest.java new file mode 100644 index 000000000..ee4a4a833 --- /dev/null +++ b/infer/tests/endtoend/java/infer/ResourceLeaksTest.java @@ -0,0 +1,1156 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package endtoend.java.infer; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; +import static utils.matchers.ResultContainsNumberOfErrorsInMethod.containsNumberOfErrors; +import static utils.matchers.ResultContainsOnlyTheseErrors.containsOnly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class ResourceLeaksTest { + + public static final String ResourceLeaks = + "infer/tests/codetoanalyze/java/infer/ResourceLeaks.java"; + + public static final String RESOURCE_LEAK = "RESOURCE_LEAK"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = InferResults.loadInferResults(ResourceLeaksTest.class, ResourceLeaks); + } + + + //FileOutputStream tests + + @Test + public void whenInferRunsOnFileOutputStreamNotClosedThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ResourceLeaks, + "fileOutputStreamNotClosed" + ) + ); + } + + @Test + public void whenInferRunsOnFileOutputStreamNotClosed2ThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ResourceLeaks, + "fileOutputStreamNotClosedAfterWrite" + ) + ); + } + + @Test + public void whenInferRunsOnFileOutputStreamClosedThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "fileOutputStreamClosed")); + } + + @Test + public void whenInferRunsOnFileOutputStreamTwoLeaksThenTwoLeaksAreFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain 2 resource leak error", + inferResults, + containsNumberOfErrors( + RESOURCE_LEAK, + ResourceLeaks, + "fileOutputStreamTwoLeaks", + 2 + ) + ); + } + + //TwoResource tests + @Test + public void whenInferRunsOnTwoResourcesHeliosFixThenResourceLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "twoResourcesHeliosFix" + ) + ); + } + + @Test + public void whenInferRunsOnTwoResourcesThenResourceLeakIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ResourceLeaks, + "twoResources" + ) + ); + } + + @Test + public void whenInferRunsOnTwoResourcesCommonFixThenResourceLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "twoResourcesCommonFix" + ) + ); + } + + @Test + public void whenInferRunsOnTwoResourcesServerSocketThenResourceLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ResourceLeaks, + "twoResourcesServerSocket" + ) + ); + } + + @Test + public void whenInferRunsOnTwoResourcesRandomAccessFileThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ResourceLeaks, + "twoResourcesRandomAccessFile" + ) + ); + } + + + @Test + public void whenInferRunsOnTwoResourcesRAFCommonFixThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "twoResourcesRandomAccessFileCommonFix" + ) + ); + } + + //NestedResource tests + + @Test + public void whenInferRunsOnNestedGoodThenResourceLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "nestedGood" + ) + ); + } + + @Test + public void whenInferRunsOnNestedBad1ThenResourceLeakIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ResourceLeaks, + "nestedBad1" + ) + ); + } + + + @Test + public void whenInferRunsOnNestedBad2ThenResourceLeakIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ResourceLeaks, + "nestedBad2" + ) + ); + } + + + @Test + public void whenInferRunsOnObjectInputStreamClosedNestedBadThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ResourceLeaks, + "objectInputStreamClosedNestedBad" + ) + ); + } + + @Test + public void whenInferRunsOnObjectOutputStreamClosedNestedBadThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ResourceLeaks, + "objectOutputStreamClosedNestedBad" + ) + ); + } + + + //ZipFile tests (JarFile tests also test ZipFiles) + + @Test + public void whenInferRunsOnZipFileLeakExceptionalBranchThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, ResourceLeaks, + "zipFileLeakExceptionalBranch" + ) + ); + } + + @Test + public void whenInferRunsOnzipFileNoLeakThenResourceLeakIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "jarFileClosed" + ) + ); + } + + //JarFile tests + + @Test + public void whenInferRunsOnJarFileNotClosedThenResourceLeakIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, ResourceLeaks, + "jarFileNotClosed" + ) + ); + } + + @Test + public void whenInferRunsOnJarFileClosedThenResourceLeakIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "jarFileClosed" + ) + ); + } + + //FileInputStream tests + @Test + public void whenInferRunsOnFileInputStreamNotClosedAfterReadThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, ResourceLeaks, + "fileInputStreamNotClosedAfterRead" + ) + ); + } + + @Test + public void whenInferRunsOnFileInputStreamClosedThenResourceLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "fileInputStreamClosed" + ) + ); + } + + //PipedInputStream tests + @Test + public void whenInferRunsOnPipedInputStreamNotClosedAftReadThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, ResourceLeaks, + "pipedInputStreamNotClosedAfterRead" + ) + ); + } + + @Test + public void whenInferRunsOnPipedInputStreamClosedThenResourceLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "pipedInputStreamClosed" + ) + ); + } + + //PipedOutputStream tests + @Test + public void whenInferRunsOnPipedOutputStreamNotClosedAfterThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ResourceLeaks, + "pipedOutputStreamNotClosedAfterWrite" + ) + ); + } + + @Test + public void whenInferRunsOnPipedOutputStreamClosedThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "pipedOutputStreamClosed" + ) + ); + } + + //ObjectOutputStream tests + @Test + public void whenInferRunsOnObjectOutputStreamNotClosedAfterThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ResourceLeaks, + "objectOutputStreamNotClosedAfterWrite" + ) + ); + } + + @Test + public void whenInferRunsOnObjectOutputStreamClosedThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "objectOutputStreamClosed" + ) + ); + } + + @Test + public void whenInferRunsOnObjectOutputStreamClosed2ThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "objectOutputStreamClosed2" + ) + ); + } + + //ObjectInputStream tests + @Test + public void whenInferRunsOnObjectInputStreamNotClosedAfterThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ResourceLeaks, + "objectInputStreamNotClosedAfterRead" + ) + ); + } + + @Test + public void whenInferRunsOnObjectInputStreamClosedThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "objectInputStreamClosed" + ) + ); + } + + //JarInputStream tests + @Test + public void whenInferRunsJarInputStreamLeakThenResourceLeakIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ResourceLeaks, + "jarInputStreamLeak" + ) + ); + } + + @Test + public void whenInferRunsOnJarInputStreamNoLeakThenResourceLeakIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "jarInputStreamNoLeak" + ) + ); + } + + @Test + public void whenInferRunsNestedBadJarInputStreamThenResourceLeakIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ResourceLeaks, + "nestedBadJarInputStream" + ) + ); + } + + //JarOutputStream tests + @Test + public void whenInferRunsOnJarOutputStreamLeakThenResourceLeakIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, ResourceLeaks, + "jarOutputStreamLeak" + ) + ); + } + + @Test + public void whenInferRunsOnJarOutputStreamNoLeakThenResourceLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "jarOutputStreamNoLeak" + ) + ); + } + + + @Test + public void whenInferRunsNestedBadJarOutputStreamThenResourceLeakIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, ResourceLeaks, + "nestedBadJarOutputStream" + ) + ); + } + + + //Socket tests + @Test + public void whenInferRunsOnSocketNotClosedThenResourceLeakIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ResourceLeaks, + "socketNotClosed" + ) + ); + } + + @Test + public void whenInferRunsOnSocketClosedThenResourceLeakIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "socketClosed" + ) + ); + } + + @Test + public void whenInferRunsOnSocketInputStreamNotClosedThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "socketInputStreamNotClosed" + ) + ); + } + + @Test + public void whenInferRunsOnSocketInputStreamClosedThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "socketInputStreamClosed" + ) + ); + } + + @Test + public void whenInferRunsOnSocketOutputStreamNotClosedThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "socketOutputStreamNotClosed" + ) + ); + } + + @Test + public void whenInferRunsOnSocketOutputStreamClosedThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "socketOutputStreamClosed" + ) + ); + } + + //ServerSocket tests + @Test + public void whenInferRunsOnServerSocketNotClosedThenResourceLeakIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ResourceLeaks, + "serverSocketNotClosed" + ) + ); + } + + @Test + public void whenInferRunsOnServerSocketClosedThenResourceLeakIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "serverSocketClosed" + ) + ); + } + + @Test + public void whenInferRunsOnServerSocketWithSocketClosedThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "serverSocketWithSocketClosed" + ) + ); + } + + //HttpURLConnection + @Test + public void whenInferRunsOnOpenHttpURLConnectionNotDisconnThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, ResourceLeaks, + "openHttpURLConnectionNotDisconnected" + ) + ); + } + + @Test + public void whenInferRunsOnOpenHttpURLConnectionDisconnThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "openHttpURLConnectionDisconnected" + ) + ); + } + + @Test + public void whenInferRunsOnOpenHttpsURLConnectionNotDisconnThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ResourceLeaks, + "openHttpsURLConnectionNotDisconnected" + ) + ); + } + + @Test + public void whenInferRunsOnOpenHttpsURLConnectionDisconnThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "openHttpsURLConnectionDisconnected" + ) + ); + } + + //Closeables.close + @Test + public void whenInferRunsOnClosedWithCloseablesThenResourceLeakIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "closedWithCloseables" + ) + ); + } + + @Test + public void whenInferRunsOnNullClosedWithCloseablesThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "closeNullWithCloseables" + ) + ); + } + + //Closeables.closeQuietly + @Test + public void whenInferRunsOnClosedQuietlyWithCloseablesThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "closedQuietlyWithCloseables" + ) + ); + } + + @Test + public void whenInferRunsOnNullClosedQuietlyWithCloseablesThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "closeNullQuietlyWithCloseables" + ) + ); + } + + // JsonParser + @Test + public void whenInferRunsOnParseFromStringNoLeakIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "parseFromStringAndNotClose" + ) + ); + } + + @Test + public void whenInferRunsOnParseFromInputStreamAndCloseNoLeakIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "parseFromInputStreamAndClose" + ) + ); + } + + @Test + public void whenInferRunsOnParseFromInputStreamAndLeakLeakIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ResourceLeaks, + "parseFromInputStreamAndLeak" + ) + ); + } + +// Irritating Installation.java fp that has been banished hopefully forever + + @Test + public void whenInferRunsOnreadInstallationFileBad() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ResourceLeaks, + "readInstallationFileBad" + ) + ); + } + + @Test + public void whenInferRunsOnreadInstallationFileGood() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "readInstallationFileGood" + ) + ); + } + + + //URLConnection tests + @Test + public void whenInferRunsOnReadConfigCloseStreamThenLeakIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "readConfigCloseStream" + ) + ); + } + + @Test + public void whenInferRunsOnReadConfigNotCloseStreamThenLeakIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ResourceLeaks, + "readConfigNotCloseStream" + ) + ); + } + + @Test + public void whenInferRunsOnReadConfigNotClosedOK() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "readConfigNotClosedOK" + ) + ); + } + + @Test + public void whenInferRunsThemeObtainTypedArrayAndRecycle() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "themeObtainTypedArrayAndRecycle" + ) + ); + } + + @Test + public void whenInferRunsThemeObtainTypedArrayAndLeak() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ResourceLeaks, + "themeObtainTypedArrayAndLeak" + ) + ); + } + + @Test + public void whenInferRunsActivityObtainTypedArrayAndRecycle() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "activityObtainTypedArrayAndRecycle" + ) + ); + } + + @Test + public void whenInferRunsActivityObtainTypedArrayAndLeak() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ResourceLeaks, + "activityObtainTypedArrayAndLeak" + ) + ); + } + + @Test + public void whenInferRunsContextObtainTypedArrayAndRecycle() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "contextObtainTypedArrayAndRecycle" + ) + ); + } + + @Test + public void whenInferRunsContextObtainTypedArrayAndLeak() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ResourceLeaks, + "contextObtainTypedArrayAndLeak" + ) + ); + } + + @Test + public void whenInferRunsOnCopyFileLeak() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ResourceLeaks, + "copyFileLeak" + ) + ); + } + + @Test + public void whenInferRunsOnCopyFileClose() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "copyFileClose" + ) + ); + } + + @Test + public void whenInferRunsOnCheckNotNullCauseNoLeakClose() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "checkNotNullCauseNoLeak" + ) + ); + } + + // java.utils.Scanner + @Test + public void whenInferRunsOnScannerNotClosedThenResourceLeakIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + ResourceLeaks, + "scannerNotClosed" + ) + ); + } + @Test + public void whenInferRunsOnScannerClosedThenResourceLeakIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + ResourceLeaks, + "scannerClosed" + ) + ); + } + + + // remember to put method names here + @Test + public void whenInferRunsOnResourceLeaksThenOnlyTheExpectedErrorsAreFound() + throws InterruptedException, IOException, InferException { + String[] expectedMethods = { + "fileOutputStreamNotClosed", + "fileOutputStreamNotClosedAfterWrite", + "fileOutputStreamClosed", + "fileOutputStreamTwoLeaks", + "twoResources", + "twoResourcesHeliosFix", + "twoResourcesCommonFix", + "twoResourcesServerSocket", + "twoResourcesRandomAccessFile", + "twoResourcesRandomAccessFileCommonFix", + "nestedGood", + "nestedBad1", + "nestedBad2", + "objectInputStreamClosedNestedBad", + "objectOutputStreamClosedNestedBad", + "zipFileLeakExceptionalBranch", + "zipFileNoLeak", + "jarFileNotClosed", + "jarFileClosed", + "fileInputStreamNotClosedAfterRead", + "fileInputStreamClosed", + "pipedInputStreamNotClosedAfterRead", + "pipedInputStreamClosed", + "pipedOutputStreamNotClosedAfterWrite", + "pipedOutputStreamClosed", + "objectOutputStreamNotClosedAfterWrite", + "objectOutputStreamClosed", + "objectInputStreamNotClosedAfterRead", + "objectInputStreamClosed", + "objectInputStreamClosed2", + "jarInputStreamNoLeak", + "jarInputStreamLeak", + "jarOutputStreamNoLeak", + "jarOutputStreamLeak", + "nestedBadJarInputStream", + "nestedBadJarOutputStream", + "socketNotClosed", + "socketClosed", + "socketInputStreamNotClosed", + "socketInputStreamClosed", + "socketOutputStreamNotClosed", + "socketOutputStreamClosed", + "serverSocketNotClosed", + "serverSocketClosed", + "serverSocketWithSocketClosed", + "openHttpURLConnectionDisconnected", + "openHttpURLConnectionNotDisconnected", + "openHttpsURLConnectionNotDisconnected", + "openHttpsURLConnectionDisconnected", + "closedWithCloseables", + "closedQuietlyWithCloseables", + "parseFromStringAndClose", + "parseFromStringAndLeak", + "parseFromInputStreamAndClose", + "parseFromInputStreamAndLeak", + "ignore", + "readInstallationFileGood", + "readInstallationFileBad", + "readConfigCloseStream", + "readConfigNotCloseStream", + "readConfigNotClosedOK", + "themeObtainTypedArrayAndRecycle", + "themeObtainTypedArrayAndLeak", + "activityObtainTypedArrayAndRecycle", + "activityObtainTypedArrayAndLeak", + "contextObtainTypedArrayAndRecycle", + "contextObtainTypedArrayAndLeak", + "copyFileClose", + "copyFileLeak", + "checkNotNullCauseNoLeak", + "scannerNotClosed", + "scannerClosed" + }; + assertThat( + "No unexpected errors should be found", inferResults, + containsOnly( + RESOURCE_LEAK, + ResourceLeaks, + expectedMethods)); + } + +} diff --git a/infer/tests/endtoend/java/infer/ReturnValueIgnoredTest.java b/infer/tests/endtoend/java/infer/ReturnValueIgnoredTest.java new file mode 100644 index 000000000..023588ffa --- /dev/null +++ b/infer/tests/endtoend/java/infer/ReturnValueIgnoredTest.java @@ -0,0 +1,61 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package endtoend.java.infer; + + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; +import static utils.matchers.ResultContainsOnlyTheseErrors.containsOnly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class ReturnValueIgnoredTest { + + public static final String ReturnValueIgnored = + "infer/tests/codetoanalyze/java/infer/ReturnValueIgnored.java"; + + public static final String RETURN_VALUE_IGNORED = "RETURN_VALUE_IGNORED"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = InferResults.loadInferResults(ReturnValueIgnoredTest.class, ReturnValueIgnored); + } + + @Test + public void returnValueIgnoredTest() + throws IOException, InferException, InterruptedException { + assertThat( + "Results should contain a return value ignored error", + inferResults, + contains( + RETURN_VALUE_IGNORED, + ReturnValueIgnored, + "returnValueIgnored" + ) + ); + } + + @Test + public void whenInferRunsOnReturnValueIgThenOnlyTheExpectedErrorsAreFound() + throws InterruptedException, IOException, InferException { + String[] expectedMethods = {"returnValueIgnored"}; + assertThat( + "No unexpected errors should be found", inferResults, + containsOnly( + RETURN_VALUE_IGNORED, + ReturnValueIgnored, + expectedMethods)); + } + +} diff --git a/infer/tests/endtoend/java/infer/WriterLeaksTest.java b/infer/tests/endtoend/java/infer/WriterLeaksTest.java new file mode 100644 index 000000000..bdefceb45 --- /dev/null +++ b/infer/tests/endtoend/java/infer/WriterLeaksTest.java @@ -0,0 +1,264 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package endtoend.java.infer; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; +import static utils.matchers.ResultContainsOnlyTheseErrors.containsOnly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class WriterLeaksTest { + + + public static final String WriterLeaks = + "infer/tests/codetoanalyze/java/infer/WriterLeaks.java"; + + public static final String RESOURCE_LEAK = "RESOURCE_LEAK"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = InferResults.loadInferResults(WriterLeaksTest.class, WriterLeaks); + } + + //Writer tests + + @Test + public void whenInferRunsOnWriterNotClosedAfterWriteThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + WriterLeaks, + "writerNotClosedAfterWrite" + ) + ); + } + + @Test + public void whenInferRunsOnWriterClosedThenResourceLeakIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + WriterLeaks, + "writerClosed" + ) + ); + } + + //PrintWriter tests + + @Test + public void whenInferRunsOnPrintWriterNotClosedAfterAppendThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, WriterLeaks, + "printWriterNotClosedAfterAppend" + ) + ); + } + + @Test + public void whenInferRunsOnPrintWriterClosedThenResourceLeakIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, WriterLeaks, + "printWriterClosed" + ) + ); + } + + //BufferedWriter tests + + @Test + public void whenInferRunsOnBufferedWriterNotClosedAfterWriteThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + WriterLeaks, + "bufferedWriterNotClosedAfterWrite" + ) + ); + } + + @Test + public void whenInferRunsOnBufferedWriterClosedThenResourceLeakIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, WriterLeaks, + "bufferedWriterClosed" + ) + ); + } + + //OutputStreamWriter tests + @Test + public void whenInferRunsOnOutputStreamWriterNotClosedAfterThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + WriterLeaks, + "outputStreamWriterNotClosedAfterWrite" + ) + ); + } + + @Test + public void whenInferRunsOnOutputStreamWriterClosedThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, WriterLeaks, + "outputStreamWriterClosed" + ) + ); + } + + //FileWriter tests + @Test + public void whenInferRunsOnFileWriterNotClosedAfterWriteThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + WriterLeaks, + "fileWriterNotClosedAfterWrite" + ) + ); + } + + @Test + public void whenInferRunsOnFileWriterClosedThenResourceLeakIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + WriterLeaks, + "fileWriterClosed" + ) + ); + } + + //PipedWriter tests + @Test + public void whenInferRunsOnPipedWriterNotClosedAfterConstrThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + WriterLeaks, + "pipedWriterNotClosedAfterConstructedWithReader" + ) + ); + } + + @Test + public void whenInferRunsOnPipedWriterNotClosedAfterConnectThenRLIsFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should contain a resource leak error", + inferResults, + contains( + RESOURCE_LEAK, + WriterLeaks, + "pipedWriterNotClosedAfterConnect" + ) + ); + } + + @Test + public void whenInferRunsOnPipedWriterClosedThenResourceLeakIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + WriterLeaks, + "pipedWriterClosed" + ) + ); + } + + @Test + public void whenInferRunsOnPipedWriterNotConnectedThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + assertThat( + "Results should not contain a resource leak error", + inferResults, + doesNotContain( + RESOURCE_LEAK, + WriterLeaks, + "pipedWriterNotConnected" + ) + ); + } + + @Test + public void whenInferRunsOnPrintWriterThenOnlyTheExpectedErrorsAreFound() + throws InterruptedException, IOException, InferException { + String[] expectedMethods = { + "writerNotClosedAfterWrite", + "writerClosed", + "printWriterNotClosedAfterAppend", + "printWriterClosed", + "bufferedWriterNotClosedAfterWrite", + "bufferedWriterClosed", + "outputStreamWriterNotClosedAfterWrite", + "outputStreamWriterClosed", + "fileWriterNotClosedAfterWrite", + "fileWriterClosed", + "pipedWriterNotClosedAfterConstructedWithReader", + "pipedWriterNotClosedAfterConnect", + "pipedWriterClosed", + "pipedWriterNotConnected", + }; + assertThat( + "No unexpected errors should be found", inferResults, + containsOnly( + RESOURCE_LEAK, + WriterLeaks, + expectedMethods)); + } + +} diff --git a/infer/tests/endtoend/java/tracing/ArrayIndexOutOfBoundsExceptionTest.java b/infer/tests/endtoend/java/tracing/ArrayIndexOutOfBoundsExceptionTest.java new file mode 100644 index 000000000..941a8ad4b --- /dev/null +++ b/infer/tests/endtoend/java/tracing/ArrayIndexOutOfBoundsExceptionTest.java @@ -0,0 +1,48 @@ +// Copyright (c) 2015-Present Facebook. All rights reserved. + +package endtoend.java.tracing; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class ArrayIndexOutOfBoundsExceptionTest { + + public static final String SOURCE_FILE = + "infer/tests/codetoanalyze/java/tracing/ArrayIndexOutOfBoundsExceptionExample.java"; + + public static final String ARRAY_OUT_OF_BOUND = + "java.lang.ArrayIndexOutOfBoundsException"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = InferResults.loadTracingResults( + ArrayIndexOutOfBoundsExceptionTest.class, + SOURCE_FILE); + } + + @Test + public void whenEradicateRunsOnConstructorThenFieldNotInitializedIsFound() + throws IOException, InterruptedException, InferException { + assertThat( + "Results should contain " + ARRAY_OUT_OF_BOUND, + inferResults, + contains( + ARRAY_OUT_OF_BOUND, + SOURCE_FILE, + "callOutOfBound" + ) + ); + } + + +} diff --git a/infer/tests/endtoend/java/tracing/BUCK b/infer/tests/endtoend/java/tracing/BUCK new file mode 100644 index 000000000..c763f2155 --- /dev/null +++ b/infer/tests/endtoend/java/tracing/BUCK @@ -0,0 +1,16 @@ +java_test( + name='tracing', + srcs=glob(['*.java']), + deps=[ + '//dependencies/java/guava:guava', + '//dependencies/java/junit:hamcrest', + '//dependencies/java/junit:junit', + '//infer/tests/utils:utils', + ], + resources=[ + '//infer/tests/codetoanalyze/java/tracing:analyze', + ], + visibility=[ + 'PUBLIC', + ], +) diff --git a/infer/tests/endtoend/java/tracing/ClassCastExceptionTest.java b/infer/tests/endtoend/java/tracing/ClassCastExceptionTest.java new file mode 100644 index 000000000..1ce7b47bd --- /dev/null +++ b/infer/tests/endtoend/java/tracing/ClassCastExceptionTest.java @@ -0,0 +1,49 @@ +// Copyright (c) 2015-Present Facebook. All rights reserved. + +package endtoend.java.tracing; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsExactly.containsExactly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class ClassCastExceptionTest { + + public static final String SOURCE_FILE = + "infer/tests/codetoanalyze/java/tracing/ClassCastExceptionExample.java"; + + public static final String CLASS_CAST_EXCEPTION = + "java.lang.ClassCastException"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = InferResults.loadTracingResults( + ClassCastExceptionTest.class, + SOURCE_FILE); + } + + @Test + public void whenEradicateRunsOnConstructorThenFieldNotInitializedIsFound() + throws IOException, InterruptedException, InferException { + String[] methods = {"foo", "bar"}; + assertThat( + "Results should contain " + CLASS_CAST_EXCEPTION, + inferResults, + containsExactly( + CLASS_CAST_EXCEPTION, + SOURCE_FILE, + methods + ) + ); + } + + +} diff --git a/infer/tests/endtoend/java/tracing/LocallyDefinedExceptionTest.java b/infer/tests/endtoend/java/tracing/LocallyDefinedExceptionTest.java new file mode 100644 index 000000000..4f20dd99c --- /dev/null +++ b/infer/tests/endtoend/java/tracing/LocallyDefinedExceptionTest.java @@ -0,0 +1,46 @@ +package endtoend.java.tracing; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class LocallyDefinedExceptionTest { + + public static final String SOURCE_FILE = + "infer/tests/codetoanalyze/java/tracing/LocallyDefinedExceptionExample.java"; + + public static final String LOCALLY_DEFINED_EXCEPTION = + "codetoanalyze.java.tracing.LocallyDefinedException"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = InferResults.loadTracingResults( + LocallyDefinedExceptionTest.class, + SOURCE_FILE); + } + + @Test + public void whenEradicateRunsOnConstructorThenFieldNotInitializedIsFound() + throws IOException, InterruptedException, InferException { + assertThat( + "Results should contain " + LOCALLY_DEFINED_EXCEPTION, + inferResults, + contains( + LOCALLY_DEFINED_EXCEPTION, + SOURCE_FILE, + "fieldInvariant" + ) + ); + } + + +} diff --git a/infer/tests/endtoend/java/tracing/NullPointerExceptionTest.java b/infer/tests/endtoend/java/tracing/NullPointerExceptionTest.java new file mode 100644 index 000000000..c77d87438 --- /dev/null +++ b/infer/tests/endtoend/java/tracing/NullPointerExceptionTest.java @@ -0,0 +1,49 @@ +// Copyright (c) 2015-Present Facebook. All rights reserved. + +package endtoend.java.tracing; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class NullPointerExceptionTest { + + public static final String SOURCE_FILE = + "infer/tests/codetoanalyze/java/tracing/NullPointerExceptionExample.java"; + + public static final String NPE = + "java.lang.NullPointerException"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = InferResults.loadTracingResults( + ArrayIndexOutOfBoundsExceptionTest.class, + SOURCE_FILE); + } + + @Test + public void whenEradicateRunsOnConstructorThenFieldNotInitializedIsFound() + throws IOException, InterruptedException, InferException { + assertThat( + "Results should contain " + NPE, + inferResults, + contains( + NPE, + SOURCE_FILE, + "callDeref" + ) + ); + } + + + +} diff --git a/infer/tests/endtoend/java/tracing/ReportOnMainTest.java b/infer/tests/endtoend/java/tracing/ReportOnMainTest.java new file mode 100644 index 000000000..fd06b9dbf --- /dev/null +++ b/infer/tests/endtoend/java/tracing/ReportOnMainTest.java @@ -0,0 +1,48 @@ +// Copyright (c) 2015-Present Facebook. All rights reserved. + +package endtoend.java.tracing; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class ReportOnMainTest { + + public static final String SOURCE_FILE = + "infer/tests/codetoanalyze/java/tracing/ReportOnMainExample.java"; + + public static final String NPE = + "java.lang.NullPointerException"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = InferResults.loadTracingResults( + ReportOnMainTest.class, + SOURCE_FILE); + } + + @Test + public void whenEradicateRunsOnConstructorThenFieldNotInitializedIsFound() + throws IOException, InterruptedException, InferException { + assertThat( + "Results should contain " + NPE, + inferResults, + contains( + NPE, + SOURCE_FILE, + "main" + ) + ); + } + + +} diff --git a/infer/tests/endtoend/java/tracing/UnavoidableExceptionTest.java b/infer/tests/endtoend/java/tracing/UnavoidableExceptionTest.java new file mode 100644 index 000000000..fc909555b --- /dev/null +++ b/infer/tests/endtoend/java/tracing/UnavoidableExceptionTest.java @@ -0,0 +1,52 @@ +// Copyright (c) 2015-Present Facebook. All rights reserved. + +package endtoend.java.tracing; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsExactly.containsExactly; + +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; + +import utils.InferException; +import utils.InferResults; + +public class UnavoidableExceptionTest { + + public static final String SOURCE_FILE = + "infer/tests/codetoanalyze/java/tracing/UnavoidableExceptionExample.java"; + + public static final String NPE = + "java.lang.NullPointerException"; + + private static InferResults inferResults; + + @BeforeClass + public static void loadResults() throws InterruptedException, IOException { + inferResults = InferResults.loadTracingResults( + UnavoidableExceptionTest.class, + SOURCE_FILE); + } + + @Test + public void matchErrors() + throws IOException, InterruptedException, InferException { + String[] methods = { + "cannotAvoidNPE", + "unavoidableNPEWithParameter", + "virtualMethodWithUnavoidableNPE", + }; + assertThat( + "Results should contain " + NPE, + inferResults, + containsExactly( + NPE, + SOURCE_FILE, + methods + ) + ); + } + +} diff --git a/infer/tests/endtoend/objc/AutoreleaseTest.java b/infer/tests/endtoend/objc/AutoreleaseTest.java new file mode 100644 index 000000000..d2d2fc555 --- /dev/null +++ b/infer/tests/endtoend/objc/AutoreleaseTest.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class AutoreleaseTest { + + public static final String memory_leak_file = + "infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/AutoreleaseExample.m"; + + private static ImmutableList inferCmd; + + public static final String MEMORY_LEAK = "MEMORY_LEAK"; + + @ClassRule + public static DebuggableTemporaryFolder folder = + new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmd = InferRunner.createObjCInferCommandWithMLBuckets( + folder, + memory_leak_file, + "cf", + false); + } + + @Test + public void whenInferRunsOnAutoreleaseExampleThenMLIsNotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should not contain memory leak", + inferResults, + doesNotContain( + MEMORY_LEAK, + memory_leak_file, + "main")); + } + /* + @Test + public void whenInferRunsOnAutoreleaseExampleTest1ThenMLIstFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should contain memory leak", + inferResults, + contains( + inferError( + MEMORY_LEAK, + memory_leak_file, + "test1"))); + } */ + + @Test + public void whenInferRunsOnAutoreleaseExampleTest2ThenMLIsNotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should not contain memory leak", + inferResults, + doesNotContain( + MEMORY_LEAK, + memory_leak_file, + "test2")); + } + + @Test + public void whenInferRunsOnAutoreleaseExampleTest3ThenMLIsNotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should not contain memory leak", + inferResults, + doesNotContain( + MEMORY_LEAK, + memory_leak_file, + "test3")); + } + +} diff --git a/infer/tests/endtoend/objc/BlockTest.java b/infer/tests/endtoend/objc/BlockTest.java new file mode 100644 index 000000000..869609887 --- /dev/null +++ b/infer/tests/endtoend/objc/BlockTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class BlockTest { + + public static final String MAIN_FILE = + "infer/tests/" + + "codetoanalyze/objc/frontend/block/main.m"; + + private static ImmutableList inferCmd; + + public static final String DIVIDE_BY_ZERO = "DIVIDE_BY_ZERO"; + + @ClassRule + public static DebuggableTemporaryFolder folder = + new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmd = InferRunner.createObjCInferCommand(folder, MAIN_FILE); + } + + @Test + public void whenInferRunsOnEOCPersonThenMemoryLeakIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should contain divide by zero error. " + + "This shows that the translation is correct and res = 8.", + inferResults, + contains( + DIVIDE_BY_ZERO, + MAIN_FILE, + "main" + ) + ); + } +} diff --git a/infer/tests/endtoend/objc/CategoryProcdescTest.java b/infer/tests/endtoend/objc/CategoryProcdescTest.java new file mode 100644 index 000000000..dfbfe5896 --- /dev/null +++ b/infer/tests/endtoend/objc/CategoryProcdescTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class CategoryProcdescTest { + + public static final String MAIN_FILE = + "infer/tests/" + + "codetoanalyze/objc/errors/category_procdesc/main.c"; + + private static ImmutableList inferCmd; + + public static final String MEMORY_LEAK = "MEMORY_LEAK"; + + @ClassRule + public static DebuggableTemporaryFolder folder = + new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmd = InferRunner.createObjCInferCommand(folder, MAIN_FILE); + } + + @Test + public void whenInferRunsOnEOCPersonThenMemoryLeakIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should contain a memory leak. " + + "This shows that it doesn't stop because of procdesc not found.", + inferResults, + contains( + MEMORY_LEAK, + MAIN_FILE, + "main" + ) + ); + } +} diff --git a/infer/tests/endtoend/objc/DispatchTest.java b/infer/tests/endtoend/objc/DispatchTest.java new file mode 100644 index 000000000..db6294f0d --- /dev/null +++ b/infer/tests/endtoend/objc/DispatchTest.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; +import static utils.matchers.ResultContainsErrorInMethod.contains; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class DispatchTest { + + public static final String NPE_FILE = + "infer/tests/codetoanalyze/objc/frontend/block/dispatch_examples.m"; + + private static ImmutableList inferCmdNPD; + + public static final String NULL_DEREFERENCE = "NULL_DEREFERENCE"; + + @ClassRule + public static DebuggableTemporaryFolder folderNPD = new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmdNPD = InferRunner.createiOSInferCommandWithMLBuckets( + folderNPD, + NPE_FILE, + "cf", + true); + } + + @Test + public void whenInferRunsOnDispatch_once_exampleThenNoNPENotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmdNPD); + assertThat( + "NPE should not be found", inferResults, + doesNotContain( + NULL_DEREFERENCE, + NPE_FILE, + "dispatch_once_example")); + } + + @Test + public void whenInferRunsOnDispatch_async_exampleThenNoNPENotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmdNPD); + assertThat( + "NPE should not be found", inferResults, + doesNotContain( + NULL_DEREFERENCE, + NPE_FILE, + "dispatch_async_example")); + } + + @Test + public void whenInferRunsOnDispatch_after_exampleThenNoNPENotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmdNPD); + assertThat( + "NPE should not be found", inferResults, + doesNotContain( + NULL_DEREFERENCE, + NPE_FILE, + "dispatch_after_example")); + } + + @Test + public void whenInferRunsOnDispatch_group_exampleThenNoNPENotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmdNPD); + assertThat( + "NPE should not be found", inferResults, + doesNotContain( + NULL_DEREFERENCE, + NPE_FILE, + "dispatch_group_example")); + } + + @Test + public void whenInferRunsOnDispatch_group_notify_exampleThenNoNPENotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmdNPD); + assertThat( + "NPE should not be found", inferResults, + doesNotContain( + NULL_DEREFERENCE, + NPE_FILE, + "dispatch_group_notify_example")); + } + + @Test + public void whenInferRunsOnDispatch_barrier_exampleThenNoNPENotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmdNPD); + assertThat( + "NPE should not be found", inferResults, + doesNotContain( + NULL_DEREFERENCE, + NPE_FILE, + "dispatch_barrier_example")); + } + + + +} diff --git a/infer/tests/endtoend/objc/FieldSuperclassTest.java b/infer/tests/endtoend/objc/FieldSuperclassTest.java new file mode 100644 index 000000000..6051a98fa --- /dev/null +++ b/infer/tests/endtoend/objc/FieldSuperclassTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; + + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class FieldSuperclassTest { + + public static final String FIELD_FILE = + "infer/tests/" + + "codetoanalyze/objc/errors/field_superclass/field.c"; + + private static ImmutableList inferCmd; + + public static final String MEMORY_LEAK = "MEMORY_LEAK"; + + @ClassRule + public static DebuggableTemporaryFolder folder = + new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmd = InferRunner.createObjCInferCommand(folder, FIELD_FILE); + } + + @Test + public void whenInferRunsOnEOCPersonThenNoMemoryLeakIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should not contain a memory leak.", + inferResults, + doesNotContain( + MEMORY_LEAK, + FIELD_FILE, + "main" + ) + ); + } +} diff --git a/infer/tests/endtoend/objc/MemoryLeakBucketingArcTest.java b/infer/tests/endtoend/objc/MemoryLeakBucketingArcTest.java new file mode 100644 index 000000000..5854596f7 --- /dev/null +++ b/infer/tests/endtoend/objc/MemoryLeakBucketingArcTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class MemoryLeakBucketingArcTest { + + public static final String memory_leak_file = + "infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/RetainReleaseExampleBucketing.m"; + + private static ImmutableList inferCmd; + + public static final String MEMORY_LEAK = "MEMORY_LEAK"; + + @ClassRule + public static DebuggableTemporaryFolder folder = + new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmd = InferRunner.createObjCInferCommandWithMLBuckets( + folder, + memory_leak_file, + "narc", + true); + } + + @Test + public void whenInferRunsOnTestWithBucketingThenMLIsNotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should not contain memory leak", + inferResults, + doesNotContain( + MEMORY_LEAK, + memory_leak_file, + "test")); + } + + +} diff --git a/infer/tests/endtoend/objc/MemoryLeakBucketingTest.java b/infer/tests/endtoend/objc/MemoryLeakBucketingTest.java new file mode 100644 index 000000000..879c6c200 --- /dev/null +++ b/infer/tests/endtoend/objc/MemoryLeakBucketingTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class MemoryLeakBucketingTest { + + public static final String memory_leak_file = + "infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/RetainReleaseExampleBucketing.m"; + + private static ImmutableList inferCmd; + + public static final String MEMORY_LEAK = "MEMORY_LEAK"; + + @ClassRule + public static DebuggableTemporaryFolder folder = + new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmd = InferRunner.createObjCInferCommandWithMLBuckets( + folder, + memory_leak_file, + "cf", + false); + } + + @Test + public void whenInferRunsOnTestWithBucketingThenMLIsNotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should not contain memory leak", + inferResults, + doesNotContain( + MEMORY_LEAK, + memory_leak_file, + "test")); + } + + +} diff --git a/infer/tests/endtoend/objc/MemoryLeakTest.java b/infer/tests/endtoend/objc/MemoryLeakTest.java new file mode 100644 index 000000000..cbcb93373 --- /dev/null +++ b/infer/tests/endtoend/objc/MemoryLeakTest.java @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class MemoryLeakTest { + + public static final String memory_leak_file = + "infer/tests/codetoanalyze/objc/errors/" + + "memory_leaks_benchmark/MemoryLeakExample.m"; + + private static ImmutableList inferCmd; + + public static final String MEMORY_LEAK = "MEMORY_LEAK"; + + @ClassRule + public static DebuggableTemporaryFolder folder = + new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmd = InferRunner.createiOSInferCommandWithMLBuckets( + folder, + memory_leak_file, + "cf", + false); + } + + @Test + public void whenInferRunsOnLayoutSubviewsThenMLIsNotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should not contain memory leak", + inferResults, + doesNotContain( + MEMORY_LEAK, + memory_leak_file, + "layoutSubviews")); + } + + @Test + public void whenInferRunsOnTestThenMLIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should contain memory leak", + inferResults, + contains( + MEMORY_LEAK, + memory_leak_file, + "test" + ) + ); + } + + @Test + public void whenInferRunsOnMeasureFrameSizeForTextThenMLIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should contain memory leak", + inferResults, + contains( + MEMORY_LEAK, + memory_leak_file, + "measureFrameSizeForText" + ) + ); + } + + @Test + public void whenInferRunsOnMeasureFrameSizeForTextNoLeakThenMLIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should not contain a memory leak", + inferResults, + doesNotContain( + MEMORY_LEAK, + memory_leak_file, + "measureFrameSizeForTextNoLeak")); + } + + @Test + public void whenInferRunsOnTest1ThenMLIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should contain memory leak", + inferResults, + contains( + MEMORY_LEAK, + memory_leak_file, + "test1" + ) + ); + } + + @Test + public void whenInferRunsOnTest1NoLeakThenMLIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should not contain a memory leak", + inferResults, + doesNotContain( + MEMORY_LEAK, + memory_leak_file, + "test1NoLeak")); + } + + @Test + public void whenInferRunsOn_createCloseCrossGlyphWithRectThenMLIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should contain memory leak", + inferResults, + contains( + MEMORY_LEAK, + memory_leak_file, + "createCloseCrossGlyph:" + ) + ); + } + + @Test + public void whenInferRunsOn_createCloseCrossGlyphWithRectNoLeakThenMLIsNotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should not contain a memory leak", + inferResults, + doesNotContain( + MEMORY_LEAK, + memory_leak_file, + "createCloseCrossGlyphNoLeak:")); + } + + @Test + public void whenInferRunsOnTest2ThenMLIFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should contain memory leak", + inferResults, + contains( + MEMORY_LEAK, + memory_leak_file, + "test2" + ) + ); + } + + @Test + public void whenInferTest2NoLakNoLeakThenMLIsNotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should not contain a memory leak", + inferResults, + doesNotContain( + MEMORY_LEAK, + memory_leak_file, + "test2NoLeak")); + } + +} diff --git a/infer/tests/endtoend/objc/MemoryLeaksFromModelsTest.java b/infer/tests/endtoend/objc/MemoryLeaksFromModelsTest.java new file mode 100644 index 000000000..9838a8a4e --- /dev/null +++ b/infer/tests/endtoend/objc/MemoryLeaksFromModelsTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class MemoryLeaksFromModelsTest { + + public static final String memory_leak_file = + "infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/" + + "NSStringInitWithBytesNoCopyExample.m"; + + private static ImmutableList inferCmd; + + public static final String MEMORY_LEAK = "MEMORY_LEAK"; + + @ClassRule + public static DebuggableTemporaryFolder folder = + new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmd = InferRunner.createiOSInferCommandWithMLBuckets( + folder, + memory_leak_file, + "cf", + false); + } + + @Test + public void whenInferRunsOnFBCreateURLQueryStringBodyEscapingThenMLIsNotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should not contain memory leak", + inferResults, + doesNotContain( + MEMORY_LEAK, + memory_leak_file, + "FBCreateURLQueryStringBodyEscaping")); + } + + @Test + public void whenInferRunsOnRandomBytesThenMLIsNotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should not contain memory leak", + inferResults, + doesNotContain( + MEMORY_LEAK, + memory_leak_file, + "randomBytes:")); + } + + @Test + public void whenInferRunsOn_macForIVThenMLIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should contain memory leak", + inferResults, + contains( + MEMORY_LEAK, + memory_leak_file, + "macForIV:" + ) + ); + } + + @Test + public void whenInferRunsOnHexStringValueThenMLIsNotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should not contain memory leak", + inferResults, + doesNotContain( + MEMORY_LEAK, + memory_leak_file, + "hexStringValue")); + } + +} diff --git a/infer/tests/endtoend/objc/NPDCoreFoundationClassTest.java b/infer/tests/endtoend/objc/NPDCoreFoundationClassTest.java new file mode 100644 index 000000000..77f1f0740 --- /dev/null +++ b/infer/tests/endtoend/objc/NPDCoreFoundationClassTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsOnlyTheseErrors.containsOnly; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class NPDCoreFoundationClassTest { + + public static final String NPD_FILE = + "infer/tests/codetoanalyze/objc/errors/npe/NPD_core_foundation.m"; + + private static ImmutableList inferCmdNPD; + + public static final String NULL_DEREFERENCE = "NULL_DEREFERENCE"; + + @ClassRule + public static DebuggableTemporaryFolder folderNPD = + new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmdNPD = InferRunner.createiOSInferCommandWithMLBuckets( + folderNPD, + NPD_FILE, + "cf", + false); + + } + + + @Test + public void whenInferRunsTest2ThenOnlyTheExpectedErrorsAreFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferC(inferCmdNPD); + String[] expectedProcedures = { + "NullDeref_test2" + }; + assertThat( + "No unexpected errors should be found", inferResults, + containsOnly( + NULL_DEREFERENCE, + NPD_FILE, + expectedProcedures)); + } + +} diff --git a/infer/tests/endtoend/objc/NPEArrayLiteralTest.java b/infer/tests/endtoend/objc/NPEArrayLiteralTest.java new file mode 100644 index 000000000..b36cf2636 --- /dev/null +++ b/infer/tests/endtoend/objc/NPEArrayLiteralTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; +import static utils.matchers.ResultContainsExactly.containsExactly; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class NPEArrayLiteralTest { + + public static final String PREMATURE_NIL_FILE = + "infer/tests/codetoanalyze/objc/errors/npe/nil_in_array_literal.m"; + + private static ImmutableList inferCmd; + + public static final String NULL_DEREFERENCE = "NULL_DEREFERENCE"; + + @ClassRule + public static DebuggableTemporaryFolder folderNPD = new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmd = InferRunner.createObjCInferCommandWithMLBuckets( + folderNPD, + PREMATURE_NIL_FILE, + "cf", + true); + } + + @Test + public void whenInferRunsOnTestThenNoNPENotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + + assertThat( + "NPE should not be found", inferResults, + doesNotContain( + NULL_DEREFERENCE, + PREMATURE_NIL_FILE, + "no_problem")); + + String[] expectedNPEProcedures = {"nilInArrayLiteral"}; + assertThat( + "Only NPE should be found", inferResults, + containsExactly( + NULL_DEREFERENCE, + PREMATURE_NIL_FILE, + expectedNPEProcedures)); + } +} diff --git a/infer/tests/endtoend/objc/NPEMallocTest.java b/infer/tests/endtoend/objc/NPEMallocTest.java new file mode 100644 index 000000000..ad509dff0 --- /dev/null +++ b/infer/tests/endtoend/objc/NPEMallocTest.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; +import static utils.matchers.ResultContainsErrorInMethod.contains; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class NPEMallocTest { + + public static final String NPE_FILE = + "infer/tests/codetoanalyze/objc/errors/npe/npe_malloc.m"; + + private static ImmutableList inferCmdNPD; + + public static final String NULL_DEREFERENCE = "NULL_DEREFERENCE"; + + @ClassRule + public static DebuggableTemporaryFolder folderNPD = new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmdNPD = InferRunner.createObjCInferCommand( + folderNPD, + NPE_FILE); + } + + @Test + public void whenInferRunsOnTestThenNoNPENotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmdNPD); + assertThat( + "NPE should not be found", inferResults, + doesNotContain( + NULL_DEREFERENCE, + NPE_FILE, + "test")); + } +} diff --git a/infer/tests/endtoend/objc/NPESelfTest.java b/infer/tests/endtoend/objc/NPESelfTest.java new file mode 100644 index 000000000..79d9ca169 --- /dev/null +++ b/infer/tests/endtoend/objc/NPESelfTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; +import static utils.matchers.ResultContainsErrorInMethod.contains; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class NPESelfTest { + + public static final String NPE_FILE = + "infer/tests/codetoanalyze/objc/errors/npe/npe_self.m"; + + private static ImmutableList inferCmdNPD; + + public static final String NULL_DEREFERENCE = "NULL_DEREFERENCE"; + + @ClassRule + public static DebuggableTemporaryFolder folderNPD = new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmdNPD = InferRunner.createiOSInferCommandWithMLBuckets( + folderNPD, + NPE_FILE, + "cf", + true); + } + + @Test + public void whenInferRunsOncaptureManagerSessionDidStartThenNoNPEFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmdNPD); + assertThat( + "NPE should not be found", inferResults, + doesNotContain( + NULL_DEREFERENCE, + NPE_FILE, + "captureManagerSessionDidStart")); + } + + @Test + public void whenInferRunsOnInitThenNoNPEFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmdNPD); + assertThat( + "NPE should be found", inferResults, + contains( + NULL_DEREFERENCE, + NPE_FILE, + "init")); + } + + @Test + public void whenInferRunsOnTestThenNoNPEFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmdNPD); + assertThat( + "NPE should be found, and the field name in the error message is correct", inferResults, + contains( + NULL_DEREFERENCE, + NPE_FILE, + "test")); + } + + @Test + public void whenInferRunsOnIsEqualThenNoNPEFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmdNPD); + assertThat( + "NPE should not be found", inferResults, + doesNotContain( + NULL_DEREFERENCE, + NPE_FILE, + "isEqual:")); + } + + +} diff --git a/infer/tests/endtoend/objc/NSAssertTest.java b/infer/tests/endtoend/objc/NSAssertTest.java new file mode 100644 index 000000000..512aed744 --- /dev/null +++ b/infer/tests/endtoend/objc/NSAssertTest.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class NSAssertTest { + + public static final String ASSERT_FILE = + "infer/tests/codetoanalyze/objc/frontend/assertions/NSAssert_example.m"; + + private static ImmutableList inferCmd; + + public static final String NULL_DEREFERENCE = "NULL_DEREFERENCE"; + + + @ClassRule + public static DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmd = InferRunner.createObjCInferCommand(folder, ASSERT_FILE); + } + + @Test + public void whenInferRunsOnNSAssertAddTargetNoNPEisFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should not contain null point dereference", + inferResults, + doesNotContain( + NULL_DEREFERENCE, + ASSERT_FILE, + "addTarget:")); + } + + @Test + public void whenInferRunsOnNSAssertNoNPEIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should not contain null point dereference", + inferResults, + doesNotContain( + NULL_DEREFERENCE, + ASSERT_FILE, + "initWithRequest:")); + } + +} diff --git a/infer/tests/endtoend/objc/NilParamDerefObjCClassTest.java b/infer/tests/endtoend/objc/NilParamDerefObjCClassTest.java new file mode 100644 index 000000000..2b3f51557 --- /dev/null +++ b/infer/tests/endtoend/objc/NilParamDerefObjCClassTest.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class NilParamDerefObjCClassTest { + + public static final String NIL_PARAM_FILE = + "infer/tests/codetoanalyze/objc/errors/npe/nil_param.m"; + + private static ImmutableList inferCmdNil; + + public static final String NULL_DEREFERENCE = "NULL_DEREFERENCE"; + + + @ClassRule + public static DebuggableTemporaryFolder folderNil = + new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmdNil = InferRunner.createObjCInferCommand(folderNil, NIL_PARAM_FILE); + } + + + @Test + public void whenInferRunsOnNil_ParamNoErrorsAreFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmdNil); + assertThat( + "Results should not contain null point dereference", + inferResults, + doesNotContain( + NULL_DEREFERENCE, + NIL_PARAM_FILE, + "test1:")); + } + +} diff --git a/infer/tests/endtoend/objc/NullDerefObjCBlockTest.java b/infer/tests/endtoend/objc/NullDerefObjCBlockTest.java new file mode 100644 index 000000000..0d8efa8f4 --- /dev/null +++ b/infer/tests/endtoend/objc/NullDerefObjCBlockTest.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; +import static utils.matchers.ResultContainsOnlyTheseErrors.containsOnly; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class NullDerefObjCBlockTest { + + public static final String BLOCK_FILE = + "infer/tests/codetoanalyze/objc/errors/npe/block.m"; + + + private static ImmutableList inferCmdBlock; + + private static final String PARAMETER_NOT_NULL_CHECKED = "PARAMETER_NOT_NULL_CHECKED"; + public static final String NULL_DEREFERENCE = "NULL_DEREFERENCE"; + public static final String IVAR_NOT_NULL_CHECKED = "IVAR_NOT_NULL_CHECKED"; + + @ClassRule + public static DebuggableTemporaryFolder folderBlock = + new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmdBlock = InferRunner.createObjCInferCommandWithMLBuckets( + folderBlock, + BLOCK_FILE, + "cf", + false); + + } + + @Test + public void whenInferRunsOnAClass1ThenNpeIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmdBlock); + assertThat( + "Results should contain parameter not null checked error", + inferResults, + contains( + PARAMETER_NOT_NULL_CHECKED, + BLOCK_FILE, + "doSomethingThenCallback:" + ) + ); + } + + @Test + public void whenInferRunsOnAClass2ThenNpeIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmdBlock); + assertThat( + "Results should contain null pointer dereference error", + inferResults, + contains( + NULL_DEREFERENCE, + BLOCK_FILE, + "foo" + ) + ); + } + + @Test + public void whenInferRunsOnAClass3ThenNpeIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmdBlock); + assertThat( + "Results should contain null pointer dereference error", + inferResults, + contains( + NULL_DEREFERENCE, + BLOCK_FILE, + "foo3:" + ) + ); + } + + @Test + public void whenInferRunsOnAClass4ThenNpeIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmdBlock); + assertThat( + "Results should contain null pointer dereference error", + inferResults, + contains( + NULL_DEREFERENCE, + BLOCK_FILE, + "foo4:" + ) + ); + } + + + @Test + public void whenInferRunsOnAClass5ThenNpeIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmdBlock); + assertThat( + "Results should contain ivar not nullable error", + inferResults, + contains( + IVAR_NOT_NULL_CHECKED, + BLOCK_FILE, + "foo7" + ) + ); + } +} diff --git a/infer/tests/endtoend/objc/NullDerefObjCClassTest.java b/infer/tests/endtoend/objc/NullDerefObjCClassTest.java new file mode 100644 index 000000000..d8e6534c3 --- /dev/null +++ b/infer/tests/endtoend/objc/NullDerefObjCClassTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; +import static utils.matchers.ResultContainsOnlyTheseErrors.containsOnly; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class NullDerefObjCClassTest { + + public static final String FRACTION_FILE = + "infer/tests/codetoanalyze/objc/errors/npe/Fraction.m"; + + private static ImmutableList inferCmdFraction; + + public static final String NULL_DEREFERENCE = "NULL_DEREFERENCE"; + + @ClassRule + public static DebuggableTemporaryFolder folderFraction = + new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmdFraction = InferRunner.createObjCInferCommandWithMLBuckets( + folderFraction, + FRACTION_FILE, + "cf", + false); + + } + + @Test + public void whenInferRunsOnNull_deref_objc_classThenNpeIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferC(inferCmdFraction); + assertThat( + "Results should contain null pointer dereference error", + inferResults, + contains( + NULL_DEREFERENCE, + FRACTION_FILE, + "test_virtual_call" + ) + ); + } + + @Test + public void whenInferRunsOnNpeThenOnlyTheExpectedErrorsAreFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferC(inferCmdFraction); + String[] expectedProcedures = { + "test_virtual_call" + }; + assertThat( + "No unexpected errors should be found", inferResults, + containsOnly( + NULL_DEREFERENCE, + FRACTION_FILE, + expectedProcedures)); + } + +} diff --git a/infer/tests/endtoend/objc/NullReturnedByMethodTest.java b/infer/tests/endtoend/objc/NullReturnedByMethodTest.java new file mode 100644 index 000000000..550f1a8a8 --- /dev/null +++ b/infer/tests/endtoend/objc/NullReturnedByMethodTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class NullReturnedByMethodTest { + + public static final String FILE = + "infer/tests/codetoanalyze/objc/errors/npe/null_returned_by_method.m"; + + private static ImmutableList inferCmdFraction; + + public static final String NULL_DEREFERENCE = "NULL_DEREFERENCE"; + + @ClassRule + public static DebuggableTemporaryFolder folderFraction = + new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmdFraction = InferRunner.createObjCInferCommand( + folderFraction, + FILE); + + } + + @Test + public void whenInferRunsOnTest1ThenNpeIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmdFraction); + assertThat( + "Results should contain null pointer dereference error", + inferResults, + contains( + NULL_DEREFERENCE, + FILE, + "test1" + ) + ); + } + +} diff --git a/infer/tests/endtoend/objc/ParameterNotNullableTest.java b/infer/tests/endtoend/objc/ParameterNotNullableTest.java new file mode 100644 index 000000000..f35b5de8a --- /dev/null +++ b/infer/tests/endtoend/objc/ParameterNotNullableTest.java @@ -0,0 +1,152 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class ParameterNotNullableTest { + + public static final String FILE = + "infer/tests/codetoanalyze/objc/warnings/ParameterNotNullableExample.m"; + + private static ImmutableList inferCmdFraction; + + public static final String PARAMETER_NOT_NULL_CHECKED = "PARAMETER_NOT_NULL_CHECKED"; + + public static final String IVAR_NOT_NULL_CHECKED = "IVAR_NOT_NULL_CHECKED"; + + @ClassRule + public static DebuggableTemporaryFolder folderFraction = + new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmdFraction = InferRunner.createObjCInferCommand( + folderFraction, + FILE); + } + + @Test + public void whenInferRunsOnFBAudioInputCallbackSimpleThenPNNIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmdFraction); + assertThat( + "Results should contain a parameter not nullable warning", + inferResults, + contains( + PARAMETER_NOT_NULL_CHECKED, + FILE, + "FBAudioInputCallbackSimple:" + ) + ); + } + + @Test + public void whenInferRunsOnFBAudioInputCallbackSimpleAliasingThenPNNIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmdFraction); + assertThat( + "Results should contain a parameter not nullable warning", + inferResults, + contains( + PARAMETER_NOT_NULL_CHECKED, + FILE, + "FBAudioInputCallbackSimpleAliasing:" + ) + ); + } + + @Test + public void whenInferRunsOnFBAudioInputCallbackChainThenPNNIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmdFraction); + assertThat( + "Results should contain a parameter not nullable warning", + inferResults, + contains( + PARAMETER_NOT_NULL_CHECKED, + FILE, + "FBAudioInputCallbackChain:" + ) + ); + } + + @Test + public void whenInferRunsOnInitThenPNNIsNotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmdFraction); + assertThat( + "Results should not contain a parameter not nullable warning", + inferResults, + doesNotContain( + PARAMETER_NOT_NULL_CHECKED, + FILE, + "init" + ) + ); + } + + @Test + public void whenInferRunsOnTestThenPNNIsNotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmdFraction); + assertThat( + "Results should not contain a parameter not nullable warning", + inferResults, + doesNotContain( + PARAMETER_NOT_NULL_CHECKED, + FILE, + "test" + ) + ); + } + + @Test + public void whenInferRunsOnFBAudioInputCallbackFieldThenIVNNIsNotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmdFraction); + assertThat( + "Results should contain an ivar not nullable warning", + inferResults, + contains( + IVAR_NOT_NULL_CHECKED, + FILE, + "FBAudioInputCallbackField" + ) + ); + } + + @Test + public void whenInferRunsOnFBAudioInputCallbackChainThenIVNNIsNotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmdFraction); + assertThat( + "Results should contain an ivar not nullable warning", + inferResults, + contains( + IVAR_NOT_NULL_CHECKED, + FILE, + "FBAudioInputCallbackChain:" + ) + ); + } + +} diff --git a/infer/tests/endtoend/objc/PrematureNilTerminationTest.java b/infer/tests/endtoend/objc/PrematureNilTerminationTest.java new file mode 100644 index 000000000..74c1daabb --- /dev/null +++ b/infer/tests/endtoend/objc/PrematureNilTerminationTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; +import static utils.matchers.ResultContainsExactly.containsExactly; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class PrematureNilTerminationTest { + + public static final String PREMATURE_NIL_FILE = + "infer/tests/codetoanalyze/objc/errors/variadic_methods/premature_nil_termination.m"; + + private static ImmutableList inferCmd; + + public static final String PREMATURE_NIL_TERMINATION_ARGUMENT = + "PREMATURE_NIL_TERMINATION_ARGUMENT"; + + @ClassRule + public static DebuggableTemporaryFolder folderNPD = new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmd = InferRunner.createObjCInferCommandWithMLBuckets( + folderNPD, + PREMATURE_NIL_FILE, + "cf", + true); + } + + @Test + public void whenInferRunsOnTestThenNoNPENotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + String[] expectedPNTAProcedures = {"nilInArrayWithObjects"}; + + assertThat( + "Only PNTA should be found", inferResults, + containsExactly( + PREMATURE_NIL_TERMINATION_ARGUMENT, + PREMATURE_NIL_FILE, + expectedPNTAProcedures)); + } +} diff --git a/infer/tests/endtoend/objc/ProcdescTest.java b/infer/tests/endtoend/objc/ProcdescTest.java new file mode 100644 index 000000000..e0ac72cae --- /dev/null +++ b/infer/tests/endtoend/objc/ProcdescTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class ProcdescTest { + + public static final String MAIN_FILE = + "infer/tests/" + + "codetoanalyze/objc/errors/procdescs/main.c"; + + private static ImmutableList inferCmd; + + public static final String MEMORY_LEAK = "MEMORY_LEAK"; + + @ClassRule + public static DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmd = InferRunner.createObjCInferCommand(folder, MAIN_FILE); + } + + @Test + public void whenInferRunsOnNull_deref_objc_classThenMemoryLeakIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferC(inferCmd); + assertThat( + "Results should contain a memory leak. " + + "This shows that it doesn't stop because of procdesc not found.", + inferResults, + contains( + MEMORY_LEAK, + MAIN_FILE, + "main" + ) + ); + } + + @Test + public void whenInferRunsOnCall_nslogThenMemoryLeakIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferC(inferCmd); + assertThat( + "Results should contain a memory leak. " + + "This shows that it doesn't stop because of procdesc not found.", + inferResults, + contains( + MEMORY_LEAK, + MAIN_FILE, + "call_nslog" + ) + ); + } + + +} diff --git a/infer/tests/endtoend/objc/PropertyMemoryLeakTest.java b/infer/tests/endtoend/objc/PropertyMemoryLeakTest.java new file mode 100644 index 000000000..7529261b1 --- /dev/null +++ b/infer/tests/endtoend/objc/PropertyMemoryLeakTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class PropertyMemoryLeakTest { + + public static final String memory_leak_file = + "infer/tests/codetoanalyze/objc/frontend/property/PropertyAttributes.m"; + + private static ImmutableList inferCmd; + + public static final String MEMORY_LEAK = "MEMORY_LEAK"; + + @ClassRule + public static DebuggableTemporaryFolder folder = + new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmd = InferRunner.createObjCInferCommandWithMLBuckets( + folder, + memory_leak_file, + "cf", + false); + } + + @Test + public void whenInferRunsOnLayoutPropertyAttributesThenMLIsNotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should not contain memory leak", + inferResults, + doesNotContain( + MEMORY_LEAK, + memory_leak_file, + "test")); + } + + +} diff --git a/infer/tests/endtoend/objc/PropertyTest.java b/infer/tests/endtoend/objc/PropertyTest.java new file mode 100644 index 000000000..e30203379 --- /dev/null +++ b/infer/tests/endtoend/objc/PropertyTest.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class PropertyTest { + + public static final String MAIN_FILE = + "infer/tests/" + + "codetoanalyze/objc/errors/property/main.c"; + + private static ImmutableList inferCmd; + + public static final String MEMORY_LEAK = "MEMORY_LEAK"; + + @ClassRule + public static DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmd = InferRunner.createObjCInferCommand(folder, MAIN_FILE); + } + + @Test + public void whenInferRunsOnPropertyDeclarationThenMemoryLeakIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferC(inferCmd); + assertThat( + "Results should contain a memory leak.", + inferResults, + contains( + MEMORY_LEAK, + MAIN_FILE, + "main" + ) + ); + } +} diff --git a/infer/tests/endtoend/objc/ProtocolProcdescTest.java b/infer/tests/endtoend/objc/ProtocolProcdescTest.java new file mode 100644 index 000000000..499841f8f --- /dev/null +++ b/infer/tests/endtoend/objc/ProtocolProcdescTest.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class ProtocolProcdescTest { + + public static final String MAIN_FILE = + "infer/tests/" + + "codetoanalyze/objc/errors/procdescs/main.c"; + + private static ImmutableList inferCmd; + + public static final String MEMORY_LEAK = "MEMORY_LEAK"; + + @ClassRule + public static DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmd = InferRunner.createObjCInferCommand(folder, MAIN_FILE); + } + + @Test + public void whenInferRunsOnBicycleThenMemoryLeakIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should contain a memory leak. " + + "This shows that it doesn't stop because of procdesc not found.", + inferResults, + contains( + MEMORY_LEAK, + MAIN_FILE, + "main" + ) + ); + } +} diff --git a/infer/tests/endtoend/objc/ResourceLeakTest.java b/infer/tests/endtoend/objc/ResourceLeakTest.java new file mode 100644 index 000000000..502b59d97 --- /dev/null +++ b/infer/tests/endtoend/objc/ResourceLeakTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class ResourceLeakTest { + + public static final String memory_leak_file = + "infer/tests/codetoanalyze/objc/errors/resource_leaks/ResourceLeakExample.m"; + + private static ImmutableList inferCmd; + + public static final String RESOURCE_LEAK = "RESOURCE_LEAK"; + + @ClassRule + public static DebuggableTemporaryFolder folder = + new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmd = InferRunner.createiOSInferCommandWithMLBuckets( + folder, + memory_leak_file, + "cf", + false); + } + + @Test + public void whenInferRunsOnFileHandleForLoggingAtPathThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should not contain resource leak", + inferResults, + doesNotContain( + RESOURCE_LEAK, + memory_leak_file, + "fileHandleForLoggingAtPath")); + } + + @Test + public void whenInferRunsOnNewOutputThenRLIsNotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should not contain resource leak", + inferResults, + doesNotContain( + RESOURCE_LEAK, + memory_leak_file, + "newOutput")); + } + +} diff --git a/infer/tests/endtoend/objc/RetainCycleTest.java b/infer/tests/endtoend/objc/RetainCycleTest.java new file mode 100644 index 000000000..b9ac62914 --- /dev/null +++ b/infer/tests/endtoend/objc/RetainCycleTest.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class RetainCycleTest { + + public static final String retain_cycle_file = + "infer/tests/codetoanalyze/objc/errors/" + + "memory_leaks_benchmark/retain_cycle.m"; + + private static ImmutableList inferCmd; + + public static final String RETAIN_CYCLE = "RETAIN_CYCLE"; + + @ClassRule + public static DebuggableTemporaryFolder folder = + new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmd = InferRunner.createiOSInferCommandWithMLBuckets( + folder, + retain_cycle_file, + "cf", + true); + } + + + + @Test + public void whenInferRunsOnStrongCycleThenRCIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should contain retain cycle", + inferResults, + contains( + RETAIN_CYCLE, + retain_cycle_file, + "strongcycle")); + } + + @Test + public void whenInferRunsOnUnsafeUnretainedCycleThenIsNotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should not contain memory leak", + inferResults, + doesNotContain( + RETAIN_CYCLE, + retain_cycle_file, + "unsafeunretainedcycle")); + } + + @Test + public void whenInferRunsOnWeakCycleThenIsNotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should not contain memory leak", + inferResults, + doesNotContain( + RETAIN_CYCLE, + retain_cycle_file, + "weakcycle")); + } + + + @Test + public void whenInferRunsOnAssignCycleThenIsNotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should not contain memory leak", + inferResults, + doesNotContain( + RETAIN_CYCLE, + retain_cycle_file, + "assigncycle")); + } + + +} diff --git a/infer/tests/endtoend/objc/RetainreleaseTest.java b/infer/tests/endtoend/objc/RetainreleaseTest.java new file mode 100644 index 000000000..4df0fe95e --- /dev/null +++ b/infer/tests/endtoend/objc/RetainreleaseTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class RetainreleaseTest { + + public static final String retain_release_file = + "infer/tests/" + + "codetoanalyze/objc/errors/memory_leaks_benchmark/RetainReleaseExample2.m"; + + private static ImmutableList inferCmd; + + public static final String MEMORY_LEAK = "MEMORY_LEAK"; + + @ClassRule + public static DebuggableTemporaryFolder folder = + new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmd = InferRunner.createObjCInferCommand( + folder, + retain_release_file); + } + + + @Test + public void whenInferRunsOnSimpleTest3ThenMLIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should contain memory leak error", + inferResults, + contains( + MEMORY_LEAK, + retain_release_file, + "test3" + ) + ); + } + + @Test + public void whenInferRunsOnSimpleTest6ThenMLIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should contain memory leak error", + inferResults, + contains( + MEMORY_LEAK, + retain_release_file, + "test6" + ) + ); + } + +} diff --git a/infer/tests/endtoend/objc/ReturnTest.java b/infer/tests/endtoend/objc/ReturnTest.java new file mode 100644 index 000000000..940e0e421 --- /dev/null +++ b/infer/tests/endtoend/objc/ReturnTest.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class ReturnTest { + + public static final String SRC_FILE = + "infer/tests/" + + "codetoanalyze/objc/errors/returnstmt/return_npe_test.m"; + + private static ImmutableList inferCmd; + + public static final String NULL_DEREFERENCE = "NULL_DEREFERENCE"; + + @ClassRule + public static DebuggableTemporaryFolder folder = + new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmd = InferRunner.createObjCInferCommand(folder, SRC_FILE); + } + + @Test + public void whenInferRunsOnMyClassThenNoNPEIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should not contain null point dereference", + inferResults, + doesNotContain( + NULL_DEREFERENCE, + SRC_FILE, + "aMethod:" + ) + ); + } +} diff --git a/infer/tests/endtoend/objc/SuperTest.java b/infer/tests/endtoend/objc/SuperTest.java new file mode 100644 index 000000000..9d227fd2b --- /dev/null +++ b/infer/tests/endtoend/objc/SuperTest.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsErrorInMethod.contains; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class SuperTest { + + public static final String MAIN_FILE = "infer/tests/codetoanalyze/objc/" + + "errors/field_superclass/SuperExample.m"; + + private static ImmutableList inferCmd; + + public static final String NULL_DEREFERENCE = "NULL_DEREFERENCE"; + + @ClassRule + public static DebuggableTemporaryFolder folder = + new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmd = InferRunner.createObjCInferCommand(folder, MAIN_FILE); + } + + @Test + public void whenInferRunsOnEOCPersonThenMemoryLeakIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should contain null pointer dereference error.", + inferResults, + contains( + NULL_DEREFERENCE, + MAIN_FILE, + "init" + ) + ); + } +} diff --git a/infer/tests/endtoend/objc/TollBridgeTest.java b/infer/tests/endtoend/objc/TollBridgeTest.java new file mode 100644 index 000000000..5df152d07 --- /dev/null +++ b/infer/tests/endtoend/objc/TollBridgeTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.InferError.inferError; +import static utils.matchers.ResultContainsErrorInMethod.contains; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class TollBridgeTest { + + public static final String memory_leak_file = + "infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/TollBridgeExample.m"; + + private static ImmutableList inferCmd; + + public static final String MEMORY_LEAK = "MEMORY_LEAK"; + + @ClassRule + public static DebuggableTemporaryFolder folder = + new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmd = InferRunner.createiOSInferCommandWithMLBuckets( + folder, + memory_leak_file, + "cf", + true); + } + + @Test + public void whenInferRunsOnTollBridgeExampleThenMLIsNotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should not contain memory leak", + inferResults, + doesNotContain( + MEMORY_LEAK, + memory_leak_file, + "bridgeTransfer")); + } + + @Test + public void whenInferRunsOnTollBridgeExampleTest1ThenMLIsNotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should not contain memory leak", + inferResults, + doesNotContain( + MEMORY_LEAK, + memory_leak_file, + "bridge")); + } + + @Test + public void whenInferRunsOnTollBridgeExampleTest2ThenMLIsFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should contain memory leak", + inferResults, + contains( + MEMORY_LEAK, + memory_leak_file, + "brideRetained" + ) + ); + } + + @Test + public void whenInferRunsOnTollBridgeExampleTest3ThenMLIsNotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should not contain memory leak", + inferResults, + doesNotContain( + MEMORY_LEAK, + memory_leak_file, + "_readHTTPHeader" + ) + ); + } + + @Test + public void whenInferRunsOnCfautorelease_testThenMLIsNotFound() + throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferObjC(inferCmd); + assertThat( + "Results should not contain memory leak", + inferResults, + doesNotContain( + MEMORY_LEAK, + memory_leak_file, + "cfautorelease_test" + ) + ); + } + +} diff --git a/infer/tests/endtoend/objc/UpdateDictNPETest.java b/infer/tests/endtoend/objc/UpdateDictNPETest.java new file mode 100644 index 000000000..15c829a9d --- /dev/null +++ b/infer/tests/endtoend/objc/UpdateDictNPETest.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package endtoend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.ResultContainsExactly.containsExactly; +import static utils.matchers.ResultContainsLineNumbers.containsLines; +import static utils.matchers.ResultContainsNoErrorInMethod.doesNotContain; +import static utils.matchers.ResultContainsErrorInMethod.contains; + +import com.google.common.collect.ImmutableList; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferResults; +import utils.InferRunner; + +public class UpdateDictNPETest { + + public static final String NPE_FILE = "infer/tests/codetoanalyze/objc/errors/npe/UpdateDict.m"; + + private static ImmutableList inferCmdNPD; + + public static final String NULL_DEREFERENCE = "NULL_DEREFERENCE"; + + @ClassRule + public static DebuggableTemporaryFolder folderNPD = new DebuggableTemporaryFolder(); + + @BeforeClass + public static void runInfer() throws InterruptedException, IOException { + inferCmdNPD = InferRunner.createiOSInferCommandWithMLBuckets( + folderNPD, + NPE_FILE, + "cf", + true); + } + + @Test + public void nullDereferenceTest() throws InterruptedException, IOException, InferException { + InferResults inferResults = InferRunner.runInferC(inferCmdNPD); + String[] procedures = { + "update_dict_with_null", + "update_dict_with_key_null", + "update_array_with_null", + "add_nil_to_array", + "insert_nil_in_array", + "add_nil_in_dict" + }; + assertThat( + "Results should contain null pointer dereference error", + inferResults, + containsExactly( + NULL_DEREFERENCE, + NPE_FILE, + procedures + ) + ); + } + + +} diff --git a/infer/tests/frontend/BUCK b/infer/tests/frontend/BUCK new file mode 100644 index 000000000..e5345359d --- /dev/null +++ b/infer/tests/frontend/BUCK @@ -0,0 +1,110 @@ +tests_dependencies = [ + '//infer/lib/java/android:android', + '//dependencies/java/guava:guava', + '//dependencies/java/junit:hamcrest', + '//dependencies/java/jackson:jackson', + '//dependencies/java/jsr-305:jsr-305', + '//dependencies/java/junit:junit', + '//dependencies/java/opencsv:opencsv', + '//infer/tests/utils:utils' +] + +integration_tests = [ + '//infer/tests:integration_tests', + '//infer/tests:objc_tests', + '//infer/tests:c_tests', + '//infer/tests:cpp_tests', + '//infer/tests:objcpp_tests', +] + +# ############### objc frontend tests ######################## + +objc_test_sources = glob(['objc/**/*.java']) +objc_frontend_test_deps = [] +for test_source in objc_test_sources: + target_name = test_source.replace("/", "_")[:-len(".java")] + objc_frontend_test_deps.append(target_name) + + java_test( + name=target_name, + srcs=[test_source], + deps=tests_dependencies, + visibility=integration_tests, + source='7', + target='7', + ) + +java_test( + name='objc_frontend_tests', + deps=[':' + x for x in objc_frontend_test_deps], + visibility=integration_tests, +) + +# ############### c frontend tests ######################## + +c_test_sources = glob(['c/**/*.java']) +c_frontend_test_deps = [] +for test_source in c_test_sources: + target_name = test_source.replace("/", "_")[:-len(".java")] + c_frontend_test_deps.append(target_name) + + java_test( + name=target_name, + srcs=[test_source], + deps=tests_dependencies, + visibility=integration_tests, + source='7', + target='7', + ) + +java_test( + name='c_frontend_tests', + deps=[':' + x for x in c_frontend_test_deps], + visibility=integration_tests, +) + +# ############### cpp frontend tests ######################## + +cpp_test_sources = glob(['cpp/**/*.java']) +cpp_frontend_test_deps = [] +for test_source in cpp_test_sources: + target_name = test_source.replace("/", "_")[:-len(".java")] + cpp_frontend_test_deps.append(target_name) + + java_test( + name=target_name, + srcs=[test_source], + deps=tests_dependencies, + visibility=integration_tests, + source='7', + target='7', + ) + +java_test( + name='cpp_frontend_tests', + deps=[':' + x for x in cpp_frontend_test_deps], + visibility=integration_tests, +) + +# ############### objcpp frontend tests ######################## + +objcpp_test_sources = glob(['objcpp/**/*.java']) +objcpp_frontend_test_deps = [] +for test_source in objcpp_test_sources: + target_name = test_source.replace("/", "_")[:-len(".java")] + objcpp_frontend_test_deps.append(target_name) + + java_test( + name=target_name, + srcs=[test_source], + deps=tests_dependencies, + visibility=integration_tests, + source='7', + target='7', + ) + +java_test( + name='objcpp_frontend_tests', + deps=[':' + x for x in objcpp_frontend_test_deps], + visibility=integration_tests, +) diff --git a/infer/tests/frontend/c/ArithmeticExpTest.java b/infer/tests/frontend/c/ArithmeticExpTest.java new file mode 100644 index 000000000..7338a81b5 --- /dev/null +++ b/infer/tests/frontend/c/ArithmeticExpTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package frontend.c; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + +public class ArithmeticExpTest { + + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + + @Test + public void whenCaptureRunOnPlus_exprThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String plus_expr = + "infer/tests/" + + "codetoanalyze/c/frontend/arithmetic/plus_expr.c"; + + String plus_expr_dotty = + "infer/tests/" + + "codetoanalyze/c/frontend/arithmetic/plus_expr.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend(folder, plus_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + plus_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(plus_expr_dotty)); + } + + @Test + public void whenCaptureRunOnCompound_assignmentThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String compound_assignment_expr = + "infer/tests/codetoanalyze/c/frontend/" + + "arithmetic/compound_assignment.c"; + + String compound_assignment_dotty = + "infer/tests/codetoanalyze/c/frontend/" + + "arithmetic/compound_assignment.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend( + folder, + compound_assignment_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + compound_assignment_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(compound_assignment_dotty)); + } + + @Test + public void whenCaptureRunOnUnaryThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String unary_expr = + "infer/tests/" + + "codetoanalyze/c/frontend/arithmetic/unary.c"; + + String unary_dotty = + "infer/tests/" + + "codetoanalyze/c/frontend/arithmetic/unary.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend(folder, unary_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + unary_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(unary_dotty)); + } + + @Test + public void whenCaptureRunOnIntConstThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String int_const_expr = + "infer/tests/" + + "codetoanalyze/c/frontend/arithmetic/int_const.c"; + + String int_const_dotty = + "infer/tests/" + + "codetoanalyze/c/frontend/arithmetic/int_const.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend(folder, int_const_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + int_const_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(int_const_dotty)); + } + +} diff --git a/infer/tests/frontend/c/BoolTest.java b/infer/tests/frontend/c/BoolTest.java new file mode 100644 index 000000000..74c2d2c54 --- /dev/null +++ b/infer/tests/frontend/c/BoolTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package frontend.c; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + +public class BoolTest { + + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + + @Test + public void whenCaptureRunCommaThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String bool_src = "infer/tests/codetoanalyze/c/frontend/booleans/bool_example.c"; + + String bool_dotty = "infer/tests/codetoanalyze/c/frontend/booleans/bool_example.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend( + folder, + bool_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + bool_src + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(bool_dotty)); + } + + @Test + public void whenCaptureBooleanConditionAsParamThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String bool_src = "infer/tests/codetoanalyze/c/frontend/booleans/condition_as_param.c"; + + String bool_dotty = "infer/tests/codetoanalyze/c/frontend/booleans/condition_as_param.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend( + folder, + bool_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + bool_src + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(bool_dotty)); + } +} diff --git a/infer/tests/frontend/c/CommaOperatorTest.java b/infer/tests/frontend/c/CommaOperatorTest.java new file mode 100644 index 000000000..9ef3bcd86 --- /dev/null +++ b/infer/tests/frontend/c/CommaOperatorTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package frontend.c; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + +public class CommaOperatorTest { + + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + + @Test + public void whenCaptureRunCommaThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String switch_src = + "infer/tests/codetoanalyze/c/frontend/comma/comma.c"; + + String switch_dotty = + "infer/tests/codetoanalyze/c/frontend/comma/comma.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend( + folder, + switch_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + switch_src + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(switch_dotty)); + } +} diff --git a/infer/tests/frontend/c/ConditionalOperatorTest.java b/infer/tests/frontend/c/ConditionalOperatorTest.java new file mode 100644 index 000000000..21954fa13 --- /dev/null +++ b/infer/tests/frontend/c/ConditionalOperatorTest.java @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package frontend.c; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + +public class ConditionalOperatorTest { + + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + + @Test + public void whenCaptureRunCommaThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String cond_src = + "infer/tests/codetoanalyze/c/frontend/conditional_operator/conditional_operator.c"; + + String cond_dotty = + "infer/tests/codetoanalyze/c/frontend/conditional_operator/conditional_operator.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend( + folder, + cond_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + cond_src + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(cond_dotty)); + } + + @Test + public void whenCaptureRunShortCThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String cond_src = + "infer/tests/codetoanalyze/c/frontend/conditional_operator/if_short_circuit.c"; + + String cond_dotty = + "infer/tests/codetoanalyze/c/frontend/conditional_operator/if_short_circuit.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend( + folder, + cond_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + cond_src + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(cond_dotty)); + } + + @Test + public void whenCaptureRunCond2ThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String cond_src = + "infer/tests/codetoanalyze/c/frontend/conditional_operator/cond2.c"; + + String cond_dotty = + "infer/tests/codetoanalyze/c/frontend/conditional_operator/cond2.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend( + folder, + cond_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + cond_src + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(cond_dotty)); + } + + @Ignore @Test + public void whenCaptureRunOnAssertExampleThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String cond_src = + "infer/tests/codetoanalyze/c/frontend/conditional_operator/assert_example.c"; + + String cond_dotty = + "infer/tests/codetoanalyze/c/frontend/conditional_operator/assert_example.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend( + folder, + cond_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + cond_src + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(cond_dotty)); + } + + @Test + public void whenCaptureRunOnIntNegationThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String cond_src = + "infer/tests/codetoanalyze/c/frontend/conditional_operator/int_negation.c"; + + String cond_dotty = + "infer/tests/codetoanalyze/c/frontend/conditional_operator/int_negation.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend( + folder, + cond_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + cond_src + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(cond_dotty)); + } + + @Test + public void whenCaptureRunBinaryOperatorThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String cond_src = + "infer/tests/codetoanalyze/c/frontend/conditional_operator/binary_operator.c"; + + String cond_dotty = + "infer/tests/codetoanalyze/c/frontend/conditional_operator/binary_operator.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend( + folder, + cond_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + cond_src + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(cond_dotty)); + } + + @Test + public void whenCaptureRunUnaryOperatorThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String cond_src = + "infer/tests/codetoanalyze/c/frontend/conditional_operator/unary_operator.c"; + + String cond_dotty = + "infer/tests/codetoanalyze/c/frontend/conditional_operator/unary_operator.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend( + folder, + cond_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + cond_src + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(cond_dotty)); + } + + @Test + public void whenCaptureRunArrayAccessThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String cond_src = + "infer/tests/codetoanalyze/c/frontend/conditional_operator/array_access.c"; + + String cond_dotty = + "infer/tests/codetoanalyze/c/frontend/conditional_operator/array_access.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend( + folder, + cond_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + cond_src + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(cond_dotty)); + } + + @Test + public void whenCaptureRunMemberAccessThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String cond_src = + "infer/tests/codetoanalyze/c/frontend/conditional_operator/member_access.c"; + + String cond_dotty = + "infer/tests/codetoanalyze/c/frontend/conditional_operator/member_access.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend( + folder, + cond_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + cond_src + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(cond_dotty)); + } + + @Test + public void whenCaptureRunPreincrementThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String cond_src = + "infer/tests/codetoanalyze/c/frontend/conditional_operator/preincrement.c"; + + String cond_dotty = + "infer/tests/codetoanalyze/c/frontend/conditional_operator/preincrement.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend( + folder, + cond_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + cond_src + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(cond_dotty)); + } + + @Test + public void whenCaptureRunFunctionCallThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String cond_src = + "infer/tests/codetoanalyze/c/frontend/conditional_operator/function_call.c"; + + String cond_dotty = + "infer/tests/codetoanalyze/c/frontend/conditional_operator/function_call.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend( + folder, + cond_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + cond_src + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(cond_dotty)); + } + +} diff --git a/infer/tests/frontend/c/EnumerationTest.java b/infer/tests/frontend/c/EnumerationTest.java new file mode 100644 index 000000000..054c3c971 --- /dev/null +++ b/infer/tests/frontend/c/EnumerationTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package frontend.c; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + +public class EnumerationTest { + + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + + @Test + public void whenCaptureRunEnumThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String enum_expr = + "infer/tests/codetoanalyze/c/frontend/enumeration/enum.c"; + + String enum_dotty = + "infer/tests/codetoanalyze/c/frontend/enumeration/enum.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend(folder, enum_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + enum_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(enum_dotty)); + } + + +} diff --git a/infer/tests/frontend/c/GnuexprTest.java b/infer/tests/frontend/c/GnuexprTest.java new file mode 100644 index 000000000..cc32deefb --- /dev/null +++ b/infer/tests/frontend/c/GnuexprTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package frontend.c; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + +public class GnuexprTest { + + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + + @Test + public void whenCaptureRunOnWhileThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String gnu_expr = + "infer/tests/codetoanalyze/" + + "c/frontend/nestedoperators/gnuexpr.c"; + + String gnu_dotty = + "infer/tests/codetoanalyze/" + + "c/frontend/nestedoperators/gnuexpr.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend(folder, gnu_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + gnu_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(gnu_dotty)); + } + +} diff --git a/infer/tests/frontend/c/GotoStmtLabelStmtTest.java b/infer/tests/frontend/c/GotoStmtLabelStmtTest.java new file mode 100644 index 000000000..dadb94add --- /dev/null +++ b/infer/tests/frontend/c/GotoStmtLabelStmtTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package frontend.c; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + +public class GotoStmtLabelStmtTest { + + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + + @Test + public void whenCaptureRunGotoStmtThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String src_file = + "infer/tests/codetoanalyze/c/frontend/gotostmt/goto_ex.c"; + + String dot_file = + "infer/tests/codetoanalyze/c/frontend/gotostmt/goto_ex.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend(folder, src_file); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + src_file + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(dot_file)); + } +} diff --git a/infer/tests/frontend/c/InitListExprTest.java b/infer/tests/frontend/c/InitListExprTest.java new file mode 100644 index 000000000..aa40fa39f --- /dev/null +++ b/infer/tests/frontend/c/InitListExprTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package frontend.c; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + +public class InitListExprTest { + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + @Test + public void whenCaptureRunOnArrayInitListExprThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String plus_expr = + "infer/tests/codetoanalyze/" + + "c/frontend/initialization/array_initlistexpr.c"; + + String plus_expr_dotty = + "infer/tests/codetoanalyze/" + + "c/frontend/initialization/array_initlistexpr.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend(folder, plus_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + plus_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(plus_expr_dotty)); + } + + @Test + public void whenCaptureRunOnStructInitListExprThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String plus_expr = + "infer/tests/codetoanalyze/" + + "c/frontend/initialization/struct_initlistexpr.c"; + + String plus_expr_dotty = + "infer/tests/codetoanalyze/" + + "c/frontend/initialization/struct_initlistexpr.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend(folder, plus_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + plus_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(plus_expr_dotty)); + } +} diff --git a/infer/tests/frontend/c/LoopsTest.java b/infer/tests/frontend/c/LoopsTest.java new file mode 100644 index 000000000..595409e5d --- /dev/null +++ b/infer/tests/frontend/c/LoopsTest.java @@ -0,0 +1,333 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package frontend.c; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + +public class LoopsTest { + + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + + @Test + public void whenCaptureRunOnWhileThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String while_expr = + "infer/tests/codetoanalyze/c/frontend/loops/while.c"; + + String while_dotty = + "infer/tests/codetoanalyze/c/frontend/loops/while.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend(folder, while_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + while_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(while_dotty)); + } + + @Test + public void whenCaptureRunOnWhile_nestedThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String while_expr = + "infer/tests/codetoanalyze/c/frontend/loops/while_nested.c"; + + String while_dotty = + "infer/tests/codetoanalyze/c/frontend/loops/while_nested.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend(folder, while_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + while_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(while_dotty)); + } + + @Test + public void whenCaptureRunOnWhile_side_effectsThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String while_expr = + "infer/tests/codetoanalyze/" + + "c/frontend/loops/while_condition_side_effects.c"; + + String while_dotty = + "infer/tests/codetoanalyze/" + + "c/frontend/loops/while_condition_side_effects.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend(folder, while_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + while_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(while_dotty)); + } + + @Test + public void whenCaptureRunOnWhile_no_bodyThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String while_expr = + "infer/tests/codetoanalyze/c/frontend/loops/while_no_body.c"; + + String while_dotty = + "infer/tests/codetoanalyze/c/frontend/loops/while_no_body.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend(folder, while_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + while_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(while_dotty)); + } + + @Test + public void whenCaptureRunOnForside_effectsThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String for_expr = + "infer/tests/codetoanalyze/" + + "c/frontend/loops/for_condition_side_effects.c"; + + String for_dotty = + "infer/tests/codetoanalyze/" + + "c/frontend/loops/for_condition_side_effects.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend(folder, for_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + for_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(for_dotty)); + } + + @Test + public void whenCaptureRunOnFor_nestedThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String for_expr = + "infer/tests/codetoanalyze/c/frontend/loops/for_nested.c"; + + String for_dotty = + "infer/tests/codetoanalyze/c/frontend/loops/for_nested.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend(folder, for_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + for_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(for_dotty)); + } + + @Test + public void whenCaptureRunOnFor_no_conditionThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String for_expr = + "infer/tests/codetoanalyze/" + + "c/frontend/loops/for_no_condition.c"; + + String for_dotty = + "infer/tests/codetoanalyze/" + + "c/frontend/loops/for_no_condition.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend(folder, for_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + for_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(for_dotty)); + } + + @Test + public void whenCaptureRunOnFor_no_conditionincrThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String for_expr = + "infer/tests/codetoanalyze/" + + "c/frontend/loops/for_no_condition_incr.c"; + + String for_dotty = + "infer/tests/codetoanalyze/" + + "c/frontend/loops/for_no_condition_incr.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend(folder, for_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + for_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(for_dotty)); + } + + @Test + public void whenCaptureRunOnFor_emptyThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String for_expr = + "infer/tests/codetoanalyze/" + + "c/frontend/loops/for_no_condition_incr_body.c"; + + String for_dotty = + "infer/tests/codetoanalyze/" + + "c/frontend/loops/for_no_condition_incr_body.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend(folder, for_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + for_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(for_dotty)); + } + + @Test + public void whenCaptureRunOnFor_only_bodyThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String for_expr = + "infer/tests/codetoanalyze/c/frontend/loops/for_only_body.c"; + + String for_dotty = + "infer/tests/codetoanalyze/c/frontend/loops/for_only_body.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend(folder, for_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + for_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(for_dotty)); + } + + @Test + public void whenCaptureRunOnFor_simpleThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String for_expr = + "infer/tests/codetoanalyze/c/frontend/loops/for_simple.c"; + + String for_dotty = + "infer/tests/codetoanalyze/c/frontend/loops/for_simple.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend(folder, for_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + for_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(for_dotty)); + } + + @Test + public void whenCaptureRunOnFor_while_nestedThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String for_expr = + "infer/tests/codetoanalyze/" + + "c/frontend/loops/for_while_nested.c"; + + String for_dotty = + "infer/tests/codetoanalyze/" + + "c/frontend/loops/for_while_nested.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend(folder, for_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + for_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(for_dotty)); + } + + @Test + public void whenCaptureRunOnDo_whileThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String do_while_expr = + "infer/tests/codetoanalyze/c/frontend/loops/do_while.c"; + + String do_while_dotty = + "infer/tests/codetoanalyze/c/frontend/loops/do_while.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend(folder, do_while_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + do_while_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(do_while_dotty)); + } + + @Test + public void whenCaptureRunOnDo_while_side_effectsThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String do_while_expr = + "infer/tests/codetoanalyze/" + + "c/frontend/loops/do_while_condition_side_effects.c"; + + String do_while_dotty = + "infer/tests/codetoanalyze/" + + "c/frontend/loops/do_while_condition_side_effects.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend(folder, do_while_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + do_while_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(do_while_dotty)); + } + + @Test + public void whenCaptureRunOnDo_while_nestedThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String do_while_expr = + "infer/tests/codetoanalyze/c/frontend/loops/do_while_nested.c"; + + String do_while_dotty = + "infer/tests/codetoanalyze/" + + "c/frontend/loops/do_while_nested.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend(folder, do_while_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + do_while_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(do_while_dotty)); + } + + @Test + public void whenCaptureWhile_with_continue_and_breakThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String while_with_continue_and_break_expr = + "infer/tests/codetoanalyze/" + + "c/frontend/loops/while_with_continue_and_break.c"; + + String while_with_continue_and_break_dotty = + "infer/tests/codetoanalyze/" + + "c/frontend/loops/while_with_continue_and_break.dot"; + + ImmutableList inferCmd = InferRunner.createCInferCommandFrontend( + folder, while_with_continue_and_break_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + while_with_continue_and_break_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(while_with_continue_and_break_dotty)); + } +} diff --git a/infer/tests/frontend/c/NestedOperatorsTest.java b/infer/tests/frontend/c/NestedOperatorsTest.java new file mode 100644 index 000000000..9af93749c --- /dev/null +++ b/infer/tests/frontend/c/NestedOperatorsTest.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package frontend.c; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + + +public class NestedOperatorsTest { + + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + + @Test + public void whenCaptureRunEnumThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String nestedassignment_expr = + "infer/tests/codetoanalyze/c/frontend/" + + "nestedoperators/nestedassignment.c"; + + String nestedassignment_dotty = + "infer/tests/codetoanalyze/c/frontend/" + + "nestedoperators/nestedassignment.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend( + folder, + nestedassignment_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + nestedassignment_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(nestedassignment_dotty)); + } + + @Test + public void whenCaptureRunUnionThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String nestedunion_expr = + "infer/tests/codetoanalyze/c/frontend/nestedoperators/union.c"; + + String nestedunion_dotty = + "infer/tests/" + + "codetoanalyze/c/frontend/nestedoperators/union.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend( + folder, + nestedunion_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + nestedunion_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(nestedunion_dotty)); + } + + @Test + public void whenCaptureRunAssignInConditionThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String condition_assign_expr = + "infer/tests/codetoanalyze/c/frontend/nestedoperators/assign_in_condition.c"; + + String condition_assign_dotty = + "infer/tests/" + + "codetoanalyze/c/frontend/nestedoperators/assign_in_condition.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend( + folder, + condition_assign_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + condition_assign_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(condition_assign_dotty)); + } + +} diff --git a/infer/tests/frontend/c/PrototypeTest.java b/infer/tests/frontend/c/PrototypeTest.java new file mode 100644 index 000000000..3e56be94e --- /dev/null +++ b/infer/tests/frontend/c/PrototypeTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package frontend.c; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + +public class PrototypeTest { + + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + + @Test + public void whenCaptureRunOnC_prototypeThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String prototype_expr = + "infer/tests/codetoanalyze/" + + "c/frontend/c_prototype/prototype.c"; + + String prototype_dotty = + "infer/tests/codetoanalyze/" + + "c/frontend/c_prototype/prototype.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend(folder, prototype_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + prototype_expr + + " the dotty files should be the same. ", + newDotFile, dotFileEqualTo(prototype_dotty)); + } + +} diff --git a/infer/tests/frontend/c/SwitchStmtTest.java b/infer/tests/frontend/c/SwitchStmtTest.java new file mode 100644 index 000000000..d6d4eedb9 --- /dev/null +++ b/infer/tests/frontend/c/SwitchStmtTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package frontend.c; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + +public class SwitchStmtTest { + + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + @Test + public void whenCaptureRunSwitchStmtThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String switch_src = + "infer/tests/codetoanalyze/c/frontend/switchstmt/switch.c"; + + String switch_dotty = + "infer/tests/codetoanalyze/c/frontend/switchstmt/switch.dot"; + + ImmutableList inferCmd = + InferRunner.createCInferCommandFrontend(folder, switch_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + switch_src + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(switch_dotty)); + } +} diff --git a/infer/tests/frontend/cpp/NamespaceTest.java b/infer/tests/frontend/cpp/NamespaceTest.java new file mode 100644 index 000000000..21e1c8d5b --- /dev/null +++ b/infer/tests/frontend/cpp/NamespaceTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package frontend.cpp; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + +public class NamespaceTest { + + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + @Test + public void whenCaptureRunCommaThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String switch_src = + "infer/tests/codetoanalyze/cpp/frontend/namespace/namespace.cpp"; + + String switch_dotty = + "infer/tests/codetoanalyze/cpp/frontend/namespace/namespace.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCPPInferCommandFrontend( + folder, + switch_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + switch_src + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(switch_dotty)); + } +} diff --git a/infer/tests/frontend/objc/ArcExampleTest.java b/infer/tests/frontend/objc/ArcExampleTest.java new file mode 100644 index 000000000..135b48000 --- /dev/null +++ b/infer/tests/frontend/objc/ArcExampleTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2014- Facebook. + * All rights reserved. + */ + +package frontend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + + +public class ArcExampleTest { + + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + + @Test + public void whenCaptureRunOnPropertyThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + + String block_src = "infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/ArcExample.m"; + + String block_dotty = + "infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/ArcExample.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontendArc(folder, block_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + block_src + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(block_dotty)); + } + + @Test + public void whenCaptureInitlistThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + + String block_src = "infer/tests/codetoanalyze/objc/frontend/vardecl/initlist.m"; + + String block_dotty = + "infer/tests/codetoanalyze/objc/frontend/vardecl/initlist.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontendArc(folder, block_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + block_src + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(block_dotty)); + } + + @Test + public void whenCaptureRunOnArcExampleThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + + String arc_src = "infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/arc_methods.m"; + + String arc_dotty = + "infer/tests/codetoanalyze/objc/errors/memory_leaks_benchmark/arc_methods.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontendArc(folder, arc_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + arc_src + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(arc_dotty)); + } +} + diff --git a/infer/tests/frontend/objc/BlockTest.java b/infer/tests/frontend/objc/BlockTest.java new file mode 100644 index 000000000..3fd676ca9 --- /dev/null +++ b/infer/tests/frontend/objc/BlockTest.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package frontend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + + +public class BlockTest { + + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + + @Test + public void whenCaptureRunOnPropertyThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + + String block_src = + "infer/tests/codetoanalyze/objc/frontend/" + + "block/block.m"; + + String block_dotty = + "infer/tests/codetoanalyze/objc/frontend/" + + "block/block.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend(folder, block_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + block_src + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(block_dotty)); + } + + @Test + public void whenCaptureRunOnBlockVarThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + + String block_src = + "infer/tests/codetoanalyze/objc/frontend/block/BlockVar.m"; + + String block_dotty = + "infer/tests/codetoanalyze/objc/frontend/block/BlockVar.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend(folder, block_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + block_src + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(block_dotty)); + } + + @Test + public void whenCaptureRunOnBlockReleaseThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + + String block_src = + "infer/tests/codetoanalyze/objc/frontend/block/block_release.m"; + + String block_dotty = + "infer/tests/codetoanalyze/objc/frontend/block/block_release.dot"; + + ImmutableList inferCmd = + InferRunner.createiOSInferCommandFrontend(folder, block_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + block_src + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(block_dotty)); + } + + @Test + public void whenCaptureRunOnBlockNoArgsThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + + String block_src = + "infer/tests/codetoanalyze/objc/frontend/block/block_no_args.m"; + + String block_dotty = + "infer/tests/codetoanalyze/objc/frontend/block/block_no_args.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend(folder, block_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + block_src + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(block_dotty)); + } + + @Test + public void whenCaptureRunOnStaticThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + + String block_src = + "infer/tests/codetoanalyze/objc/frontend/block/static.m"; + + String block_dotty = + "infer/tests/codetoanalyze/objc/frontend/block/static.dot"; + + ImmutableList inferCmd = + InferRunner.createiOSInferCommandFrontend(folder, block_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + block_src + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(block_dotty)); + } + @Test + public void whenCaptureRunOnRetainCycleThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + + String block_src = + "infer/tests/codetoanalyze/objc/frontend/block/retain_cycle.m"; + + String block_dotty = + "infer/tests/codetoanalyze/objc/frontend/block/retain_cycle.dot"; + + ImmutableList inferCmd = + InferRunner.createiOSInferCommandFrontend(folder, block_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + block_src + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(block_dotty)); + } + + @Test + public void whenCaptureRunOnDispatchThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + + String block_src = + "infer/tests/codetoanalyze/objc/frontend/block/dispatch.m"; + + String block_dotty = + "infer/tests/codetoanalyze/objc/frontend/block/dispatch.dot"; + + ImmutableList inferCmd = + InferRunner.createiOSInferCommandFrontend(folder, block_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + block_src + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(block_dotty)); + } + + @Test + public void whenCaptureRunOnDispatch_exampleThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + + String block_src = + "infer/tests/codetoanalyze/objc/frontend/block/dispatch_examples.m"; + + String block_dotty = + "infer/tests/codetoanalyze/objc/frontend/block/dispatch_examples.dot"; + + ImmutableList inferCmd = + InferRunner.createiOSInferCommandFrontend(folder, block_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + block_src + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(block_dotty)); + } + + +} diff --git a/infer/tests/frontend/objc/BoxingTest.java b/infer/tests/frontend/objc/BoxingTest.java new file mode 100644 index 000000000..2edef0f17 --- /dev/null +++ b/infer/tests/frontend/objc/BoxingTest.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package frontend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + +public class BoxingTest { + + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + + @Test + public void whenCaptureRunOnBoxingThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String boxing_expr = + "infer/tests/codetoanalyze/" + + "objc/frontend/boxing/Boxing.m"; + + String boxing_dotty = + "infer/tests/codetoanalyze/" + + "objc/frontend/boxing/Boxing.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend(folder, boxing_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + boxing_expr + + " the dotty files should be the same. " + + "In each procedure the translation of the boxing " + + "syntactic sugar is the same " + + "as the tranlation of the underlying method call.", + newDotFile, dotFileEqualTo(boxing_dotty)); + } + + @Test + public void whenCaptureRunOnArray_literalThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String arr_literal_expr = + "infer/tests/codetoanalyze/" + + "objc/frontend/boxing/array_literal.c"; + + String arr_literal_dotty = + "infer/tests/codetoanalyze/" + + "objc/frontend/boxing/array_literal.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend( + folder, + arr_literal_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + arr_literal_expr + + " the dotty files should be the same. " + + "In each procedure the translation of the boxing " + + "syntactic sugar is the same " + + "as the tranlation of the underlying method call.", + newDotFile, dotFileEqualTo(arr_literal_dotty)); + } + + @Test + public void whenCaptureRunOnDict_literalThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String dict_literal_expr = + "infer/tests/codetoanalyze/" + + "objc/frontend/boxing/dict_literal.c"; + + String dict_literal_dotty = + "infer/tests/codetoanalyze/" + + "objc/frontend/boxing/dict_literal.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend( + folder, + dict_literal_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + dict_literal_expr + + " the dotty files should be the same. " + + "In each procedure the translation of the boxing " + + "syntactic sugar is the same " + + "as the tranlation of the underlying method call.", + newDotFile, dotFileEqualTo(dict_literal_dotty)); + } + + @Test + public void whenCaptureRunOnString_literalThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String string_literal_expr = + "infer/tests/codetoanalyze/" + + "objc/frontend/boxing/string_literal.c"; + + String string_literal_dotty = + "infer/tests/codetoanalyze/" + + "objc/frontend/boxing/string_literal.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend( + folder, + string_literal_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + string_literal_expr + + " the dotty files should be the same. " + + "In each procedure the translation of the boxing " + + "syntactic sugar is the same " + + "as the tranlation of the underlying method call.", + newDotFile, dotFileEqualTo(string_literal_dotty)); + } + + @Test + public void whenCaptureRunOnForCollection_ThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String array_expr = + "infer/tests/codetoanalyze/objc/frontend/boxing/array.m"; + + String array_dotty = + "infer/tests/codetoanalyze/objc/frontend/boxing/array.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend(folder, array_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + array_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(array_dotty)); + } + +} diff --git a/infer/tests/frontend/objc/CategoryTest.java b/infer/tests/frontend/objc/CategoryTest.java new file mode 100644 index 000000000..ff2112feb --- /dev/null +++ b/infer/tests/frontend/objc/CategoryTest.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package frontend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + +public class CategoryTest { + + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + + @Test + public void whenCaptureRunOnmainThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String cat_expr = + "infer/tests/codetoanalyze/" + + "objc/errors/category_procdesc/main.c"; + + String cat_dotty = + "infer/tests/codetoanalyze/" + + "objc/errors/category_procdesc/main.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend(folder, cat_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + cat_expr + + " the dotty files should be the same. " + + "In each procedure the translation of the boxing " + + "syntactic sugar is the same " + + "as the tranlation of the underlying method call.", + newDotFile, dotFileEqualTo(cat_dotty)); + } + + @Test + public void whenCaptureRunOnEOCPersonThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String cat_expr = + "infer/tests/codetoanalyze/" + + "objc/errors/category_procdesc/EOCPerson.m"; + + String cat_dotty = + "infer/tests/codetoanalyze/" + + "objc/errors/category_procdesc/EOCPerson.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend(folder, cat_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + cat_expr + + " the dotty files should be the same. " + + "In each procedure the translation of the boxing " + + "syntactic sugar is the same " + + "as the tranlation of the underlying method call.", + newDotFile, dotFileEqualTo(cat_dotty)); + } +} diff --git a/infer/tests/frontend/objc/ConditionalOperatorTest.java b/infer/tests/frontend/objc/ConditionalOperatorTest.java new file mode 100644 index 000000000..048cfe401 --- /dev/null +++ b/infer/tests/frontend/objc/ConditionalOperatorTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package frontend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + +public class ConditionalOperatorTest { + + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + + @Test + public void whenCaptureRunCommaThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String cond_src = + "infer/tests/codetoanalyze/objc/frontend/conditional_operation/ConditionalOperation.m"; + + String cond_dotty = + "infer/tests/codetoanalyze/objc/frontend/conditional_operation/ConditionalOperation.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend( + folder, + cond_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + cond_src + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(cond_dotty)); + } +} diff --git a/infer/tests/frontend/objc/ExceptionTest.java b/infer/tests/frontend/objc/ExceptionTest.java new file mode 100644 index 000000000..dee2101d7 --- /dev/null +++ b/infer/tests/frontend/objc/ExceptionTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package frontend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + + +public class ExceptionTest { + + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + + @Test + public void whenCaptureRunOnTestThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + + String exception_src = + "infer/tests/codetoanalyze/objc/frontend/exceptions/ExceptionExample.m"; + + String exception_dotty = + "infer/tests/codetoanalyze/objc/frontend/exceptions/ExceptionExample.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend(folder, exception_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + exception_src + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(exception_dotty)); + } +} diff --git a/infer/tests/frontend/objc/LateDefinedVarDeclTest.java b/infer/tests/frontend/objc/LateDefinedVarDeclTest.java new file mode 100644 index 000000000..48a67f816 --- /dev/null +++ b/infer/tests/frontend/objc/LateDefinedVarDeclTest.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package frontend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + +public class LateDefinedVarDeclTest { + + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + + @Test + public void whenCaptureRunLateDefinedStaticVarThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String src_file = + "infer/tests/" + + "codetoanalyze/objc/frontend/vardecl/aclass.m"; + + String dot_file = + "infer/tests/" + + "codetoanalyze/objc/frontend/vardecl/aclass.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend(folder, src_file); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + src_file + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(dot_file)); + } + + @Test + public void whenCaptureRunLateDefinedVarThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String src_file = + "infer/tests/" + + "codetoanalyze/objc/frontend/vardecl/aclass_2.m"; + + String dot_file = + "infer/tests/" + + "codetoanalyze/objc/frontend/vardecl/aclass_2.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend(folder, src_file); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + src_file + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(dot_file)); + } + + @Test + public void whenCaptureRunOnFunctionDeclWithOrderedVarDeclsThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String src_file = + "infer/tests/" + + "codetoanalyze/objc/frontend/vardecl/last_af.m"; + + String dot_file = + "infer/tests/" + + "codetoanalyze/objc/frontend/vardecl/last_af.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend(folder, src_file); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + src_file + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(dot_file)); + } +} diff --git a/infer/tests/frontend/objc/MallocTest.java b/infer/tests/frontend/objc/MallocTest.java new file mode 100644 index 000000000..30ad4c331 --- /dev/null +++ b/infer/tests/frontend/objc/MallocTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package frontend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + +public class MallocTest { + + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + + @Test + public void whenCaptureRunLateDefinedStaticVarThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String src_file = + "infer/tests/codetoanalyze/objc/errors/npe/npe_malloc.m"; + + String dot_file = + "infer/tests/codetoanalyze/objc/errors/npe/npe_malloc.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend(folder, src_file); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + src_file + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(dot_file)); + } +} diff --git a/infer/tests/frontend/objc/MemoryLeakBenchmarkTest.java b/infer/tests/frontend/objc/MemoryLeakBenchmarkTest.java new file mode 100644 index 000000000..39c0f1989 --- /dev/null +++ b/infer/tests/frontend/objc/MemoryLeakBenchmarkTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package frontend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + + +public class MemoryLeakBenchmarkTest { + + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + + @Test + public void whenCaptureRunOnMemoryLeakExampleThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String ml_expr = + "infer/tests/codetoanalyze/" + + "objc/errors/memory_leaks_benchmark/MemoryLeakExample.m"; + + String ml_dotty = + "infer/tests/codetoanalyze/" + + "objc/errors/memory_leaks_benchmark/MemoryLeakExample.dot"; + + ImmutableList inferCmd = + InferRunner.createiOSInferCommandFrontend(folder, ml_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + ml_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(ml_dotty)); + } + + @Test + public void whenCaptureRunOnRetainReleaseExampleThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String ml_expr = + "infer/tests/codetoanalyze/" + + "objc/errors/memory_leaks_benchmark/RetainReleaseExample.m"; + + String ml_dotty = + "infer/tests/codetoanalyze/" + + "objc/errors/memory_leaks_benchmark/RetainReleaseExample.dot"; + + ImmutableList inferCmd = + InferRunner.createiOSInferCommandFrontend(folder, ml_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + ml_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(ml_dotty)); + } + + @Test + public void whenCaptureRunOnRetainReleaseExample2ThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String ml_expr = + "infer/tests/codetoanalyze/" + + "objc/errors/memory_leaks_benchmark/RetainReleaseExample2.m"; + + String ml_dotty = + "infer/tests/codetoanalyze/" + + "objc/errors/memory_leaks_benchmark/RetainReleaseExample2.dot"; + + ImmutableList inferCmd = + InferRunner.createiOSInferCommandFrontend(folder, ml_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + ml_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(ml_dotty)); + } + + + @Test + public void whenCaptureRunOnAutoreleaseExampleThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String ml_expr = + "infer/tests/codetoanalyze/" + + "objc/errors/memory_leaks_benchmark/AutoreleaseExample.m"; + + String ml_dotty = + "infer/tests/codetoanalyze/" + + "objc/errors/memory_leaks_benchmark/AutoreleaseExample.dot"; + + ImmutableList inferCmd = + InferRunner.createiOSInferCommandFrontend(folder, ml_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + ml_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(ml_dotty)); + } + + @Test + public void whenCaptureRunOnTollBridgeExampleThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String ml_expr = + "infer/tests/codetoanalyze/" + + "objc/errors/memory_leaks_benchmark/TollBridgeExample.m"; + + String ml_dotty = + "infer/tests/codetoanalyze/" + + "objc/errors/memory_leaks_benchmark/TollBridgeExample.dot"; + + ImmutableList inferCmd = + InferRunner.createiOSInferCommandFrontend(folder, ml_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + ml_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(ml_dotty)); + } +} diff --git a/infer/tests/frontend/objc/NSAssertTest.java b/infer/tests/frontend/objc/NSAssertTest.java new file mode 100644 index 000000000..25b56b646 --- /dev/null +++ b/infer/tests/frontend/objc/NSAssertTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2014- Facebook. + * All rights reserved. + */ + +package frontend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + + +public class NSAssertTest { + + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + + @Test + public void whenCaptureRunOnPropertyThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + + String src = "infer/tests/codetoanalyze/objc/frontend/assertions/NSAssert_example.m"; + + String dotty = "infer/tests/codetoanalyze/objc/frontend/assertions/NSAssert_example.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontendArc(folder, src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + src + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(dotty)); + } +} diff --git a/infer/tests/frontend/objc/PredefinedExpressionTest.java b/infer/tests/frontend/objc/PredefinedExpressionTest.java new file mode 100644 index 000000000..2fe05d5ad --- /dev/null +++ b/infer/tests/frontend/objc/PredefinedExpressionTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2014- Facebook. + * All rights reserved. + */ + +package frontend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + + +public class PredefinedExpressionTest { + + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + + @Test + public void whenCaptureRunOnPropertyThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + + String src = "infer/tests/codetoanalyze/objc/frontend/predefined_expr/PredefinedExprExample.m"; + + String dotty = + "infer/tests/codetoanalyze/objc/frontend/predefined_expr/PredefinedExprExample.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontendArc(folder, src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + src + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(dotty)); + } +} diff --git a/infer/tests/frontend/objc/PropertyTest.java b/infer/tests/frontend/objc/PropertyTest.java new file mode 100644 index 000000000..8a3476633 --- /dev/null +++ b/infer/tests/frontend/objc/PropertyTest.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package frontend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + + +public class PropertyTest { + + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + + @Test + public void whenCaptureRunOnPropertyThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String property_expr = + "infer/tests/codetoanalyze/" + + "objc/frontend/property/main_car.m"; + + String property_dotty = + "infer/tests/codetoanalyze/" + + "objc/frontend/property/main_car.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend(folder, property_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + property_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(property_dotty)); + } + + @Test + public void whenCaptureRunOnDynamicPropertyThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String property_expr = + "infer/tests/codetoanalyze/" + + "objc/frontend/property/aclass.m"; + + String property_dotty = + "infer/tests/codetoanalyze/" + + "objc/frontend/property/aclass.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend(folder, property_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + property_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(property_dotty)); + } + + @Test + public void whenCaptureRunOnPropertyInProtocolThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String property_expr = + "infer/tests/codetoanalyze/objc/frontend/" + + "property_in_protocol/Test.m"; + + String property_dotty = + "infer/tests/codetoanalyze/objc/frontend/" + + "property_in_protocol/Test.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend(folder, property_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + property_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(property_dotty)); + } + + @Test + public void whenCaptureRunOnPropertyImplSetterThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String property_expr = + "infer/tests/codetoanalyze/objc/frontend/" + + "property/PropertyImplSetter.m"; + + String property_dotty = + "infer/tests/codetoanalyze/objc/frontend/" + + "property/PropertyImplSetter.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend(folder, property_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + property_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(property_dotty)); + } + + @Test + public void whenCaptureRunOnPropertyAttributesThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String property_expr = + "infer/tests/codetoanalyze/objc/frontend/" + + "property/PropertyAttributes.m"; + + String property_dotty = + "infer/tests/codetoanalyze/objc/frontend/" + + "property/PropertyAttributes.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend(folder, property_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + property_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(property_dotty)); + } + + @Test + public void whenCaptureRunOnProperty_getterThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String property_expr = + "infer/tests/codetoanalyze/objc/frontend/property/Property_getter.m"; + + String property_dotty = + "infer/tests/codetoanalyze/objc/frontend/property/Property_getter.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend(folder, property_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + property_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(property_dotty)); + } + + @Test + public void whenCaptureRunOnPropertyCustomAccessorThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String property_expr = + "infer/tests/codetoanalyze/objc/frontend/property/PropertyCustomAccessor.m"; + + String property_dotty = + "infer/tests/codetoanalyze/objc/frontend/property/PropertyCustomAccessor.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend(folder, property_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + property_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(property_dotty)); + } + + + +} diff --git a/infer/tests/frontend/objc/ProtocolTest.java b/infer/tests/frontend/objc/ProtocolTest.java new file mode 100644 index 000000000..4707f7888 --- /dev/null +++ b/infer/tests/frontend/objc/ProtocolTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package frontend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + +public class ProtocolTest { + + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + + @Test + public void whenCaptureRunOnProtocolThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String protocol_expr = + "infer/tests/codetoanalyze/" + + "objc/frontend/protocol/protocol.m"; + + String protocol_dotty = + "infer/tests/codetoanalyze/" + + "objc/frontend/protocol/protocol.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend( + folder, + protocol_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + protocol_expr + + " the dotty files should be the same. " + + "In each procedure the translation of the boxing " + + "syntactic sugar is the same " + + "as the tranlation of the underlying method call.", + newDotFile, dotFileEqualTo(protocol_dotty)); + } + + @Test + public void whenCaptureRunOnBicycleThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String protocol_expr = + "infer/tests/codetoanalyze/" + + "objc/errors/protocol_procdesc/main.c"; + + String protocol_dotty = + "infer/tests/codetoanalyze/" + + "objc/errors/protocol_procdesc/main.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend( + folder, + protocol_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + protocol_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(protocol_dotty)); + } + +} diff --git a/infer/tests/frontend/objc/ReturnTest.java b/infer/tests/frontend/objc/ReturnTest.java new file mode 100644 index 000000000..81b153e70 --- /dev/null +++ b/infer/tests/frontend/objc/ReturnTest.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package frontend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + + +public class ReturnTest { + + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + + @Test + public void whenCaptureRunOnVoidReturnThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + + String src_file = + "infer/tests/codetoanalyze/objc/frontend/" + + "returnstmt/void_return.m"; + + String dotty_file = + "infer/tests/codetoanalyze/objc/frontend/" + + "returnstmt/void_return.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend(folder, src_file); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + src_file + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(dotty_file)); + } + +} diff --git a/infer/tests/frontend/objc/StaticSelf.java b/infer/tests/frontend/objc/StaticSelf.java new file mode 100644 index 000000000..2de41b574 --- /dev/null +++ b/infer/tests/frontend/objc/StaticSelf.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package frontend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + + +public class StaticSelf { + + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + + @Test + public void whenCaptureRunOnStaticThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String property_expr = + "infer/tests/codetoanalyze/" + + "objc/frontend/self_static/static.m"; + + String property_dotty = + "infer/tests/codetoanalyze/" + + "objc/frontend/self_static/static.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend( + folder, + property_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + property_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(property_dotty)); + } + + @Test + public void whenCaptureRunOnSuperTestThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String expr = "infer/tests/codetoanalyze/objc/errors/field_superclass/SuperExample.m"; + + String dotty = "infer/tests/codetoanalyze/objc/errors/field_superclass/SuperExample.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend( + folder, + expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(dotty)); + } + + @Test + public void whenCaptureRunOnSelfThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String expr = "infer/tests/codetoanalyze/objc/frontend/self_static/Self.m"; + + String dotty = "infer/tests/codetoanalyze/objc/frontend/self_static/Self.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend( + folder, + expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(dotty)); + } + + +} diff --git a/infer/tests/frontend/objc/StringTest.java b/infer/tests/frontend/objc/StringTest.java new file mode 100644 index 000000000..7d7932d88 --- /dev/null +++ b/infer/tests/frontend/objc/StringTest.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package frontend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + +public class StringTest { + + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + + @Test + public void whenCaptureRunOnStringLiteralThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String string_expr = + "infer/tests/codetoanalyze/" + + "objc/frontend/strings/string_literal.m"; + + String string_dotty = + "infer/tests/codetoanalyze/" + + "objc/frontend/strings/string_literal.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend(folder, string_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + string_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(string_dotty)); + } + + @Test + public void whenCaptureRunOnGlobalStringLiteralThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String string_expr = + "infer/tests/codetoanalyze/" + + "objc/frontend/strings/global_string_literal.m"; + + String string_dotty = + "infer/tests/codetoanalyze/" + + "objc/frontend/strings/global_string_literal.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend(folder, string_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + string_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(string_dotty)); + } +} diff --git a/infer/tests/frontend/objc/SubclassTest.java b/infer/tests/frontend/objc/SubclassTest.java new file mode 100644 index 000000000..2aebc24e6 --- /dev/null +++ b/infer/tests/frontend/objc/SubclassTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package frontend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + +public class SubclassTest { + + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + + @Test + public void whenCaptureRunOnStringLiteralThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String myClass_expr = + "infer/tests/codetoanalyze/" + + "objc/frontend/subclass/MyClass.m"; + + String string_dotty = + "infer/tests/codetoanalyze/" + + "objc/frontend/subclass/MyClass.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend( + folder, + myClass_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + myClass_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(string_dotty)); + } + + @Test + public void whenCaptureRunOnGlobalStringLiteralThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String mySubClass_expr = + "infer/tests/codetoanalyze/" + + "objc/frontend/subclass/MySubClass.m"; + + String string_dotty = + "infer/tests/codetoanalyze/" + + "objc/frontend/subclass/MySubClass.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend( + folder, + mySubClass_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + mySubClass_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(string_dotty)); + } + + @Test + public void whenCaptureRunOnMainThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String mySubClass_expr = "infer/tests/codetoanalyze/objc/frontend/subclass/main.c"; + + String string_dotty = "infer/tests/codetoanalyze/objc/frontend/subclass/main.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend( + folder, + mySubClass_expr); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + mySubClass_expr + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(string_dotty)); + } + + +} diff --git a/infer/tests/frontend/objc/TypesTest.java b/infer/tests/frontend/objc/TypesTest.java new file mode 100644 index 000000000..38afb54de --- /dev/null +++ b/infer/tests/frontend/objc/TypesTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package frontend.objc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + + +public class TypesTest { + + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + + @Test + public void whenCaptureRunOnTestLoopThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + + String block_src = + "infer/tests/codetoanalyze/objc/frontend/" + + "types/testloop.m"; + + String block_dotty = + "infer/tests/codetoanalyze/objc/frontend/" + + "types/testloop.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontend(folder, block_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + block_src + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(block_dotty)); + } + + @Test + public void whenCaptureRunOnAttributesThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + + String block_src = + "infer/tests/codetoanalyze/objc/frontend/" + + "types/attributes.m"; + + String block_dotty = + "infer/tests/codetoanalyze/objc/frontend/" + + "types/attributes.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontendArc(folder, block_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + block_src + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(block_dotty)); + } + + @Test + public void whenCaptureRunOnVoidCall() + throws InterruptedException, IOException, InferException { + + String block_src = + "infer/tests/codetoanalyze/objc/frontend/" + + "types/void_call.m"; + + String block_dotty = + "infer/tests/codetoanalyze/objc/frontend/" + + "types/void_call.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCInferCommandFrontendArc(folder, block_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + block_src + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(block_dotty)); + } +} diff --git a/infer/tests/frontend/objcpp/FuncOverloadingTest.java b/infer/tests/frontend/objcpp/FuncOverloadingTest.java new file mode 100644 index 000000000..3d3a62fb3 --- /dev/null +++ b/infer/tests/frontend/objcpp/FuncOverloadingTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2013- Facebook. + * All rights reserved. + */ + +package frontend.objcpp; + +import static org.hamcrest.MatcherAssert.assertThat; +import static utils.matchers.DotFilesEqual.dotFileEqualTo; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import utils.DebuggableTemporaryFolder; +import utils.InferException; +import utils.InferRunner; + +public class FuncOverloadingTest { + + @Rule + public DebuggableTemporaryFolder folder = new DebuggableTemporaryFolder(); + + + @Test + public void whenCaptureRunSwitchStmtThenDotFilesAreTheSame() + throws InterruptedException, IOException, InferException { + String switch_src = + "infer/tests/codetoanalyze/objcpp/frontend/" + + "funcoverloading/af_test.mm"; + + String switch_dotty = + "infer/tests/codetoanalyze/objcpp/frontend/" + + "funcoverloading/af_test.dot"; + + ImmutableList inferCmd = + InferRunner.createObjCPPInferCommand(folder, switch_src); + File newDotFile = InferRunner.runInferFrontend(inferCmd); + assertThat( + "In the capture of " + switch_src + + " the dotty files should be the same.", + newDotFile, dotFileEqualTo(switch_dotty)); + } +} diff --git a/infer/tests/tests.iml b/infer/tests/tests.iml new file mode 100644 index 000000000..0b2cf5832 --- /dev/null +++ b/infer/tests/tests.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/infer/tests/utils/BUCK b/infer/tests/utils/BUCK new file mode 100644 index 000000000..3792baf39 --- /dev/null +++ b/infer/tests/utils/BUCK @@ -0,0 +1,18 @@ +java_library( + name='utils', + srcs=glob(['**/*.java']), + deps=[ + '//infer/lib/java/android:android', + '//dependencies/java/guava:guava', + '//dependencies/java/junit:hamcrest', + '//dependencies/java/jackson:jackson', + '//dependencies/java/jsr-305:jsr-305', + '//dependencies/java/junit:junit', + '//dependencies/java/opencsv:opencsv' + ], + visibility = [ + 'PUBLIC', + ], + source='7', + target='7', +) diff --git a/infer/tests/utils/DebuggableTemporaryFolder.java b/infer/tests/utils/DebuggableTemporaryFolder.java new file mode 100644 index 000000000..af03c1873 --- /dev/null +++ b/infer/tests/utils/DebuggableTemporaryFolder.java @@ -0,0 +1,71 @@ +/* + * Copyright 2012-present Facebook, Inc. + * + * 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 utils; + +import org.junit.rules.TemporaryFolder; + +import java.util.Map; + +import javax.annotation.Nullable; + + +public class DebuggableTemporaryFolder extends TemporaryFolder { + + private + @Nullable + String name; + private boolean doNotDeleteOnExit; + + public DebuggableTemporaryFolder() { + Map env = System.getenv(); + if (env.get("INFER_KEEP_FOLDER") != null) { + doNotDeleteOnExit = true; + } + } + + /** + * If invoked, the directory created by this {@link org.junit.rules.TemporaryFolder} will not be + * deleted when the test finishes. + * + * @return {@code this} + */ + public DebuggableTemporaryFolder doNotDeleteOnExit() { + this.doNotDeleteOnExit = true; + return this; + } + + /** + * Name to use to identify this {@link org.junit.rules.TemporaryFolder} when writing log messages + * to stdout. + * + * @return {@code this} + */ + public DebuggableTemporaryFolder setName(String name) { + this.name = name; + return this; + } + + @Override + public void after() { + if (doNotDeleteOnExit) { + String name = this.name == null ? "TemporaryFolder" : this.name; + System.out.printf("%s available at %s.\n", name, getRoot()); + } else { + super.after(); + } + } +} diff --git a/infer/tests/utils/InferError.java b/infer/tests/utils/InferError.java new file mode 100644 index 000000000..cf4386e01 --- /dev/null +++ b/infer/tests/utils/InferError.java @@ -0,0 +1,86 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package utils; + +import java.nio.file.Path; + +public class InferError { + + private String errorType; + private Path errorFile; + private String errorMethod; + private int errorLine; + + public InferError(String errorType, Path errorFile, String errorMethod, int errorLine) { + this.errorType = errorType; + this.errorMethod = errorMethod; + this.errorLine = errorLine; + this.errorFile = errorFile; + } + + public InferError(String errorType, Path errorFile, String errorMethod) { + this.errorType = errorType; + this.errorFile = errorFile; + this.errorMethod = errorMethod; + this.errorLine = -1; + } + + public static InferError inferError(String errorMethod, Path errorFile, String errorType) { + return new InferError(errorMethod, errorFile, errorType); + } + + public String getErrorType() { + return errorType; + } + + public String getErrorMethod() { + return errorMethod; + } + + public Path getErrorFile() { + return errorFile; + } + + public int getErrorLine() { + return errorLine; + } + + public String toStringNoLine() { + return errorType + " at " + errorFile + ", method " + errorMethod; + } + + public String toString() { + if (errorLine > 0) + return toStringNoLine() + " (line " + errorLine + ")"; + else return toStringNoLine(); + } + + public String toStringFileMethod() { + return errorFile + ", method " + errorMethod; + } + + public String toStringErrorTypeMethod() { + return errorType + " at " + errorMethod; + } + + public boolean matchType(String type) { + return this.errorType.equals(type); + } + + public boolean matchFile(Path file) { + return this.errorFile.equals(file); + } + + public boolean matchMethod(String method) { + return this.errorMethod.equals(method); + } + + public boolean matchLine(int line) { + return this.errorLine < 0 || line < 0 + || (this.errorLine == line); + } + +} diff --git a/infer/tests/utils/InferException.java b/infer/tests/utils/InferException.java new file mode 100644 index 000000000..968ddcd51 --- /dev/null +++ b/infer/tests/utils/InferException.java @@ -0,0 +1,24 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package utils; + + +public class InferException extends RuntimeException { + + public InferException(String message) { + super(message); + } + + @Override + public String toString() { + return ""; + } + + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } +} diff --git a/infer/tests/utils/InferResults.java b/infer/tests/utils/InferResults.java new file mode 100644 index 000000000..354f94533 --- /dev/null +++ b/infer/tests/utils/InferResults.java @@ -0,0 +1,217 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package utils; + + +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.StringReader; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Vector; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import au.com.bytecode.opencsv.CSVReader; + +public class InferResults { + + private ImmutableList inferCmd; + + public static final Pattern JAVA_METHOD_NAME = Pattern.compile("(\\.)(.*)(\\()"); + public static final Pattern C_FUNCTION_NAME = Pattern.compile("(\")(.*)(\")"); + public static final Pattern OBJC_FUNCTION_NAME = Pattern.compile("(.*)_(.*)"); + + private Vector errors = new Vector(); + + InferResults() { + this.inferCmd = ImmutableList.of(""); + } + + InferResults(ImmutableList inferCmd) { + this.inferCmd = inferCmd; + } + + public void parseInferResultsFromString( + Pattern pattern, + String errorString) throws IOException, InferException { + + CSVReader reader = new CSVReader(new StringReader(errorString)); + List lines = reader.readAll(); + Path root = Paths.get(System.getProperty("user.dir")); + + for (String[] items : lines) { + String errorKind = items[1].trim(); + String errorType = items[2].trim(); + if (errorKind.equals("ERROR") || + errorType.equals("RETURN_VALUE_IGNORED") || + errorType.equals("IMMUTABLE_CAST") || + errorType.equals("PARAMETER_NOT_NULL_CHECKED") || + errorType.equals("IVAR_NOT_NULL_CHECKED") || + errorType.startsWith("ERADICATE")) { + Integer errorLine = Integer.parseInt(items[5].trim()); + String procedure = items[6]; + Path path = Paths.get(items[8]); + if (path.isAbsolute()) { + path = root.relativize(Paths.get(items[8])); + } + Matcher methodMatcher = pattern.matcher(procedure); + boolean matching = methodMatcher.find(); + if (matching) { + procedure = methodMatcher.group(2); + if (procedure == null) { + throw new InferException("Unexpected method name structure."); + } + } + procedure = procedure.trim(); + InferError error = new InferError(errorType, path, procedure, errorLine); + errors.add(error); + } + } + } + + public void parseJavaInferResultsFromString(String errorString) + throws IOException, InferException { + parseInferResultsFromString(JAVA_METHOD_NAME, errorString); + } + + public void parseCInferResultsFromString(String errorString) + throws IOException, InferException { + parseInferResultsFromString(C_FUNCTION_NAME, errorString); + } + + public void parseObjCInferResultsFromString(String errorString) + throws IOException, InferException { + parseInferResultsFromString(OBJC_FUNCTION_NAME, errorString); + } + + public Vector getErrors() { + return errors; + } + + public String inferCmdToString() { + return "Infer command: " + Joiner.on(' ').join(inferCmd); + } + + public String toString() { + String s = ""; + for (InferError e : errors) { + s = s + "\n" + e.toString(); + } + return s; + } + + public void filter(String filename) { + Vector filtered_errors = new Vector(); + for (InferError error : errors) { + if (error.getErrorFile().endsWith(filename)) { + filtered_errors.add(error); + } + } + errors = filtered_errors; + } + + public static InferResults loadResultsFromReader( + BufferedReader reader, + String sourceFile, + Pattern pattern) { + InferResults inferResults = new InferResults(); + String resultString = ""; + try { + String line = reader.readLine(); + while (line != null) { + resultString += line + "\n"; + line = reader.readLine(); + } + inferResults.parseInferResultsFromString( + pattern, + resultString); + } catch (IOException e) { + throw new RuntimeException(e.toString()); + } + inferResults.filter(sourceFile); + return inferResults; + } + + public static InferResults loadInferResults(Class currentClass, String sourceFile) { + BufferedReader reader = + new BufferedReader( + new InputStreamReader( + currentClass.getResourceAsStream( + "/infer/tests/codetoanalyze/java/infer/report.csv"))); + return loadResultsFromReader( + Preconditions.checkNotNull(reader), + sourceFile, + InferResults.JAVA_METHOD_NAME); + } + + public static InferResults loadEradicateResults(Class currentClass, String sourceFile) { + BufferedReader reader = + new BufferedReader( + new InputStreamReader( + currentClass.getResourceAsStream( + "/infer/tests/codetoanalyze/java/eradicate/report.csv"))); + return loadResultsFromReader( + Preconditions.checkNotNull(reader), + sourceFile, + InferResults.JAVA_METHOD_NAME); + } + + public static InferResults loadCheckersResults(Class currentClass, String sourceFile) { + BufferedReader reader = + new BufferedReader( + new InputStreamReader( + currentClass.getResourceAsStream( + "/infer/tests/codetoanalyze/java/checkers/report.csv"))); + return loadResultsFromReader( + Preconditions.checkNotNull(reader), + sourceFile, + InferResults.JAVA_METHOD_NAME); + } + + public static InferResults loadTracingResults(Class currentClass, String sourceFile) { + BufferedReader reader = + new BufferedReader( + new InputStreamReader( + currentClass.getResourceAsStream( + "/infer/tests/codetoanalyze/java/tracing/report.csv"))); + return loadResultsFromReader( + Preconditions.checkNotNull(reader), + sourceFile, + InferResults.JAVA_METHOD_NAME); + } + + public static InferResults loadTracingComparisonResults(Class currentClass, String sourceFile) { + BufferedReader reader = + new BufferedReader( + new InputStreamReader( + currentClass.getResourceAsStream( + "/infer/tests/codetoanalyze/java/infer/comparison_report.csv"))); + return loadResultsFromReader( + Preconditions.checkNotNull(reader), + sourceFile, + InferResults.JAVA_METHOD_NAME); + } + + public static InferResults loadCInferResults(Class currentClass, String sourceFile) { + BufferedReader reader = + new BufferedReader( + new InputStreamReader( + currentClass.getResourceAsStream( + "/infer/tests/codetoanalyze/c/errors/report.csv"))); + return loadResultsFromReader( + Preconditions.checkNotNull(reader), + sourceFile, + InferResults.C_FUNCTION_NAME); + } + +} diff --git a/infer/tests/utils/InferRunner.java b/infer/tests/utils/InferRunner.java new file mode 100644 index 000000000..a2f5ca2fc --- /dev/null +++ b/infer/tests/utils/InferRunner.java @@ -0,0 +1,566 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package utils; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; + +import org.junit.rules.TemporaryFolder; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.HashMap; +import java.util.Map; + +import javax.annotation.Nullable; + +public class InferRunner { + + public static final String BUGS_FILE_NAME = "report.csv"; + + public static final String DOT_FILE_NAME = "icfg.dot"; + + public static final String CAPTURED_FOLDER = "captured"; + + private static File bugsFile; + + @Nullable + private static File dotFile; + + private static File resultsDir; + + private static final ImmutableList EMPTY_ARGS = ImmutableList.of(); + + private static final String ANDROID = + "/infer/lib/java/android/android-19.jar"; + + private static final String[] LIBRARIES = { + "/infer/annotations/annotations.jar", + "/dependencies/java/guava/guava-10.0.1-fork.jar", + "/dependencies/java/jackson/jackson-2.2.3.jar", + }; + + private static final String IOS_ISYSROOT_SUFFIX = + "/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk"; + + private static HashMap inferResultsMap = + new HashMap(); + + private InferRunner() { + } + + private static String getXcodeRoot() throws IOException, InterruptedException { + ProcessBuilder pb = new ProcessBuilder("xcode-select", "-p"); + Process process = pb.start(); + InputStream is = process.getInputStream(); + InputStreamReader isr = new InputStreamReader(is); + BufferedReader br = new BufferedReader(isr); + String line = br.readLine(); + process.waitFor(); + return line; + } + + private static ImmutableList createInferJavaCommand( + TemporaryFolder folder, + ImmutableList sourceFiles, + String analyzer, + ImmutableList args) { + try { + File resultsDir = createResultsDir(folder); + String resultsDirName = resultsDir.getAbsolutePath(); + InferRunner.bugsFile = new File(resultsDir, BUGS_FILE_NAME); + + ImmutableList javacCmd = createJavacCommand( + folder, + sourceFiles); + + return new ImmutableList.Builder() + .add("infer") + .addAll(args) + .add("-o") + .add(resultsDirName) + .add("-a") + .add(analyzer) + .add("--") + .addAll(javacCmd) + .build(); + } catch (IOException e) { + e.printStackTrace(); + System.exit(1); + return ImmutableList.of(); // unreachable + } + } + + private static ImmutableList createJavacCommand( + TemporaryFolder folder, + ImmutableList sourceFiles) + throws IOException { + File classesDir = folder.newFolder("classes_out"); + String classesDirName = classesDir.getAbsolutePath(); + + ImmutableList javacCmd = new ImmutableList.Builder() + .add("javac") + .add("-classpath") + .add(getClasspath()) + .add("-bootclasspath") + .add(getBootClasspath()) + .add("-d") + .add(classesDirName) + .addAll(sourceFiles).build(); + return javacCmd; + } + + public static ImmutableList createJavaInferHarnessCommand( + TemporaryFolder folder, + ImmutableList sourceFiles) { + ImmutableList args = new ImmutableList.Builder().build(); + return createInferJavaCommand(folder, sourceFiles, "infer", args); + } + + public static ImmutableList createJavaInferHarnessCommand( + TemporaryFolder folder, + String sourceFile) { + return createJavaInferHarnessCommand(folder, ImmutableList.of(sourceFile)); + } + + public static ImmutableList createJavaInferAngelicHarnessCommand( + TemporaryFolder folder, + ImmutableList sourceFiles) { + ImmutableList args = (new ImmutableList.Builder()).build(); + return createInferJavaCommand(folder, sourceFiles, "infer", args); + } + + public static String getClangLangOption(Language lang) { + String langOption = ""; + switch (lang) { + case C: + langOption = "c"; + break; + + case ObjC: + langOption = "objective-c"; + break; + + case CPP: + langOption = "c++"; + break; + + case ObjCPP: + langOption = "objective-c++"; + break; + + default: + throw new RuntimeException( + "It should be called only with the " + + "languages (C, C++, ObjC, ObjC++)"); + } + return langOption; + } + + public static ImmutableList createClangCommand( + String sourceFile, + Language lang, + @Nullable String isysroot, + boolean arc) { + ImmutableList.Builder isysrootOption = + new ImmutableList.Builder<>(); + if (isysroot != null) { + isysrootOption + .add("-isysroot") + .add(isysroot) + .add("-mios-simulator-version-min=8.2"); + } + ImmutableList.Builder arcOption = + new ImmutableList.Builder<>(); + if (arc) { + arcOption.add("-fobjc-arc"); + } + ImmutableList clangCmd = new ImmutableList.Builder() + .add("clang") + .add("-x") + .add(getClangLangOption(lang)) + .addAll(isysrootOption.build()) + .addAll(arcOption.build()) + .add("-c") + .add(sourceFile) + .add("-o") + .add(sourceFile + ".o") + .build(); + return clangCmd; + } + + public static ImmutableList createClangInferCommand( + TemporaryFolder folder, + String sourceFile, + Language lang, + boolean analyze, + @Nullable String isysroot, + @Nullable String ml_buckets, + boolean arc) { + File resultsDir = createResultsDir(folder); + String resultsDirName = resultsDir.getAbsolutePath(); + InferRunner.bugsFile = new File(resultsDir, BUGS_FILE_NAME); + + ImmutableList.Builder analyzeOption = + new ImmutableList.Builder<>(); + if (!analyze) { + analyzeOption + .add("--analyzer") + .add("capture"); + } + ImmutableList.Builder ml_bucketsOption = + new ImmutableList.Builder<>(); + ml_bucketsOption + .add("--objc_ml_buckets") + .add(ml_buckets == null ? "all" : ml_buckets); + ImmutableList inferCmd = new ImmutableList.Builder() + .add("infer") + .add("--out") + .add(resultsDirName) + .add("--testing_mode") + .addAll(analyzeOption.build()) + .addAll(ml_bucketsOption.build()) + .add("--") + .addAll(createClangCommand(sourceFile, lang, isysroot, arc)) + .build(); + return inferCmd; + } + + public static ImmutableList createCInferCommandFrontend( + TemporaryFolder folder, + String sourceFile) { + return createClangInferCommand( + folder, + sourceFile, + Language.C, + false, + null, + null, + false); + } + + public static ImmutableList createCPPInferCommandFrontend( + TemporaryFolder folder, + String sourceFile) { + return createClangInferCommand( + folder, + sourceFile, + Language.CPP, + false, + null, + null, + false); + } + + public static ImmutableList createObjCInferCommandFrontend( + TemporaryFolder folder, + String sourceFile) { + return createClangInferCommand( + folder, + sourceFile, + Language.ObjC, + false, + null, + null, + false); + } + + public static ImmutableList createObjCInferCommandFrontendArc( + TemporaryFolder folder, + String sourceFile) { + return createClangInferCommand( + folder, + sourceFile, + Language.ObjC, + false, + null, + null, + true); + } + + public static ImmutableList createObjCPPInferCommandFrontend( + TemporaryFolder folder, + String sourceFile) { + return createClangInferCommand( + folder, + sourceFile, + Language.ObjCPP, + false, + null, + null, + false); + } + + public static ImmutableList createCInferCommand( + TemporaryFolder folder, + String sourceFile) { + return createClangInferCommand( + folder, + sourceFile, + Language.C, + true, + null, + null, + false); + } + + public static ImmutableList createCPPInferCommand( + TemporaryFolder folder, + String sourceFile) { + return createClangInferCommand( + folder, + sourceFile, + Language.CPP, + true, + null, + null, + false); + } + + public static ImmutableList createObjCInferCommand( + TemporaryFolder folder, + String sourceFile) { + return createClangInferCommand( + folder, + sourceFile, + Language.ObjC, + true, + null, + null, + false); + } + + public static ImmutableList createObjCInferCommandWithMLBuckets( + TemporaryFolder folder, + String sourceFile, + String ml_bucket, + boolean arc) { + return createClangInferCommand( + folder, + sourceFile, + Language.ObjC, + true, + null, + ml_bucket, + arc); + } + + public static ImmutableList createObjCPPInferCommand( + TemporaryFolder folder, + String sourceFile) { + return createClangInferCommand( + folder, + sourceFile, + Language.ObjCPP, + true, + null, + null, + false); + } + + public static ImmutableList createiOSInferCommandFrontend( + TemporaryFolder folder, + String sourceFile) throws IOException, InterruptedException { + return createClangInferCommand( + folder, + sourceFile, + Language.ObjC, + false, + getXcodeRoot() + IOS_ISYSROOT_SUFFIX, + null, + false); + } + + public static ImmutableList createiOSInferCommand( + TemporaryFolder folder, + String sourceFile) throws IOException, InterruptedException { + return createClangInferCommand( + folder, + sourceFile, + Language.ObjC, + true, + getXcodeRoot() + IOS_ISYSROOT_SUFFIX, + null, + false); + } + + public static ImmutableList createiOSInferCommandWithMLBuckets( + TemporaryFolder folder, + String sourceFile, + String bucket, + boolean arc) throws IOException, InterruptedException { + return createClangInferCommand( + folder, + sourceFile, + Language.ObjC, + true, + getXcodeRoot() + IOS_ISYSROOT_SUFFIX, + bucket, + arc); + } + + @Nullable + public static File runInferFrontend(ImmutableList inferCmd) + throws IOException, InterruptedException, InferException { + runCommand(inferCmd, TestType.FRONTEND); + return dotFile; + } + + private static InferResults runInfer( + ImmutableList inferCmd, + Language lang) + throws IOException, InterruptedException, InferException { + String inferCmdString = Joiner.on(' ').join(inferCmd); + InferResults results = inferResultsMap.get(inferCmdString); + if (results == null) { + if (lang == Language.Java) checkLibraries(); + + runCommand(inferCmd, TestType.END_TO_END); + String errorString = getErrors(bugsFile); + results = new InferResults(inferCmd); + if (lang == Language.Java) + results.parseJavaInferResultsFromString(errorString); + else if (lang == Language.C || lang == Language.CPP || lang == Language.ObjCPP) + results.parseCInferResultsFromString(errorString); + else if (lang == Language.ObjC) + results.parseObjCInferResultsFromString(errorString); + inferResultsMap.put(inferCmdString, results); + } + return results; + } + + public static InferResults runInferJava(ImmutableList inferCmd) + throws IOException, InterruptedException, InferException { + return runInfer(inferCmd, Language.Java); + } + + public static InferResults runInferC(ImmutableList inferCmd) + throws IOException, InterruptedException, InferException { + return runInfer(inferCmd, Language.C); + } + + public static InferResults runInferCPP(ImmutableList inferCmd) + throws IOException, InterruptedException, InferException { + return runInfer(inferCmd, Language.CPP); + } + + public static InferResults runInferObjC(ImmutableList inferCmd) + throws IOException, InterruptedException, InferException { + return runInfer(inferCmd, Language.ObjC); + } + + public static InferResults runInferObjCPP(ImmutableList inferCmd) + throws IOException, InterruptedException, InferException { + return runInfer(inferCmd, Language.ObjCPP); + } + + + private static void runCommand( + ImmutableList inferCmd, + TestType testType) + throws IOException, InterruptedException, InferException { + ProcessBuilder pb = new ProcessBuilder(inferCmd); + + Map env = pb.environment(); + env.put("REPORT_ASSERTION_FAILURE", "1"); + + Process process = pb.start(); + StringBuilder stderr = new StringBuilder(); + try (BufferedReader bufferedReader = + new BufferedReader( + new InputStreamReader( + process.getErrorStream()))) { + String line = null; + while ((line = bufferedReader.readLine()) != null) { + stderr.append(line + "\n"); + } + } + process.waitFor(); + + File file = null; + if (testType == TestType.END_TO_END) { + file = bugsFile; + } else if (testType == TestType.FRONTEND) { + getDotFile(); + file = dotFile; + } + if (file == null || !file.exists()) { + String fileNotFoundMessage = file == null ? + "" : "\n\nFile " + file + " not found."; + throw new InferException( + "There was an error while calling Infer." + + "\n\nCommand output:\n" + stderr + + "\nCommand: " + Joiner.on(' ').join(inferCmd) + + fileNotFoundMessage + + "\n=========================================================================="); + } + } + + static String getErrors(File file) throws IOException { + String s = ""; + String line = ""; + try (BufferedReader br = new BufferedReader(new FileReader(file))) { + br.readLine(); + while ((line = br.readLine()) != null) { + s += line + "\n"; + } + } + return s; + } + + private static String getBootClasspath() { + String current_dir = System.getProperty("user.dir"); + String bootclasspath = current_dir + ANDROID; + return bootclasspath; + } + + private static String getClasspath() { + String current_dir = System.getProperty("user.dir"); + StringBuilder classpath = new StringBuilder(); + for (String library : LIBRARIES) { + classpath.append(current_dir); + classpath.append(library); + classpath.append(":"); + } + classpath.deleteCharAt(classpath.length() - 1); + return classpath.toString(); + } + + private static void checkLibraries() throws InferException { + String current_dir = System.getProperty("user.dir"); + for (String lib_file : LIBRARIES) { + File lib = new File(current_dir + lib_file); + if (!lib.exists()) { + throw new InferException("File " + lib_file + " not found."); + } + } + } + + private static File createResultsDir(TemporaryFolder folder) { + try { + resultsDir = folder.newFolder("infer_out"); + return resultsDir; + } catch (IOException e) { + e.printStackTrace(); + System.exit(1); + return null; // unreachable + } + } + + private static void getDotFile() { + File captured = new File(resultsDir, CAPTURED_FOLDER); + if (captured.exists()) { + File f = new File(captured, captured.list()[0]); + dotFile = new File(f, DOT_FILE_NAME); + } else { + dotFile = null; + } + } +} diff --git a/infer/tests/utils/Language.java b/infer/tests/utils/Language.java new file mode 100644 index 000000000..bdcd84950 --- /dev/null +++ b/infer/tests/utils/Language.java @@ -0,0 +1,10 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package utils; + +public enum Language { + Java, C, ObjC, CPP, ObjCPP +} diff --git a/infer/tests/utils/TestType.java b/infer/tests/utils/TestType.java new file mode 100644 index 000000000..59e0b8138 --- /dev/null +++ b/infer/tests/utils/TestType.java @@ -0,0 +1,10 @@ +/* +* Copyright (c) 2013- Facebook. +* All rights reserved. +*/ + +package utils; + +public enum TestType { + END_TO_END, FRONTEND; +} diff --git a/infer/tests/utils/matchers/DotFilesEqual.java b/infer/tests/utils/matchers/DotFilesEqual.java new file mode 100644 index 000000000..bb4efb4de --- /dev/null +++ b/infer/tests/utils/matchers/DotFilesEqual.java @@ -0,0 +1,77 @@ +package utils.matchers; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; + +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStreamReader; +import java.util.Map; + +public class DotFilesEqual extends BaseMatcher { + + String dotFile; + + public DotFilesEqual(String dotFile) { + this.dotFile = dotFile; + } + + private StringBuilder diffOutput = new StringBuilder(); + private String diffCommand = ""; + + @Override + public boolean matches(Object o) { + File newDotFile = (File) o; + Map env = System.getenv(); + if (env.get("INFER_DOT_REPLACE") != null) { + diffCommand = "cp " + newDotFile.getAbsolutePath() + " " + dotFile; + } else { + diffCommand = "diff -u " + dotFile + " " + newDotFile.getAbsolutePath(); + } + try { + Process diff_process = Runtime.getRuntime().exec(diffCommand); + try (BufferedReader inputReader = + new BufferedReader(new InputStreamReader(diff_process.getInputStream())); + BufferedReader errorReader = + new BufferedReader(new InputStreamReader(diff_process.getErrorStream()))) { + + String line = inputReader.readLine(); + while (line != null) { + diffOutput = diffOutput.append(line + "\n"); + line = inputReader.readLine(); + } + String errorLine = errorReader.readLine(); + while (errorLine != null) { + diffOutput = diffOutput.append(errorLine + "\n"); + errorLine = errorReader.readLine(); + } + diff_process.waitFor(); + return "".equals(diffOutput.toString()); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + @Override + public void describeTo(Description description) { + description.appendText("Dot file equal to " + dotFile); + } + + @Override + public void describeMismatch(Object item, Description description) { + String outputDesc = "The command:\n\n " + diffCommand + + "\n\nfailed with output:\n" + diffOutput.toString() + "\n"; + description.appendText(outputDesc); + } + + public static Matcher dotFileEqualTo(String dotFile) { + return new DotFilesEqual(dotFile); + } + +} diff --git a/infer/tests/utils/matchers/ErrorPattern.java b/infer/tests/utils/matchers/ErrorPattern.java new file mode 100644 index 000000000..68e880347 --- /dev/null +++ b/infer/tests/utils/matchers/ErrorPattern.java @@ -0,0 +1,60 @@ +/* +* Copyright (c) 2015 - Facebook. +* All rights reserved. +*/ + +package utils.matchers; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +import utils.InferError; + +public class ErrorPattern { + + private final String errorType; + + private final Path errorFile; + + private final String errorMethod; + + public ErrorPattern(String type, String file, String method) { + errorType = type; + errorFile = Paths.get(file); + errorMethod = method; + } + + String getErrorType() { + return errorType; + } + + Path getErrorFile() { + return errorFile; + } + + String getErrorMethod() { + return errorMethod; + } + + public boolean match(InferError error) { + return error.matchType(errorType) + && error.matchMethod(errorMethod) + && error.matchFile(errorFile); + } + + public static List createPatterns(String type, String file, String[] methods) { + List patterns = new ArrayList<>(); + for (String method : methods) { + patterns.add(new ErrorPattern(type, file, method)); + } + return patterns; + } + + @Override + public String toString() { + return getErrorType() + " in file: " + getErrorFile() + ", method: " + getErrorMethod(); + } + +} diff --git a/infer/tests/utils/matchers/ResultContainsErrorInMethod.java b/infer/tests/utils/matchers/ResultContainsErrorInMethod.java new file mode 100644 index 000000000..16061a093 --- /dev/null +++ b/infer/tests/utils/matchers/ResultContainsErrorInMethod.java @@ -0,0 +1,61 @@ +package utils.matchers; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import utils.InferError; +import utils.InferResults; + +public class ResultContainsErrorInMethod extends BaseMatcher { + + private String errorType; + private Path errorFile; + private String errorMethod; + + public ResultContainsErrorInMethod(String type, String file, String method) { + this.errorType = type; + this.errorFile = Paths.get(file); + this.errorMethod = method; + } + + @Override + public boolean matches(Object o) { + InferResults results = (InferResults) o; + for (InferError foundError : results.getErrors()) { + if (foundError.matchType(this.errorType) + && foundError.matchMethod(this.errorMethod) + && foundError.matchFile(this.errorFile)) { + return true; + } + } + return false; + } + + @Override + public void describeTo(Description description) { + description.appendText( + this.errorType + " error in file: " + this.errorFile + + ", method: " + this.errorMethod); + } + + @Override + public void describeMismatch(Object item, Description description) { + InferResults results = (InferResults) item; + String resultsString = ""; + for (InferError error : results.getErrors()) { + resultsString = resultsString + "\n\t" + error; + } + description.appendText( + "Found errors: \n" + resultsString + + "\nwith:\n" + results.inferCmdToString()); + } + + public static Matcher contains(String type, String file, String method) { + return new ResultContainsErrorInMethod(type, file, method); + } + +} diff --git a/infer/tests/utils/matchers/ResultContainsErrorNoFilename.java b/infer/tests/utils/matchers/ResultContainsErrorNoFilename.java new file mode 100644 index 000000000..5b5173472 --- /dev/null +++ b/infer/tests/utils/matchers/ResultContainsErrorNoFilename.java @@ -0,0 +1,54 @@ +package utils.matchers; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; + +import utils.InferError; +import utils.InferResults; + +public class ResultContainsErrorNoFilename extends BaseMatcher { + + private String errorType; + private String errorMethod; + + public ResultContainsErrorNoFilename(String type, String method) { + this.errorType = type; + this.errorMethod = method; + } + + @Override + public boolean matches(Object o) { + InferResults results = (InferResults) o; + for (InferError foundError : results.getErrors()) { + if (foundError.matchType(this.errorType) + && foundError.matchMethod(this.errorMethod)) { + return true; + } + } + return false; + } + + @Override + public void describeTo(Description description) { + description.appendText( + this.errorType + " error in " + this.errorMethod); + } + + @Override + public void describeMismatch(Object item, Description description) { + InferResults results = (InferResults) item; + String resultsString = ""; + for (InferError error : results.getErrors()) { + resultsString = resultsString + "\n\t" + error; + } + description.appendText( + "Found errors: \n" + resultsString + + "\nwith:\n" + results.inferCmdToString()); + } + + public static Matcher contains(String type, String method) { + return new ResultContainsErrorNoFilename(type, method); + } + +} diff --git a/infer/tests/utils/matchers/ResultContainsExactly.java b/infer/tests/utils/matchers/ResultContainsExactly.java new file mode 100644 index 000000000..fc609de74 --- /dev/null +++ b/infer/tests/utils/matchers/ResultContainsExactly.java @@ -0,0 +1,59 @@ +// Copyright (c) 2015-Present Facebook. All rights reserved. + +package utils.matchers; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; + +import java.util.ArrayList; +import java.util.List; + +import utils.InferResults; + +public class ResultContainsExactly extends BaseMatcher { + + Matcher containsAll; + Matcher containsOnly; + + private ResultContainsExactly(List patterns) { + containsAll = ResultContainsTheseErrors.contains(patterns); + containsOnly = ResultContainsOnlyTheseErrors.containsOnly(patterns); + } + + @Override + public boolean matches(Object o) { + return containsAll.matches(o) && containsOnly.matches(o); + } + + @Override + public void describeTo(Description description) { + containsAll.describeTo(description); + } + + @Override + public void describeMismatch(Object item, Description description) { + if (!containsAll.matches(item)) { + containsAll.describeMismatch(item, description); + } + if (!containsOnly.matches(item)) { + containsOnly.describeMismatch(item, description); + } + } + + public static Matcher containsExactly(List patterns) { + return new ResultContainsExactly(patterns); + } + + public static Matcher containsExactly( + String type, + String file, + String[] methods) { + ArrayList patterns = new ArrayList<>(); + for (String method : methods) { + patterns.add(new ErrorPattern(type, file, method)); + } + return new ResultContainsExactly(patterns); + } + +} diff --git a/infer/tests/utils/matchers/ResultContainsLineNumbers.java b/infer/tests/utils/matchers/ResultContainsLineNumbers.java new file mode 100644 index 000000000..d1589f961 --- /dev/null +++ b/infer/tests/utils/matchers/ResultContainsLineNumbers.java @@ -0,0 +1,64 @@ +package utils.matchers; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; + +import java.util.Arrays; +import java.util.Vector; + +import utils.InferError; +import utils.InferResults; + +public class ResultContainsLineNumbers extends BaseMatcher { + + int[] lines; + + public ResultContainsLineNumbers(int[] lines) { + this.lines = lines; + } + + @Override + public boolean matches(Object o) { + InferResults results = (InferResults) o; + boolean allContained = true; + for (int line : lines) { + boolean isContained = false; + for (InferError error : results.getErrors()) { + isContained = isContained || line == error.getErrorLine(); + } + allContained = allContained && isContained; + } + return allContained; + } + + @Override + public void describeTo(Description description) { + description.appendText("Correct line numbers in the error report."); + } + + @Override + public void describeMismatch(Object item, Description description) { + InferResults results = (InferResults) item; + String linesString = Arrays.toString(lines); + String reportedLinesString = Arrays.toString(findLineNumbersInReport(results)); + description.appendText( + "Infer did not report an error in the following lines: " + linesString + + ". \n \t Reported lines are: " + reportedLinesString + + "\n" + results.inferCmdToString()); + } + + public static Matcher containsLines(int[] lines) { + return new ResultContainsLineNumbers(lines); + } + + private int[] findLineNumbersInReport(InferResults results) { + Vector errors = results.getErrors(); + int nErrors = errors.size(); + int[] lines = new int[nErrors]; + for (int i = 0; i < nErrors; i++) { + lines[i] = errors.get(i).getErrorLine(); + } + return lines; + } +} diff --git a/infer/tests/utils/matchers/ResultContainsNoErrorInMethod.java b/infer/tests/utils/matchers/ResultContainsNoErrorInMethod.java new file mode 100644 index 000000000..25ad662c9 --- /dev/null +++ b/infer/tests/utils/matchers/ResultContainsNoErrorInMethod.java @@ -0,0 +1,48 @@ +package utils.matchers; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; + +import utils.InferError; +import utils.InferResults; + +public class ResultContainsNoErrorInMethod extends BaseMatcher { + + ErrorPattern pattern; + + public ResultContainsNoErrorInMethod(String type, String file, String method) { + pattern = new ErrorPattern(type, file, method); + } + + @Override + public boolean matches(Object o) { + InferResults results = (InferResults) o; + for (InferError foundError : results.getErrors()) { + if (pattern.match(foundError)) { + return false; + } + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText( + "No " + pattern.getErrorType() + " error in " + pattern.getErrorMethod()); + } + + @Override + public void describeMismatch(Object item, Description description) { + InferResults results = (InferResults) item; + description.appendText( + pattern.getErrorType() + " error found in " + + pattern.getErrorMethod() + " in the results of Infer.\n" + + results.inferCmdToString()); + } + + public static Matcher doesNotContain(String type, String file, String method) { + return new ResultContainsNoErrorInMethod(type, file, method); + } + +} diff --git a/infer/tests/utils/matchers/ResultContainsNumberOfErrorsInMethod.java b/infer/tests/utils/matchers/ResultContainsNumberOfErrorsInMethod.java new file mode 100644 index 000000000..ce4417363 --- /dev/null +++ b/infer/tests/utils/matchers/ResultContainsNumberOfErrorsInMethod.java @@ -0,0 +1,62 @@ +package utils.matchers; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; + +import utils.InferError; +import utils.InferResults; + +public class ResultContainsNumberOfErrorsInMethod extends BaseMatcher { + + private final ErrorPattern pattern; + private final int numberOfErrors; + int actualNumberErrors; + + public ResultContainsNumberOfErrorsInMethod( + String type, + String file, + String method, + int numberOfErrors) { + this.pattern = new ErrorPattern(type, file, method); + this.numberOfErrors = numberOfErrors; + this.actualNumberErrors = 0; + } + + @Override + public boolean matches(Object o) { + InferResults results = (InferResults) o; + for (InferError error : results.getErrors()) { + if (pattern.match(error)) { + actualNumberErrors++; + } + } + return (actualNumberErrors == numberOfErrors); + } + + @Override + public void describeTo(Description description) { + description.appendText( + numberOfErrors + " " + pattern.getErrorType() + + " errors in file: " + pattern.getErrorFile() + + ", method: " + pattern.getErrorMethod()); + } + + @Override + public void describeMismatch(Object item, Description description) { + InferResults results = (InferResults) item; + description.appendText( + actualNumberErrors + " " + pattern.getErrorType() + + " errors found in " + pattern.getErrorMethod() + " in the results of Infer." + + "\n" + results.inferCmdToString()); + } + + public static Matcher containsNumberOfErrors( + String type, + String file, + String method, + int numberOfErrors) { + return new ResultContainsNumberOfErrorsInMethod(type, file, method, numberOfErrors); + } + +} diff --git a/infer/tests/utils/matchers/ResultContainsOnlyTheseErrors.java b/infer/tests/utils/matchers/ResultContainsOnlyTheseErrors.java new file mode 100644 index 000000000..f560d2fcd --- /dev/null +++ b/infer/tests/utils/matchers/ResultContainsOnlyTheseErrors.java @@ -0,0 +1,78 @@ +package utils.matchers; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; + +import java.util.ArrayList; +import java.util.List; + +import utils.InferError; +import utils.InferResults; + +public class ResultContainsOnlyTheseErrors extends BaseMatcher { + + private List patterns; + + public ResultContainsOnlyTheseErrors(List patterns) { + this.patterns = patterns; + } + + private boolean mathTypeAndAnyMethod(InferError e) { + for (ErrorPattern pattern : patterns) { + if (pattern.match(e)) { + return true; + } + } + return false; + } + + @Override + public boolean matches(Object o) { + InferResults results = (InferResults) o; + for (InferError error : results.getErrors()) { + if (!mathTypeAndAnyMethod(error)) { + return false; + } + } + return true; + } + + @Override + public void describeTo(Description description) { + String errorsString = ""; + for (ErrorPattern pattern : patterns) { + errorsString = + errorsString + "\t" + pattern.toString() + "\n"; + } + description.appendText("Only these errors: \n" + errorsString); + } + + @Override + public void describeMismatch(Object item, Description description) { + InferResults results = (InferResults) item; + String unexpectedErrors = ""; + + for (InferError error : results.getErrors()) { + if (!mathTypeAndAnyMethod(error)) { + unexpectedErrors = unexpectedErrors + "\t" + error + "\n"; + } + } + description.appendText( + "Infer reported the following unexpected errors: \n" + + unexpectedErrors + "\n" + results.inferCmdToString()); + } + + public static Matcher containsOnly(String type, String file, String[] methods) { + ArrayList patterns = new ArrayList<>(); + for (String method : methods) { + patterns.add(new ErrorPattern(type, file, method)); + } + return new ResultContainsOnlyTheseErrors(patterns); + } + + public static Matcher containsOnly(List patterns) { + return new ResultContainsOnlyTheseErrors(patterns); + } + +} diff --git a/infer/tests/utils/matchers/ResultContainsTheseErrors.java b/infer/tests/utils/matchers/ResultContainsTheseErrors.java new file mode 100644 index 000000000..e1c468e4c --- /dev/null +++ b/infer/tests/utils/matchers/ResultContainsTheseErrors.java @@ -0,0 +1,80 @@ +// Copyright (c) 2015-Present Facebook. All rights reserved. + +package utils.matchers; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; + +import java.util.ArrayList; +import java.util.List; + +import utils.InferError; +import utils.InferResults; + +public class ResultContainsTheseErrors extends BaseMatcher { + + private List patterns; + + private ResultContainsTheseErrors(List patterns) { + this.patterns = patterns; + } + + private boolean patternFound(ErrorPattern pattern, InferResults results) { + for (InferError error : results.getErrors()) { + if (pattern.match(error)) { + return true; + } + } + return false; + } + + @Override + public boolean matches(Object o) { + InferResults results = (InferResults) o; + for (ErrorPattern pattern : patterns) { + if (!patternFound(pattern, results)) { + return false; + } + } + return true; + } + + @Override + public void describeTo(Description description) { + String errorsString = ""; + for (ErrorPattern pattern : patterns) { + errorsString += "\t" + pattern.toString() + "\n"; + } + description.appendText("Expecting these errors: \n" + errorsString); + } + + @Override + public void describeMismatch(Object item, Description description) { + + InferResults results = (InferResults) item; + String expectedErrors = ""; + + for (ErrorPattern pattern : patterns) { + if (!patternFound(pattern, results)) { + expectedErrors += "\t" + pattern.toString() + "\n"; + } + } + description.appendText( + "The following errors were not reported: \n" + expectedErrors + "\n" + "but found: " + + results.toString() + "\n"); + } + + public static Matcher contains(List patterns) { + return new ResultContainsTheseErrors(patterns); + } + + public static Matcher contains(String type, String file, String[] methods) { + ArrayList patterns = new ArrayList<>(); + for (String method : methods) { + patterns.add(new ErrorPattern(type, file, method)); + } + return new ResultContainsTheseErrors(patterns); + } + +} diff --git a/infer/tests/utils/matchers/ResultContainsZeroErrorsInMethod.java b/infer/tests/utils/matchers/ResultContainsZeroErrorsInMethod.java new file mode 100644 index 000000000..beaaae44d --- /dev/null +++ b/infer/tests/utils/matchers/ResultContainsZeroErrorsInMethod.java @@ -0,0 +1,50 @@ +package utils.matchers; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; + +import utils.InferError; +import utils.InferResults; + +public class ResultContainsZeroErrorsInMethod extends BaseMatcher { + + String fileName; + String methodName; + int actualNumberErrors; + + public ResultContainsZeroErrorsInMethod(String fileName, String methodName) { + this.fileName = fileName; + this.methodName = methodName; + } + + @Override + public boolean matches(Object o) { + InferResults results = (InferResults) o; + for (InferError error : results.getErrors()) { + if (fileName.equals(error.getErrorFile()) && + methodName.equals(error.getErrorMethod())) { + actualNumberErrors++; + } + } + return actualNumberErrors == 0; + } + + @Override + public void describeTo(Description description) { + description.appendText(actualNumberErrors + " errors found in method." + methodName); + } + + @Override + public void describeMismatch(Object item, Description description) { + InferResults results = (InferResults) item; + description.appendText( + actualNumberErrors + " errors found in " + methodName + + " in the results of Infer, but expected 0." + "\n" + results.inferCmdToString()); + } + + public static Matcher containsZeroErrors(String fileName, String methodName) { + return new ResultContainsZeroErrorsInMethod(fileName, methodName); + } + +} diff --git a/scripts/check_clang_plugin_version.sh b/scripts/check_clang_plugin_version.sh new file mode 100755 index 000000000..c0d29315c --- /dev/null +++ b/scripts/check_clang_plugin_version.sh @@ -0,0 +1,55 @@ +#!/bin/bash +# +# Copyright (c) 2014 - Facebook. All rights reserved. +# + +set -e + +parent=$(dirname "$0") +SCRIPT_DIR=$( cd "$parent" && pwd ) +README_MSG="To get and compile facebook-clang-plugins, please consult the README file." + +function echoerr { + echo $@ >&2 +} + +# Counting the passed arguments +if [ "$#" -lt 1 ]; then + echoerr "Error: not enough arguments." + echoerr "USAGE: $0 " + echoerr "$README_MSG" + exit 1 +fi + +REPO_DIR=$1 + +# Check that the directory passed in input exists +([ -d "$REPO_DIR" ] && [ -d "$REPO_DIR/libtooling" ]) || { + echoerr "$REPO_DIR does not exist or is invalid."; + echoerr "Please check that the path to facebook-clang-plugins is correct."; + echoerr "$README_MSG"; + exit 1; +} + +VERSION_FILE="$SCRIPT_DIR/../dependencies/clang-plugin/clang-plugin-version.config" + +cd $REPO_DIR +[ ! -d ".git" ] && { + echoerr "SKIPPING the facebook-clang-plugins version check since"; + echoerr "$REPO_DIR is NOT a Git repository"; + exit 0; +} + +echoerr "Checking that the revision of facebook-clang-plugins is correct..." +GIT_CURRENT_REVISION=$(git log --pretty=format:'%H' -n 1) +GIT_EXPECTED_REVISION=$(cat "$VERSION_FILE") + +echoerr "Current revision is $GIT_CURRENT_REVISION" +echoerr "Expected revision is $GIT_EXPECTED_REVISION" + +if [ "$GIT_CURRENT_REVISION" != "$GIT_EXPECTED_REVISION" ]; then + echoerr "Revisions mismatching. Please, run update-fcp.sh to get the revision needed by Infer." + exit 1 +else + echoerr "Clang plugin is up to date! Continue..." +fi diff --git a/scripts/create_binaries.sh b/scripts/create_binaries.sh new file mode 100755 index 000000000..5f74b8f37 --- /dev/null +++ b/scripts/create_binaries.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +# Copyright 2013 - present Facebook. +# All Rights Reserved. + +set -x +set -e + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +INFER_DIR=$SCRIPT_DIR/../infer +CLANG_PLUGIN_DIR=$SCRIPT_DIR/../../facebook-clang-plugin +platform=`uname` + +# Build Infer +cd $INFER_DIR && make + +VERSION=$($INFER_DIR/bin/infer --version 2>&1 | head -1 | awk '{print $3}') +if [ $platform == 'Darwin' ]; then + BINARY_DIR=infer-osx-$VERSION +else + BINARY_DIR=infer-linux64-$VERSION +fi +BINARY_TARBALL=$BINARY_DIR.tar.xz + +# Build package. +PKG_DIR=$SCRIPT_DIR/../$BINARY_DIR + +# Start with infer +mkdir -p $PKG_DIR/infer/infer/{annotations,bin,lib} +mkdir -p $PKG_DIR/infer/examples +mkdir -p $PKG_DIR/infer/scripts +cp -r $INFER_DIR/annotations/* $PKG_DIR/infer/infer/annotations +cp -r $INFER_DIR/bin/* $PKG_DIR/infer/infer/bin +cp -r $INFER_DIR/lib/* $PKG_DIR/infer/infer/lib +cp -r $INFER_DIR/../examples/* $PKG_DIR/infer/examples +cp $INFER_DIR/../{CONTRIBUTING.md,LICENSE,PATENTS,README.md,INSTALL.md} $PKG_DIR/infer/ +cp -r $INFER_DIR/../scripts/* $PKG_DIR/infer/scripts +# don't include pyc files into the release +find $PKG_DIR -name "*.pyc" | xargs rm + +# Add facebook-clang-plugin +PKG_PLUGIN_DIR=$PKG_DIR/facebook-clang-plugin +mkdir -p $PKG_PLUGIN_DIR/clang/{bin,lib} +mkdir -p $PKG_PLUGIN_DIR/libtooling/build +cp $CLANG_PLUGIN_DIR/{CONTRIBUTING.md,LICENSE,LLVM-LICENSE,PATENTS,README.md} $PKG_PLUGIN_DIR +cp -r $CLANG_PLUGIN_DIR/clang/bin/clang* $PKG_PLUGIN_DIR/clang/bin +cp -r $CLANG_PLUGIN_DIR/clang/lib/* $PKG_PLUGIN_DIR/clang/lib +rm $PKG_PLUGIN_DIR/clang/lib/*.a + +cp -r $CLANG_PLUGIN_DIR/libtooling/build/* $PKG_PLUGIN_DIR/libtooling/build + +cd $PKG_DIR/.. && tar cJf $BINARY_TARBALL $BINARY_DIR + +# Cleanup. +rm -rf $PKG_DIR + +# vim: sw=2 ts=2 et: diff --git a/scripts/test.sh b/scripts/test.sh new file mode 100755 index 000000000..d4211f3e1 --- /dev/null +++ b/scripts/test.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +# Copyright 2013 - present Facebook. +# All Rights Reserved. + +set -e +set -x + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +INFER_DIR="$SCRIPT_DIR/.." +INFER_BIN="$INFER_DIR/infer/bin" + +if [ "$1" == "--xml" ]; then + XML="--xml test.xml" +fi + +platform=`uname` + +TARGETS_TO_COMPILE=() +TARGETS_TO_TEST=() +if [ -e "$INFER_BIN/InferJava" ]; then + TARGETS_TO_COMPILE+=('java') + TARGETS_TO_TEST+=('java') +fi + +if [ -e "$INFER_BIN/InferClang" ]; then + TARGETS_TO_COMPILE=('clang') + TARGETS_TO_TEST+=('c' 'cpp') + if [ $platform == 'Darwin' ]; then + TARGETS_TO_TEST+=('objc') + fi +fi + +# We must collect at least one target, or Infer must be recompiled +([ -z "$TARGETS_TO_TEST" ] || [ -z "$TARGETS_TO_COMPILE" ]) \ + && echo 'Infer is not compiled properly. Run make -C infer clean && make -C infer' \ + && exit 1 + +cd $SCRIPT_DIR/.. +make -C infer ${TARGETS_TO_COMPILE[@]} + +# Must clean in order to force running the tests with the latest version +# of infer. There is no dependency between buck targets and infer. +buck clean +rm -rf test.xml + +buck test ${TARGETS_TO_TEST[@]} $XML || exit 1 diff --git a/update-fcp.sh b/update-fcp.sh new file mode 100755 index 000000000..e50ba409a --- /dev/null +++ b/update-fcp.sh @@ -0,0 +1,25 @@ +#!/bin/bash +set -x +set -e + +# This script fetches the revision of the 'facebook-clang-plugins' +# required by Infer. + +INFER_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +PLUGIN_REPO=https://github.com/facebook/facebook-clang-plugins +PLUGIN_DIR="$INFER_ROOT/../facebook-clang-plugin" +VERSION_FILE="$INFER_ROOT/dependencies/clang-plugin/clang-plugin-version.config" + +# check if the repo is already in place +if [ ! -e "$PLUGIN_DIR" ]; then + echo "$PLUGIN_DIR not found, cloning..." + git $GIT_OPTIONS clone $PLUGIN_REPO "$PLUGIN_DIR" +fi + +# update revision if needed +echo "Checking out the right version of the clang plugin..." +pushd $PLUGIN_DIR +git checkout master +git $GIT_OPTIONS pull +git checkout $(cat "$VERSION_FILE") +popd