This is an automated email from the git hooks/post-receive script. ebourg-guest pushed a commit to branch master in repository libcofoja-java.
commit 84c12319d68cb5d38282c1a46b9dfc5f7b236a01 Author: Emmanuel Bourg <[email protected]> Date: Tue Oct 4 10:21:22 2016 +0200 New upstream version 1.3 --- README | 136 ------ README.md | 486 +++++++++++++++++++++ build.xml | 188 +++++--- default.properties | 18 +- ivy.xml | 18 + src/com/google/java/contract/ContractImport.java | 37 ++ .../java/contract/core/agent/ContractAnalyzer.java | 13 +- .../core/agent/ContractClassFileTransformer.java | 6 +- .../core/agent/ContractFixingClassAdapter.java | 35 +- .../java/contract/core/agent/ContractHandle.java | 11 +- .../core/agent/ContractMethodSignatures.java | 2 + .../contract/core/agent/HelperClassAdapter.java | 4 +- .../core/agent/LineNumberingClassAdapter.java | 2 +- .../core/agent/LineNumberingMethodAdapter.java | 2 +- .../core/agent/SpecificationClassAdapter.java | 15 +- .../core/agent/SpecificationMethodAdapter.java | 17 +- .../contract/core/apt/AbstractTypeBuilder.java | 27 +- .../contract/core/apt/AnnotationProcessor.java | 114 +++-- .../java/contract/core/apt/ContractCreation.java | 2 + .../contract/core/apt/ContractCreationTrait.java | 5 +- .../core/apt/ContractExpressionTransformer.java | 4 - .../contract/core/apt/ContractJavaCompiler.java | 9 +- .../java/contract/core/apt/ContractWriter.java | 10 +- .../contract/core/apt/MethodContractCreator.java | 24 +- .../contract/core/apt/SourceDependencyParser.java | 9 +- .../core/model/ContractAnnotationModel.java | 9 +- .../java/contract/core/model/ContractKind.java | 8 + .../java/contract/core/model/ElementModel.java | 6 +- .../google/java/contract/core/util/JavaUtils.java | 5 +- .../google/java/contract/core/util/PatternMap.java | 10 +- .../java/contract/core/util/SyntheticJavaFile.java | 9 +- .../java/contract/tests/ConstantContracts.java | 24 + .../java/contract/tests/ConstantContractsTest.java | 30 ++ .../java/contract/tests/ConstructorTest.java | 10 +- .../google/java/contract/tests/GenericsTest.java | 4 +- .../java/contract/tests/Java8StreamTest.java | 97 ++++ 36 files changed, 1078 insertions(+), 328 deletions(-) diff --git a/README b/README deleted file mode 100644 index 29adefa..0000000 --- a/README +++ /dev/null @@ -1,136 +0,0 @@ -Contracts for Java is a contract programming framework for Java, which -uses annotation processing and bytecode instrumentation to provide -run-time checking. - - -1. DEPENDENCIES - -Contracts fo Java requires Java version 6 for annotation processing -and bytecode instrumentation. The bytecode rewriter depends on the ASM -bytecode manipulation library, version 4.x or higher. - -Since the Java agent rewrites bytecode in order to inject contracts -into the loaded code, the ASM library needs to be available in the -class path at run time if contract checking is enforced through the -agent. Pre-contracted class files do not need the ASM library or the -agent to run with contracts enabled. - - -2. CONFIGURATION - -In order for the build script to run properly, you must at least -specify the correct path to Cofoja's dependencies in your local build -properties file: - - ./local.properties - -This file does not exist by default; you can either create it or start -from a copy of the default configuration file: - - ./default.properties - -That file also contains all user-settable properties with their -descriptions and default values. Once you're done, set the following -property to true to let Ant run: - - configured=true - - -3. QUICK START - -To build a JAR file containing all Cofoja classes, run: - - ant dist - -The JAR file will be located at: - - ./dist/lib/cofoja-<version>.jar - -It can be used both as a Java agent and annotation processor and -should be added to your class path. - -To compile code with contract annotations, run: - - javac -processor com.google.java.contract.core.apt.AnnotationProcessor <someclass>.java - -To execute code compiled with contract checking enabled, make sure the -generated files (additional .class and .contracts files) are in your -class path, and run: - - java -javaagent:path/to/cofoja-<version>.jar <someclass> - - -4. ADVANCED BUILD - -Contracts for Java is annotated with its own contracts, which can be -compiled, tested and bundled into the result JAR file so it checks its -own contracts when compiling and checking your program's contracts! - -Please note that running such a build will necessarily be slower than -running an unchecked version of Contracts for Java, but is a great way -for you to contribute to the project by helping exercise its own -capabilities while using it. - -To build a contracted version of Contracts for Java, you need to have -a Cofoja JAR file ready first (see previous section; or you could -reuse one you've built with a previous run of this bootstrap -process). Copy the JAR file to: - - ./build/bootstrap.jar - -Then run: - - ant bootstrap - -Once the contracted version is complete, you can run the test suite -with: - - ant test - -Aside from self-contracted builds, Cofoja JAR files bundled with ASM -library classes can also be produced, for the sake of easier -distribution: - - ant -Dasmjar=path/to/asm-all-<version>.jar dist - - -5. USAGE - -Contracts for Java consists of an annotation processor, an -instrumentation agent, as well as an offline bytecode rewriter. The -annotation processor compiles annotations into separate contract class -files. The instrumentation agent weaves these contract files with the -real classes before they are loaded into the JVM. Alternatively, the -offline bytecode rewriter can be used to produce pre-weaved class -files that can be directly deployed without any Cofoja dependency. - -The following instructions assume that you have the Cofoja and ASM JAR -files in your class path. - -The annotation processor's entry point is (for use with the -processor -javac option): - - com.google.java.contract.core.apt.AnnotationProcessor - -The Java agent can be invoked from the compiled JAR file (for use with -the -javaagent java option): - - ./dist/cofoja-<version>.jar - -The offline instrumenter can be run with: - - java -Dcom.google.java.contract.classoutput=<outdir> \ - com.google.java.contract.core.agent.PreMain <someclass>.class - -Please refer to the official online documentation for more -information: - - http://code.google.com/p/cofoja/wiki/QuickReference - - -6. BUGS - -Contracts for Java is a very young project. Please help us make it -better by reporting bugs and posting patches at: - - http://code.google.com/p/cofoja/issues/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..d100aec --- /dev/null +++ b/README.md @@ -0,0 +1,486 @@ +# Cofoja + +Contracts for Java, or Cofoja for short, is a contract programming +framework and test tool for Java, which uses annotation processing and +bytecode instrumentation to provide run-time checking. (In particular, +this is not a static analysis tool.) + +I originally wrote Cofoja when interning at Google in 2010, based on +prior work on Modern Jass. It was open-sourced in early 2011, and has +since been maintained by myself, with the help of a small community. + +**Contents** + +* [Download](#download) +* [Build](#build) + * [Dependencies](#dependencies) + * [Configuration](#configuration) + * [Ant targets](#ant-targets) +* [Usage](#usage) + * [Annotations](#annotations) + * [Method contracts](#method-contracts) + * [Class and interface contracts](#class-and-interface-contracts) + * [Inheritance](#inheritance) + * [Other features](#other-features) + * [Import annotations](#import-annotations) + * [Java expressions](#java-expressions) + * [Constructors](#constructors) + * [Exception handling](#exception-handling) +* [Invocation](#invocation) +* [Run-time contract configuration](#run-time-contract-configuration) + * [Selective contracts](#selective-contracts) + * [Blacklist](#blacklist) + * [Debug tracing](#debug-tracing) +* [Quick reference](#quick-reference) + * [Annotations](#annotations-1) + * [Keywords](#keywords) + * [Annotation processor options](#annotation-processor-options) + * [Java agent properties](#java-agent-properties) + * [Run-time contract configuration methods](#run-time-contract-configuration-methods) +* [Help](#help) + + +## Download + +Pre-built JAR files are available on the GitHub release page: +https://github.com/nhatminhle/cofoja/releases + +Each release comes in four flavours. `cofoja` is the basic version, +which contains every feature Cofoja has to offer. `+asm` builds are +bundled with a compatible version of the ASM library. `+contracts` are +debug builds that include self-check contracts on the annotation +processor and library themselves. + +If you are confused about which version to choose, pick either the +plain `cofoja` JAR file if you already have the ASM library installed, +or plan to install it by other means, or the `cofoja+asm` JAR file if +you want a single JAR file that works out of the box. + +The class files are compiled for Java 6. Cofoja itself depends on +features not available to older versions of Java. + + +## Build + +### Dependencies + +Building Cofoja requires: + +* JDK 6 or higher, for annotation processing and bytecode + instrumentation. +* ASM 5.x (or higher versions with ASM5-compatible API), for bytecode + instrumentation. http://asm.ow2.org +* JUnit 3.8 or 4.x if you want to run tests. +* Ant 1.9.1 or higher for the build script. + +If Ivy is installed, calling the `resolve` Ant target retrieves the +dependencies automatically. + +Alternatively, put JAR files from dependencies under the `lib` folder, +in the current directory. The directory does not exist by default and +must be created. + +In order to enable contract checking at run time, Cofoja binaries and +dependencies are needed. Normal execution (with contracts disabled) +does not require Cofoja or any of its dependencies. + + +### Configuration + +The build script reads properties from the user-provided +`local.properties` file. See `default.properties` for a list of +configuration options to adjust to fit your build environment. Most +importantly, you may want to adjust the path to the `tools.jar` +(`classes.jar` on Mac OS) file, which should be provided by the JDK. + + +### Ant targets + +The default target, `dist`, builds all four release JAR files. To +include only those without self-contracts, execute the `nobootstrap` +target. + +By default, the produced JAR files are placed in the `dist` folder, in +the current directory. + +You should run the `test` target to check that your build of Cofoja +behaves (somewhat) as expected. + + +## Usage + +Cofoja supports a contract model similar to that of Eiffel, with added +support for a few Java-specific things, such as exceptions. + +Some general understanding of contract programming (also called design +by contract) is required to use Cofoja effectively. If you have no +idea what this is, Wikipedia may be a good place to start: +http://en.wikipedia.org/wiki/Design_by_Contract + + +### Annotations + +In Cofoja, contracts are written as Java code within quoted strings, +embedded in annotations. E.g., `@Requires("x < 100")` states that `x` +must be less than 100. Any Java expression, except anonymous classes, +may be used, provided the string is properly escaped. + +An annotation binds a contract to a code element: either a method or +a type. Cofoja defines three main annotation types, which live in the +`com.google.java.contract` package: + +* `@Requires` for method preconditions; +* `@Ensures` for method postconditions; +* `@Invariant` for class and interface invariants; + +Contract annotations work on both classes and interfaces. For +convenience, arrays of quoted expressions are accepted, and behave as +if the components were separated by `&&`. E.g., `@Ensures({ "x > 0", +"x < 50" })` is equivalent to `@Ensures("x > 0 && x < 50")`. + + +### Method contracts + +A method may have preconditions and postconditions attached to +it. Together, they specify the contract between caller and callee: if +the precondition is satisfied on entry of the method, then the caller +may assume the postcondition on exit. The precondition is what the +callee demands of the caller, and in return the caller expects the +postcondition to hold after the call. + +As an example, consider the following specification of the square root +function, which states that for any non-negative double `x` given, +`sqrt` will return a non-negative result. + +```java +@Requires("x >= 0") +@Ensures("result >= 0") +static double sqrt(double x); +``` + +As shown in this example, a precondition may access parameter values; +in fact, preconditions and postconditions are evaluated in the context +of the method they are bound to. More precisely, each annotation +behaves as if it were a method, with the same arguments and in the +same scope as the qualified method. In terms of scoping, the previous +code is equivalent to the following: + +```java +static void sqrt_Requires(double x) { + assert x >= 0; +} +static void sqrt_Ensures(double result) { + assert result >= 0; +} +static double sqrt(double x); +``` + +In addition, postconditions may contain a few extensions: + +* As we have seen, they may refer to the returned value, using the + `result` keyword. +* Within a postcondition, `old` is a keyword which is followed by + a single parenthesized expression, such that `old (x)` evaluates to + the value of `x` on entry of the method invocation. +* An `old` expression is evaluated in the same context as + preconditions and has access to the same things, including parameter + values. + +Given this, a more complete specification of `sqrt` might be: + +```java +@Requires("x >= 0") +@Ensures({ "result >= 0", "Math.abs(x - result * result) < EPS" }) +static double sqrt(double x); +``` + +At run time, when contracts are enabled, preconditions and +postconditions translate to checks on entry and exit, respectively, of +the method. A failure results in a `PreconditionError` or +`PostconditionError` being thrown, depending on the origin: failure to +meet a precondition means that the method was called incorrectly, +whereas an unsatisfied postcondition points to a bug in the +implementation of the method itself. + + +### Class and interface contracts + +A class or interface may have associated invariants. Instead of +specifying a contract between a caller and a callee, those invariants +describe the state of a valid object of the qualified type. Calling +methods on an object may cause it to change; invariants guarantee that +after any such change, the object remains in a consistent state. + +Of course, internal operations are allowed to muck around and +temporarily invalidate invariants to do their job, but they agree to +eventually put everything back into their proper places. Intuitively, +any operation made against `this` is considered internal and does not +need to obey the invariants. Only method invocations on other +variables do. + +In Cofoja, when contracts are enabled, invariants are checked on entry +and exit of method calls on objects not already in the call stack +(including `this`). Failure results in an `InvariantError` exception +and indicates that the guilty method has left the object in an +inconsistent state. + + +### Inheritance + +Contracts apply to all objects of the associated type, including any +instances of derived classes: all implementations of a contracted +interface must honor the interface contracts, all children of a class +must honor the contracts of their parent. + +In addition, derived types may refine those contracts by adding their +own preconditions, postconditions and invariants to the mix. However, +they cannot replace the inherited contracts, only augment them +according to the following subtyping rules. + +Preconditions may be weakened, i.e., methods may be overriden with +implementations that accept a wider range of inputs. Callers that +access the object through a superclass or interface need only +establish the parent contracts. In Cofoja, a method checks that either +its inherited *or* its own preconditions are satisfied. + +Postconditions may be strengthened, i.e., methods may be overriden +with implementations that produce a smaller range of outputs. Callers +that access the object through a superclass or interface expect at +least the parent contracts. In Cofoja, a method checks both its +inherited *and* its own postconditions. + +Invariants may be strengthened, i.e., classes and interfaces may be +derived to restrict the set of valid states. An object must qualify as +a consistent value of any of its superclasses or interfaces. In +Cofoja, a type asserts both its inherited *and* its own invariants. + + +### Other features + +#### Import annotations + +Previously, we have used `Math.abs` in our examples, which resides in +`java.lang`, and is thus available to any Java code. For other +symbols, we may need to import a class or package. Imports for +contracts are kept separately from those of the main file. The +`@ContractImport` annotation specifies names to be imported for use in +contract code within the associated type. It must be put on the +enclosing type and accepts an array of strings, each one containing an +import pattern, compatible with the `import` Java statement. + +#### Java expressions + +Contracts are made of Java expressions, with the addition of several +keywords. Cofoja uses the Java compiler from the standard tools +package and hence supports the same language as provided by that +compiler (which should usually be the same as that of the top-level +compiler). + +However, some expressions may generate bridges or other synthetic +methods in addition to their direct translations to bytecode. Such +artifacts need to be identified and handled specially by Cofoja. The +following features are currently known to require particular +processing: + +Feature | Supported | Description +------------------------- | --------- | ------------------------------- +Inner class accesses | Yes | May generate `access$` methods +Anonymous classes | No | Generate additional class files +Java 8 lambda expressions | Yes | Generate `lambda$` methods + +Since uses of synthetic methods follow no set rules, compilers other +than Javac may opt for different compilation strategies. At the +moment, no such incompatible scheme has been reported. + +#### Constructors + +With respect to Cofoja, constructors behave slightly differently from +other methods. They assert invariants, but only on exit, and may be +attached to preconditions and postconditions. However, a very +important distinction is that constructor preconditions are checked +*after* any call to a parent constructor. This is due to `super` calls +being necessarily the first instruction of any constructor call, +making it impossible to insert precondition checks before them. (This +is considered a known bug.) + +#### Exception handling + +When an exception is thrown from within a contracted method, normal +postconditions are not checked by Cofoja, as they may refer to +a result that does not exist. + +Instead, the `@ThrowEnsures` annotation is provided. It functions +similarly to `@Ensures` but accepts an alternating list of exception +type names to catch and matching postcondition expressions, as quoted +strings. Within those exceptional postconditions, the keyword `signal` +refers to the exception object that has been thrown. + +A possible use of `@ThrowEnsures` is to guarantee that exceptional +method exits do not inadvertently alter the state of the object. + +```java +class RestrictedInteger { + int x; + + @Ensures("x == y") + @ThrowEnsures({ "IllegalArgumentException", "x == old (x)" }) + void setX(int y) throws IllegalArgumentException { + ... + } +} +``` + + +### Invocation + +Contracts for Java consists of an annotation processor, an +instrumentation agent, as well as an offline bytecode rewriter. The +annotation processor compiles annotations into separate contract class +files. The instrumentation agent weaves these contract files with the +real classes before they are loaded into the JVM. Alternatively, the +offline bytecode rewriter can be used to produce pre-weaved class +files that can be directly deployed without requiring the Java agent +or ASM library at run time. + +The pre-built JAR files contain both the Java agent and annotation +processor. The latter is named `AnnotationProcessor` (in the +`com.google.java.contract.core.apt` package); the agent can be loaded +directly by specifying the JAR file (e.g., as an argument to the +`-javaagent` argument). + +To execute code compiled with contract checking enabled, make sure the +generated files (additional `.class` and `.contracts` files) are in +your class path and enable the Cofoja JAR file as a Java agent. + +Or use the offline instrumenter, which lives in the `PreMain` class, +under the `com.google.java.contract.core.agent` package, and takes +paths to class files as command-line arguments. + + +### Run-time contract configuration + +#### Selective contracts + +When using the Java agent, contract evaluation can be enabled +selectively, similar to how assertions can be toggled on and off for +specific types. Whether contracts are checked on methods of a given +type is determined at load time and may not be changed afterwards. + +These settings are controlled through a user-defined configurator +object. As part of its early start-up procedure, the Java agent +attempts to create an instance of the configurator class, whose name +is provided through the `com.google.java.contract.configurator` JVM +property. It then calls the `configure` method on the newly created +object, passing it an argument of type `ContractEnvironment` (from +package `com.google.java.contract`). + +The `ContractEnvironment` object provides methods such as +`enablePreconditions` and `disablePreconditions`, to enable or disable +contracts selectively. Each method accepts an import-like string +pattern. In case of pattern overlap, the behavior specified by the +last matching call is retained. + +Disabling contracts for a specific type does not prevent its contracts +from being inherited and checked correctly for the derived types. + +#### Blacklist + +The blacklist is controlled through the `ContractEnvironment` methods +`ignore` and `unignore`, which also take patterns, similarly to the +contract selection methods. + +Ignoring a pattern is different from disabling contracts for that +pattern. A blacklisted type is not be examined at all by the Java +agent; in particular, it is not searched for contracts and its +bytecode is not modified in any way. It is assumed to contain no +contracts; thus, derived types inherit nothing from it. + +#### Debug tracing + +For debug purposes, Cofoja may be instructed to print a trace to +stderr of contract that are evaluated. Compilation support for tracing +is provided by the `com.google.java.contract.debug` annotation +processor flag. The actual trace is obtained by running the contracted +program with the `com.google.java.contract.log.contract` JVM property +set to `true`. + + +### Quick reference + +#### Annotations + +All annotations reside in the `com.google.java.contract` package. + +Annotation | Checked on | Inheritance +--------------- | --------------------- | ----------- +`@Invariant` | Object entry and exit | And +`@Requires` | Entry | Or +`@Ensures` | Normal exit | And +`@ThrowEnsures` | Abnormal exit | And + +#### Keywords + +Keyword | May appear in | Description +-------- | --------------------------- | --------------------- +`old` | `@Ensures`, `@ThrowEnsures` | Value on method entry +`result` | `@Ensures` | Value to be returned +`signal` | `@ThrowEnsures` | Exception thrown + +#### Annotation processor options + +All option names reside in the `com.google.java.contract` name +space. Options that have a Javac counterpart are passed down to the +underlying Java compiler used to compile contract code. In most cases, +they should match those used to compile the main project. + +Option | Type | Javac option | Description +------------- | --------- | ------------- | ------------------------------------ +`classpath` | path | `-classpath` | Class path for contract code +`sourcepath` | path | `-sourcepath` | Where to find source files +`classoutput` | directory | `-d` | Where to put compiled contract files +`debug` | flag | | Enable run-time logging support +`dump` | directory | | Where to put generated source files + +Additionally, you may want to pass `-proc:only` (or equivalent) to the +Java compiler, so it only runs the annotation processor, which will +generate compiled contract files without producing the normal class +files. This is recommended for medium-to-large projects. + +#### Java agent properties + +All properties reside in the `com.google.java.contract` name space. + +Property | Type | Description +-------------- | ------- | ---------------------------------------------- +`configurator` | String | Configurator class name +`dump` | String | Where to dump instrumented class files +`log.contract` | Boolean | Print a trace of evaluated contracts to stderr + +`log.contract` requires contracts compiled with the `debug` annotation +processor option. + +#### Run-time contract configuration methods + +All methods live in `com.google.java.contract.ContractEnvironment`. + +Method | Description +----------------------- | --------------------------------------------------- +`enablePreconditions` | Check preconditions for all methods of class +`disablePreconditions` | Do not check preconditions for any method of class +`enablePostconditions` | Check postconditions for all methods of class +`disablePostconditions` | Do not check postconditions for any method of class +`enableInvariants` | Check invariants for class +`disableInvariants` | Do not check invariants for class +`ignore` | Do not search class for contracts +`unignore` | Search class for contracts + + +## Help + +Contracts for Java is a not-so-young project. Please help make it +better by reporting bugs at: +https://github.com/nhatminhle/cofoja/issues + +For general questions and help with using Cofoja, you can reach out to +the dedicated Google discussion group: +http://groups.google.com/group/cofoja diff --git a/build.xml b/build.xml index 9edd215..3410f53 100644 --- a/build.xml +++ b/build.xml @@ -1,15 +1,41 @@ -<project name="Cofoja" default="dist" basedir="."> +<project name="Cofoja" default="dist" basedir="." + xmlns:if="ant:if" + xmlns:unless="ant:unless" + xmlns:ivy="antlib:org.apache.ivy.ant"> <!-- Configurable properties. --> + <condition property="platform.suffix" value=".${platform}" else=""> + <isset property="platform" /> + </condition> <property file="local.properties" /> + <property file="local${platform.suffix}.properties" if:set="platform" /> <property file="default.properties" /> <!-- Private properties. --> + <condition property="java8"> + <or> + <and> + <isset property="rt.jar" /> + <available ignoresystemclasses="true" + classname="java.lang.invoke.LambdaMetafactory"> + <classpath location="${rt.jar}" /> + </available> + </and> + <and> + <not> + <isset property="rt.jar" /> + </not> + <available classname="java.lang.invoke.LambdaMetafactory" /> + </and> + </or> + </condition> + + <property name="lib.dir" location="lib" /> <property name="src.dir" location="src" /> <property name="test.dir" location="test" /> <property name="build.dir" location="build" /> - <property name="obj.dir" location="obj" /> - <property name="dist.dir" location="dist" /> + <property name="obj.dir" location="obj${platform.suffix}" /> + <property name="dist.dir" location="dist${platform.suffix}" /> <property name="manifest.path" value="${src.dir}/META-INF/MANIFEST.MF" /> @@ -19,21 +45,68 @@ value="com.google.java.contract.tests.Cofoja" /> <path id="base.class.path"> - <pathelement path="${asm.jar}" /> + <fileset dir="${lib.dir}"> + <include name="asm-*.jar" /> + </fileset> + </path> + + <path id="build.class.path"> + <path refid="base.class.path" /> <pathelement path="${tools.jar}" /> </path> <path id="test.class.path"> <path refid="base.class.path" /> - <pathelement path="${junit.jar}" /> + <fileset dir="${lib.dir}"> + <include name="*.jar" /> + </fileset> + </path> + + <path id="test1.class.path"> + <path refid="test.class.path" /> + <pathelement path="${obj.dir}/stage2" /> + </path> + + <path id="test2.class.path"> + <path refid="test.class.path" /> + <pathelement path="${obj.dir}/stage2" /> + <pathelement path="${obj.dir}/test" /> </path> <!-- Build macros. --> <presetdef name="ujavac"> - <javac encoding="utf-8" debug="${debug}" includeantruntime="false" /> + <javac encoding="utf-8" debug="${debug}" includeantruntime="false"> + <bootclasspath location="${rt.jar}" if:set="rt.jar" /> + </javac> </presetdef> + <macrodef name="cofojavac"> + <attribute name="srcdir" /> + <attribute name="destdir" /> + <attribute name="debug" default="false" /> + <attribute name="bootstrappath" default="" /> + <attribute name="classpathid" /> + <element name="contents" optional="true" implicit="true" /> + <sequential> + <path id="processor.path"> + <path refid="base.class.path" /> + <pathelement location="@{bootstrappath}" /> + </path> + <ujavac srcdir="@{srcdir}" destdir="@{destdir}" debug="@{debug}"> + <classpath refid="@{classpathid}" /> + <compilerarg value="-processorpath" unless:blank="@{bootstrappath}" /> + <compilerarg value="${toString:processor.path}" unless:blank="@{bootstrappath}" /> + <compilerarg value="-processor" /> + <compilerarg value="${apt.class}" /> + <compilerarg value="-Acom.google.java.contract.classpath=${toString:@{classpathid}}" /> + <compilerarg value="-Acom.google.java.contract.sourcepath=@{srcdir}" /> + <compilerarg value="-Acom.google.java.contract.classoutput=@{destdir}" /> + <contents /> + </ujavac> + </sequential> + </macrodef> + <macrodef name="checkjar"> <attribute name="label" /> <attribute name="property" /> @@ -85,7 +158,11 @@ provider="${apt.class}" /> <zipfileset includes="**/*.class" src="@{barejarfile}" /> - <zipfileset includes="**/*.class" src="${asm.jar}" /> + <zipfileset includes="**/*.class"> + <fileset dir="${lib.dir}"> + <include name="asm-*.jar" /> + </fileset> + </zipfileset> </jar> </sequential> </macrodef> @@ -96,33 +173,43 @@ <sequential> <mkdir dir="${obj.dir}/tmp@{stage}" /> <mkdir dir="${obj.dir}/stage@{stage}" /> - <ujavac srcdir="${src.dir}" destdir="${obj.dir}/tmp@{stage}" debug="true"> - <classpath refid="base.class.path" /> - <compilerarg value="-processorpath" /> - <compilerarg value="@{bootstrappath}:${asm.jar}" /> - <compilerarg value="-processor" /> - <compilerarg value="${apt.class}" /> - </ujavac> + <cofojavac srcdir="${src.dir}" destdir="${obj.dir}/tmp@{stage}" debug="true" + bootstrappath="@{bootstrappath}" classpathid="build.class.path" /> <cofojab srcdir="${obj.dir}/tmp@{stage}" destdir="${obj.dir}/stage@{stage}" /> </sequential> </macrodef> + <!-- Dependency management. --> + + <target name="resolve" description="retrieve dependencies with Ivy"> + <ivy:retrieve pattern="${lib.dir}/[artifact]-[revision].[ext]" + type="jar" conf="test" /> + </target> + + <target name="publish" depends="resolve,dist" + description="publish artifacts with Ivy"> + <ivy:publish resolver="local" pubrevision="${version}" + artifactspattern="dist/[artifact]-${cofoja.version}.[ext]" /> + </target> + <!-- Initialization. --> <target name="configure"> <echo message="Configuration" /> <echo message="-------------" /> - <checkjar label="ASM JAR: " property="asm.jar" /> - <checkjar label="JUnit JAR: " property="junit.jar" /> + <sequential if:set="rt.jar"> + <checkjar label="JRE RT JAR: " property="rt.jar" /> + </sequential> <checkjar label="JDK Tools JAR: " property="tools.jar" /> - <checkjar label="Bootstrap JAR: " property="bootstrap.jar" /> + <sequential if:set="bootstrap.jar"> + <checkjar label="Bootstrap JAR: " property="bootstrap.jar" /> + </sequential> <echo message="Snapshot: ${snapshot}" /> <echo message="Debug: ${debug}" /> </target> <target name="init" depends="configure"> - <requirejar property="asm.jar" /> <requirejar property="tools.jar" /> <tstamp /> @@ -132,13 +219,13 @@ <equals arg1="${snapshot}" arg2="true" /> </condition> <property name="cofoja.jar" - value="${dist.dir}/cofoja-${cofoja.version}.jar" /> + value="${dist.dir}/cofoja+asm-${cofoja.version}.jar" /> <property name="cofoja.bare.jar" - value="${dist.dir}/cofoja-${cofoja.version}-bare.jar" /> + value="${dist.dir}/cofoja-${cofoja.version}.jar" /> <property name="cofoja.contracted.jar" - value="${dist.dir}/ccofoja-${cofoja.version}.jar" /> + value="${dist.dir}/cofoja+contracts+asm-${cofoja.version}.jar" /> <property name="cofoja.contracted.bare.jar" - value="${dist.dir}/ccofoja-${cofoja.version}-bare.jar" /> + value="${dist.dir}/cofoja+contracts-${cofoja.version}.jar" /> <mkdir dir="${obj.dir}" /> <mkdir dir="${dist.dir}" /> @@ -150,30 +237,35 @@ description="build class files"> <mkdir dir="${obj.dir}/bare" /> <ujavac srcdir="${src.dir}" destdir="${obj.dir}/bare"> - <classpath refid="base.class.path" /> + <classpath refid="build.class.path" /> </ujavac> </target> - <target name="dist" depends="build" - description="build JAR files for distribution"> + <target name="nobootstrap" depends="build" + description="build JAR files without self-contracts"> <barejar jarfile="${cofoja.bare.jar}" basedir="${obj.dir}/bare" /> <fulljar jarfile="${cofoja.jar}" barejarfile="${cofoja.bare.jar}" /> + <property name="bootstrap.jar" value="${cofoja.bare.jar}" /> </target> <!-- Bootstrap. --> - <target name="antinit" depends="init"> - <requirejar property="bootstrap.jar" /> + <target name="antinit" depends="init,nobootstrap"> <mkdir dir="${obj.dir}/build" /> <ujavac srcdir="${build.dir}" destdir="${obj.dir}/build"> <classpath> <path refid="base.class.path" /> <pathelement path="${java.class.path}" /> - <pathelement path="${bootstrap.jar}" /> + <pathelement location="${bootstrap.jar}" /> </classpath> </ujavac> - <taskdef name="cofojab" classname="PreAgentAntTask" - classpath="${obj.dir}/build:${bootstrap.jar}:${asm.jar}" /> + <taskdef name="cofojab" classname="PreAgentAntTask"> + <classpath> + <path refid="base.class.path" /> + <pathelement location="${obj.dir}/build" /> + <pathelement location="${bootstrap.jar}" /> + </classpath> + </taskdef> </target> <target name="stage0" depends="antinit"> @@ -188,8 +280,8 @@ <bootstrapcomp stage="2" bootstrappath="${obj.dir}/stage1" /> </target> - <target name="bootstrap" depends="stage2" - description="build bootstrap-contracted JAR files"> + <target name="dist" depends="stage2" + description="build JAR files for distribution"> <barejar jarfile="${cofoja.contracted.bare.jar}" basedir="${obj.dir}/stage2" /> <fulljar jarfile="${cofoja.contracted.jar}" @@ -199,49 +291,39 @@ <!-- Tests. --> <target name="buildtest1" depends="stage2"> - <requirejar property="junit.jar" /> <mkdir dir="${obj.dir}/test" /> - <ujavac srcdir="${test.dir}" destdir="${obj.dir}/test"> - <classpath> - <path refid="test.class.path" /> - <pathelement path="${obj.dir}/stage2" /> - </classpath> - <compilerarg value="-processor" /> - <compilerarg value="${apt.class}" /> + <cofojavac srcdir="${test.dir}" destdir="${obj.dir}/test" + classpathid="test1.class.path"> <include name="**/SeparateGenericSuperclass.java" /> <include name="**/SeparateInterface.java" /> - </ujavac> + </cofojavac> </target> <target name="buildtest2" depends="buildtest1,stage2"> - <ujavac srcdir="${test.dir}" destdir="${obj.dir}/test"> - <classpath> - <path refid="test.class.path" /> - <pathelement path="${obj.dir}/stage2" /> - <pathelement path="${obj.dir}/test" /> - </classpath> - <compilerarg value="-processor" /> - <compilerarg value="${apt.class}" /> + <cofojavac srcdir="${test.dir}" destdir="${obj.dir}/test" + classpathid="test2.class.path"> <compilerarg value="-Acom.google.java.contract.dump=${obj.dir}/test/dump" /> <exclude name="**/SeparateGenericSuperclass.java" /> <exclude name="**/SeparateInterface.java" /> - </ujavac> + <exclude name="**/Java8*.java" unless:true="${java8}" /> + </cofojavac> </target> - <target name="test" depends="buildtest2,bootstrap" + <target name="test" depends="buildtest2,dist" description="run tests"> <junit printsummary="yes" haltonfailure="yes"> <classpath> <path refid="test.class.path" /> <pathelement path="${obj.dir}/stage2" /> - <pathelement location="${obj.dir}/test"/> + <pathelement location="${obj.dir}/test" /> </classpath> <jvmarg value="-javaagent:${cofoja.contracted.bare.jar}" /> <jvmarg value="-Dcom.google.java.contract.configurator=${test.configurator.class}" /> <formatter type="plain" /> <batchtest fork="yes" todir="${obj.dir}/test"> <fileset dir="${obj.dir}/test"> - <include name="**/*Test.class"/> + <include name="**/*Test.class" /> + <exclude name="**/Java8*.class" unless:true="${java8}" /> </fileset> </batchtest> </junit> diff --git a/default.properties b/default.properties index a74d238..ba01f4a 100644 --- a/default.properties +++ b/default.properties @@ -3,21 +3,13 @@ ## Version information. Set 'snapshot' to false if you are building ## a release. -version=1.1 +version=1.2 snapshot=true debug=true -## Location of an older build of Cofoja, for bootstrapping purposes. -bootstrap.jar=build/bootstrap.jar +## Location of an older build of Cofoja, for bootstrapping +## purposes. Leave unset to use a newly built binary. +# bootstrap.jar=build/bootstrap.jar -## Location of dependency JAR files needed to build Cofoja. The -## defaults assume the JAR files are in the 'build' subdirectory, -## except for 'tools.jar', which is probably not what you want. -asm.jar=build/asm.jar -junit.jar=build/junit.jar +## Location of the JDK 'tools.jar' or 'classes.jar' (on Mac OS X). tools.jar=${java.home}/../lib/tools.jar - -## If you use Maven, the following may help, instead. -# maven.repo.dir=${user.home}/.m2/repository -# asm.jar=${maven.repo.dir}/asm/asm-all/3.3.1/asm-all-3.3.1.jar -# junit.jar=${maven.repo.dir}/junit/junit/4.8.2/junit-4.8.2.jar diff --git a/ivy.xml b/ivy.xml new file mode 100644 index 0000000..7732a6e --- /dev/null +++ b/ivy.xml @@ -0,0 +1,18 @@ +<ivy-module version="2.0"> + <info organisation="com.google.java.contract" module="cofoja" /> + <configurations> + <conf name="core" description="core libraries" /> + <conf name="contracts" extends="core" + description="core libraries with contracts" /> + <conf name="test" extends="core" visibility="private" + description="for testing" /> + </configurations> + <publications> + <artifact conf="core" /> + <artifact name="cofoja+contracts" conf="contracts" /> + </publications> + <dependencies> + <dependency org="org.ow2.asm" name="asm-all" rev="5.+" conf="*->default" /> + <dependency org="junit" name="junit-dep" rev="[3.8,)" conf="test->default" /> + </dependencies> +</ivy-module> diff --git a/src/com/google/java/contract/ContractImport.java b/src/com/google/java/contract/ContractImport.java new file mode 100644 index 0000000..962c4b1 --- /dev/null +++ b/src/com/google/java/contract/ContractImport.java @@ -0,0 +1,37 @@ +/* + * Copyright 2014 Nhat Minh Lê + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ +package com.google.java.contract; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Specifies import statements for use in contracts. + * + * @author [email protected] (Nhat Minh Lê) + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.SOURCE) +public @interface ContractImport { + /** + * The list of import statements. + */ + String[] value(); +} diff --git a/src/com/google/java/contract/core/agent/ContractAnalyzer.java b/src/com/google/java/contract/core/agent/ContractAnalyzer.java index 931dbe3..c5b7012 100644 --- a/src/com/google/java/contract/core/agent/ContractAnalyzer.java +++ b/src/com/google/java/contract/core/agent/ContractAnalyzer.java @@ -18,14 +18,11 @@ */ package com.google.java.contract.core.agent; -import com.google.java.contract.AllowUnusedImport; +import com.google.java.contract.ContractImport; import com.google.java.contract.Ensures; import com.google.java.contract.Invariant; import com.google.java.contract.Requires; -import com.google.java.contract.core.model.ClassName; import com.google.java.contract.core.model.ContractKind; -import com.google.java.contract.util.Iterables; -import com.google.java.contract.util.Predicates; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; @@ -49,7 +46,11 @@ import java.util.Map; * @author [email protected] (Nhat Minh Lê) * @author [email protected] (Johannes Rieken) */ -@AllowUnusedImport({ ClassName.class, Iterables.class, Predicates.class }) +@ContractImport({ + "com.google.java.contract.core.model.ClassName", + "com.google.java.contract.util.Iterables", + "com.google.java.contract.util.Predicates" +}) @Invariant({ "className == null || ClassName.isBinaryName(className)", "classHandles != null", @@ -71,7 +72,7 @@ class ContractAnalyzer extends ClassVisitor { * intended to be filled through its visitor interface. */ ContractAnalyzer() { - super(Opcodes.ASM4); + super(Opcodes.ASM5); classHandles = new ArrayList<ClassContractHandle>(); methodHandles = new HashMap<String, ArrayList<MethodContractHandle>>(); } diff --git a/src/com/google/java/contract/core/agent/ContractClassFileTransformer.java b/src/com/google/java/contract/core/agent/ContractClassFileTransformer.java index e75bcc3..c36ec36 100644 --- a/src/com/google/java/contract/core/agent/ContractClassFileTransformer.java +++ b/src/com/google/java/contract/core/agent/ContractClassFileTransformer.java @@ -18,7 +18,7 @@ */ package com.google.java.contract.core.agent; -import com.google.java.contract.AllowUnusedImport; +import com.google.java.contract.ContractImport; import com.google.java.contract.Ensures; import com.google.java.contract.Requires; import com.google.java.contract.core.model.ClassName; @@ -58,14 +58,14 @@ import javax.tools.JavaFileObject.Kind; * @author [email protected] (Nhat Minh Lê) * @author [email protected] (Johannes Rieken) */ -@AllowUnusedImport(ClassName.class) +@ContractImport("com.google.java.contract.core.model.ClassName") public class ContractClassFileTransformer implements ClassFileTransformer { /** * Find and store superclass information. */ private class SuperInfoFinder extends ClassVisitor { private SuperInfoFinder() { - super(Opcodes.ASM4); + super(Opcodes.ASM5); } @Override diff --git a/src/com/google/java/contract/core/agent/ContractFixingClassAdapter.java b/src/com/google/java/contract/core/agent/ContractFixingClassAdapter.java index 038c3f6..e387e6f 100644 --- a/src/com/google/java/contract/core/agent/ContractFixingClassAdapter.java +++ b/src/com/google/java/contract/core/agent/ContractFixingClassAdapter.java @@ -21,6 +21,7 @@ package com.google.java.contract.core.agent; import com.google.java.contract.Requires; import com.google.java.contract.core.util.JavaUtils; import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Handle; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; @@ -52,7 +53,7 @@ class ContractFixingClassAdapter extends ClassVisitor { */ @Requires("mv != null") public AccessMethodAdapter(MethodVisitor mv) { - super(Opcodes.ASM4, mv); + super(Opcodes.ASM5, mv); } /** @@ -61,14 +62,36 @@ class ContractFixingClassAdapter extends ClassVisitor { */ @Override public void visitMethodInsn(int opcode, String owner, String name, - String desc) { + String desc, boolean itf) { if (!name.startsWith("access$")) { - mv.visitMethodInsn(opcode, owner, name, desc); + mv.visitMethodInsn(opcode, owner, name, desc, itf); } else { - mv.visitMethodInsn(opcode, owner, - JavaUtils.SYNTHETIC_MEMBER_PREFIX + name, desc); + String newName = JavaUtils.SYNTHETIC_MEMBER_PREFIX + name; + mv.visitMethodInsn(opcode, owner, newName, desc, itf); } } + + /** + * Converts calls to {@code lambda$n} synthetic methods to the + * equivalent injected methods. + */ + @Override + public void visitInvokeDynamicInsn(String name, String desc, + Handle bsm, Object... bsmArgs) { + for (int i = 0; i < bsmArgs.length; ++i) { + if (!(bsmArgs[i] instanceof Handle)) { + continue; + } + Handle h = (Handle)bsmArgs[i]; + if (!h.getName().startsWith("lambda$")) { + continue; + } + String newName = JavaUtils.SYNTHETIC_MEMBER_PREFIX + h.getName(); + bsmArgs[i] = new Handle(h.getTag(), h.getOwner(), + newName, h.getDesc()); + } + mv.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); + } } /** @@ -78,7 +101,7 @@ class ContractFixingClassAdapter extends ClassVisitor { */ @Requires("cv != null") public ContractFixingClassAdapter(ClassVisitor cv) { - super(Opcodes.ASM4, cv); + super(Opcodes.ASM5, cv); } /** diff --git a/src/com/google/java/contract/core/agent/ContractHandle.java b/src/com/google/java/contract/core/agent/ContractHandle.java index c8e348b..4cd6587 100644 --- a/src/com/google/java/contract/core/agent/ContractHandle.java +++ b/src/com/google/java/contract/core/agent/ContractHandle.java @@ -18,15 +18,12 @@ */ package com.google.java.contract.core.agent; -import com.google.java.contract.AllowUnusedImport; +import com.google.java.contract.ContractImport; import com.google.java.contract.Ensures; import com.google.java.contract.Invariant; import com.google.java.contract.Requires; -import com.google.java.contract.core.model.ClassName; import com.google.java.contract.core.model.ContractKind; import com.google.java.contract.core.util.JavaUtils; -import com.google.java.contract.util.Iterables; -import com.google.java.contract.util.Predicates; import org.objectweb.asm.tree.MethodNode; import java.util.List; @@ -39,7 +36,11 @@ import java.util.List; * @author [email protected] (Nhat Minh Lê) * @author [email protected] (Johannes Rieken) */ -@AllowUnusedImport({ ClassName.class, Iterables.class, Predicates.class }) +@ContractImport({ + "com.google.java.contract.core.model.ClassName", + "com.google.java.contract.util.Iterables", + "com.google.java.contract.util.Predicates" +}) @Invariant({ "getKind() != null", "ClassName.isBinaryName(getClassName())", diff --git a/src/com/google/java/contract/core/agent/ContractMethodSignatures.java b/src/com/google/java/contract/core/agent/ContractMethodSignatures.java index 2378ac1..2ad3dc2 100644 --- a/src/com/google/java/contract/core/agent/ContractMethodSignatures.java +++ b/src/com/google/java/contract/core/agent/ContractMethodSignatures.java @@ -51,6 +51,8 @@ class ContractMethodSignatures { return Enum.valueOf(ContractKind.class, pair[1]); } else if (contractMethod.name.startsWith("access$")) { return ContractKind.ACCESS; + } else if (contractMethod.name.startsWith("lambda$")) { + return ContractKind.LAMBDA; } else { return null; } diff --git a/src/com/google/java/contract/core/agent/HelperClassAdapter.java b/src/com/google/java/contract/core/agent/HelperClassAdapter.java index e32a746..f303916 100644 --- a/src/com/google/java/contract/core/agent/HelperClassAdapter.java +++ b/src/com/google/java/contract/core/agent/HelperClassAdapter.java @@ -56,7 +56,7 @@ class HelperClassAdapter extends ClassVisitor { public AnnotationVisitor visitAnnotation(String desc, boolean visible) { if (Type.getType(desc).getInternalName().equals( "com/google/java/contract/core/agent/ContractMethodSignature")) { - return new AnnotationVisitor(Opcodes.ASM4) { + return new AnnotationVisitor(Opcodes.ASM5) { @Override public void visit(String name, Object value) { if (name.equals("lines")) { @@ -71,7 +71,7 @@ class HelperClassAdapter extends ClassVisitor { @Requires("cv != null") public HelperClassAdapter(ClassVisitor cv) { - super(Opcodes.ASM4, cv); + super(Opcodes.ASM5, cv); } @Override diff --git a/src/com/google/java/contract/core/agent/LineNumberingClassAdapter.java b/src/com/google/java/contract/core/agent/LineNumberingClassAdapter.java index 60c81f0..5b2f318 100644 --- a/src/com/google/java/contract/core/agent/LineNumberingClassAdapter.java +++ b/src/com/google/java/contract/core/agent/LineNumberingClassAdapter.java @@ -45,7 +45,7 @@ class LineNumberingClassAdapter extends ClassVisitor { "ContractMethodSignatures.isLineNumberList(lineNumbers)" }) public LineNumberingClassAdapter(ClassVisitor cv, List<Long> lineNumbers) { - super(Opcodes.ASM4, cv); + super(Opcodes.ASM5, cv); this.lineNumbers = lineNumbers; } diff --git a/src/com/google/java/contract/core/agent/LineNumberingMethodAdapter.java b/src/com/google/java/contract/core/agent/LineNumberingMethodAdapter.java index 882ede7..0e06bc6 100644 --- a/src/com/google/java/contract/core/agent/LineNumberingMethodAdapter.java +++ b/src/com/google/java/contract/core/agent/LineNumberingMethodAdapter.java @@ -61,7 +61,7 @@ abstract class LineNumberingMethodAdapter extends AdviceAdapter { }) public LineNumberingMethodAdapter(MethodVisitor mv, int access, String name, String desc) { - super(Opcodes.ASM4, mv, access, name, desc); + super(Opcodes.ASM5, mv, access, name, desc); lineNumbers = null; } diff --git a/src/com/google/java/contract/core/agent/SpecificationClassAdapter.java b/src/com/google/java/contract/core/agent/SpecificationClassAdapter.java index 6cd1a1e..af0934f 100644 --- a/src/com/google/java/contract/core/agent/SpecificationClassAdapter.java +++ b/src/com/google/java/contract/core/agent/SpecificationClassAdapter.java @@ -18,9 +18,8 @@ */ package com.google.java.contract.core.agent; -import com.google.java.contract.AllowUnusedImport; +import com.google.java.contract.ContractImport; import com.google.java.contract.Invariant; -import com.google.java.contract.core.model.ClassName; import com.google.java.contract.core.model.ContractKind; import com.google.java.contract.core.util.DebugUtils; import org.objectweb.asm.ClassVisitor; @@ -28,6 +27,7 @@ import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.MethodNode; +import java.util.ArrayList; import java.util.List; /** @@ -37,7 +37,7 @@ import java.util.List; * @author [email protected] (Nhat Minh Lê) * @author [email protected] (Johannes Rieken) */ -@AllowUnusedImport(ClassName.class) +@ContractImport("com.google.java.contract.core.model.ClassName") @Invariant({ "getClassName() == null || ClassName.isBinaryName(getClassName())", "getContracts() != null", @@ -49,7 +49,7 @@ class SpecificationClassAdapter extends ClassVisitor { public SpecificationClassAdapter(ClassVisitor cv, ContractAnalyzer contracts) { - super(Opcodes.ASM4, cv); + super(Opcodes.ASM5, cv); this.contracts = contracts; } @@ -75,9 +75,10 @@ class SpecificationClassAdapter extends ClassVisitor { @Override public void visitEnd() { if (contracts != null) { - List<ClassContractHandle> accesses = - contracts.getClassHandles(ContractKind.ACCESS); - for (ClassContractHandle h : accesses) { + List<ClassContractHandle> synths = new ArrayList<ClassContractHandle>(); + synths.addAll(contracts.getClassHandles(ContractKind.ACCESS)); + synths.addAll(contracts.getClassHandles(ContractKind.LAMBDA)); + for (ClassContractHandle h : synths) { h.getContractMethod().accept(cv); } diff --git a/src/com/google/java/contract/core/agent/SpecificationMethodAdapter.java b/src/com/google/java/contract/core/agent/SpecificationMethodAdapter.java index f546269..3b600c0 100644 --- a/src/com/google/java/contract/core/agent/SpecificationMethodAdapter.java +++ b/src/com/google/java/contract/core/agent/SpecificationMethodAdapter.java @@ -19,15 +19,12 @@ */ package com.google.java.contract.core.agent; -import com.google.java.contract.AllowUnusedImport; +import com.google.java.contract.ContractImport; import com.google.java.contract.Ensures; import com.google.java.contract.Invariant; import com.google.java.contract.Requires; -import com.google.java.contract.core.model.ClassName; import com.google.java.contract.core.model.ContractKind; import com.google.java.contract.core.util.DebugUtils; -import com.google.java.contract.util.Iterables; -import com.google.java.contract.util.Predicates; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; @@ -49,7 +46,11 @@ import java.util.List; * @author [email protected] (Nhat Minh Lê) * @author [email protected] (Johannes Rieken) */ -@AllowUnusedImport({ ClassName.class, Iterables.class, Predicates.class }) +@ContractImport({ + "com.google.java.contract.core.model.ClassName", + "com.google.java.contract.util.Iterables", + "com.google.java.contract.util.Predicates" +}) @Invariant({ "methodStart != null", "methodEnd != null", @@ -133,7 +134,7 @@ public class SpecificationMethodAdapter extends AdviceAdapter { MethodVisitor mv, int access, String methodName, String methodDesc) { - super(Opcodes.ASM4, mv, access, methodName, methodDesc); + super(Opcodes.ASM5, mv, access, methodName, methodDesc); methodStart = new Label(); methodEnd = new Label(); @@ -543,10 +544,10 @@ public class SpecificationMethodAdapter extends AdviceAdapter { protected void invokeContractMethod(MethodNode contractMethod) { if (!statik) { mv.visitMethodInsn(INVOKESPECIAL, className, - contractMethod.name, contractMethod.desc); + contractMethod.name, contractMethod.desc, false); } else { mv.visitMethodInsn(INVOKESTATIC, className, - contractMethod.name, contractMethod.desc); + contractMethod.name, contractMethod.desc, false); } } diff --git a/src/com/google/java/contract/core/apt/AbstractTypeBuilder.java b/src/com/google/java/contract/core/apt/AbstractTypeBuilder.java index 2049d44..bb96167 100644 --- a/src/com/google/java/contract/core/apt/AbstractTypeBuilder.java +++ b/src/com/google/java/contract/core/apt/AbstractTypeBuilder.java @@ -29,6 +29,7 @@ import com.google.java.contract.core.model.TypeName; import com.google.java.contract.core.util.JavaUtils; import java.util.Collections; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; @@ -256,19 +257,37 @@ abstract class AbstractTypeBuilder @Ensures("result != null") @SuppressWarnings("unchecked") protected Set<String> getImportNames(Element element) { + HashSet<String> importNames = new HashSet<String>(); if (JavaUtils.classExists("com.sun.source.util.Trees")) { try { - return (Set<String>) Class + Set<String> classImportNames = (Set<String>) Class .forName("com.google.java.contract.core.apt.JavacUtils") .getMethod("getImportNames", ProcessingEnvironment.class, Element.class) .invoke(null, utils.processingEnv, element); + importNames.addAll(classImportNames); } catch (Exception e) { - return Collections.emptySet(); + /* Ignore. */ + } + } + + /* Add import statements from explicit annotations. */ + for (AnnotationMirror ann : element.getAnnotationMirrors()) { + if (!ann.getAnnotationType().toString() + .equals("com.google.java.contract.ContractImport")) { + continue; + } + for (AnnotationValue annotationValue : + ann.getElementValues().values()) { + @SuppressWarnings("unchecked") + List<? extends AnnotationValue> values = + (List<? extends AnnotationValue>) annotationValue.getValue(); + for (AnnotationValue value : values) + importNames.add((String) value.getValue()); } - } else { - return Collections.emptySet(); } + + return importNames; } /** diff --git a/src/com/google/java/contract/core/apt/AnnotationProcessor.java b/src/com/google/java/contract/core/apt/AnnotationProcessor.java index 732b7ba..f66742e 100644 --- a/src/com/google/java/contract/core/apt/AnnotationProcessor.java +++ b/src/com/google/java/contract/core/apt/AnnotationProcessor.java @@ -18,10 +18,7 @@ */ package com.google.java.contract.core.apt; -import com.sun.tools.javac.main.OptionName; -import com.sun.tools.javac.processing.JavacProcessingEnvironment; -import com.sun.tools.javac.util.Options; -import com.google.java.contract.AllowUnusedImport; +import com.google.java.contract.ContractImport; import com.google.java.contract.Ensures; import com.google.java.contract.Requires; import com.google.java.contract.core.model.ContractAnnotationModel; @@ -32,6 +29,7 @@ import com.google.java.contract.core.util.SyntheticJavaFile; import java.io.File; import java.io.IOException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; @@ -47,7 +45,6 @@ import javax.annotation.processing.SupportedOptions; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; import javax.lang.model.util.ElementScanner6; import javax.tools.JavaCompiler.CompilationTask; @@ -61,7 +58,7 @@ import javax.tools.JavaFileObject.Kind; * @author [email protected] (Johannes Rieken) * @author [email protected] (Leonardo Chatain) */ -@AllowUnusedImport(ElementKind.class) +@ContractImport("javax.lang.model.element.ElementKind") @SupportedAnnotationTypes("*") @SupportedOptions({ AnnotationProcessor.OPT_DEBUG, @@ -130,6 +127,61 @@ public class AnnotationProcessor extends AbstractProcessor { protected boolean debug; protected boolean dump; + private Class<?> javacProcessingEnvironmentClass; + private Method getContextMethod; + private Method optionsInstanceMethod; + private Method optionsGetMethod; + + /** + * Initialize classes and methods needed for OpenJDK javac reflection. + */ + private void setupReflection() { + try { + javacProcessingEnvironmentClass = Class.forName( + "com.sun.tools.javac.processing.JavacProcessingEnvironment"); + } catch (ClassNotFoundException e) { + /* Javac isn't on the classpath. */ + return; + } + + try { + Class<?> contextClass = Class.forName("com.sun.tools.javac.util.Context"); + getContextMethod = javacProcessingEnvironmentClass.getMethod("getContext"); + Class<?> optionsClass = Class.forName("com.sun.tools.javac.util.Options"); + optionsInstanceMethod = optionsClass.getMethod("instance", contextClass); + optionsGetMethod = optionsClass.getMethod("get", String.class); + } catch (Exception e) { + throw new LinkageError(e.getMessage()); + } + } + + /** + * Calls {@code com.sun.tools.javac.util.Options.get(String)} reflectively. + */ + private String getJavacOption(Object options, String name) { + try { + return (String) optionsGetMethod.invoke(options, name); + } catch (Exception e) { + throw new LinkageError(e.getMessage()); + } + } + + /** + * Calls {@code com.sun.tools.javac.util.Options.instance(Context)} + * reflectively. + */ + private Object getJavacOptions() { + if (!javacProcessingEnvironmentClass.isInstance(processingEnv)) { + return null; + } + try { + return optionsInstanceMethod.invoke(null, + getContextMethod.invoke(processingEnv)); + } catch (Exception e) { + throw new LinkageError(e.getMessage()); + } + } + @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); @@ -145,6 +197,7 @@ public class AnnotationProcessor extends AbstractProcessor { utils = new FactoryUtils(processingEnv); factory = new TypeFactory(utils, options.get(OPT_DEPSPATH)); + setupReflection(); setupPaths(); } @@ -209,39 +262,34 @@ public class AnnotationProcessor extends AbstractProcessor { outputDirectory = processingEnv.getOptions().get(OPT_CLASSOUTPUT); /* - * Not using instanceof here because than in every case the JVM - * tries to load the JavacProcessingEnvironment-class file which, - * for instance, is not possible with an IBM JVM. - * - * TODO(lenh): This may not work; use reflection call instead. + * Load classes in com.sun.tools reflectively for graceful fallback when + * the OpenJDK javac isn't available (e.g. with J9, or ecj). */ - if (processingEnv.getClass().getName().equals( - "com.sun.tools.javac.processing.JavacProcessingEnvironment")) { - JavacProcessingEnvironment javacEnv = - (JavacProcessingEnvironment) processingEnv; - Options options = Options.instance(javacEnv.getContext()); - - if (sourcePath == null) { - sourcePath = options.get(OptionName.SOURCEPATH); - } + Object options = getJavacOptions(); + if (options == null) { + return; + } + + if (sourcePath == null) { + sourcePath = getJavacOption(options, "-sourcepath"); + } - if (classPath == null) { - String classPath1 = options.get(OptionName.CP); - String classPath2 = options.get(OptionName.CLASSPATH); - if (classPath1 != null) { - if (classPath2 != null) { - classPath = classPath1 + File.pathSeparator + classPath2; - } else { - classPath = classPath1; - } + if (classPath == null) { + String classPath1 = getJavacOption(options, "-cp"); + String classPath2 = getJavacOption(options, "-classpath"); + if (classPath1 != null) { + if (classPath2 != null) { + classPath = classPath1 + File.pathSeparator + classPath2; } else { - classPath = classPath2; + classPath = classPath1; } + } else { + classPath = classPath2; } + } - if (outputDirectory == null) { - outputDirectory = options.get(OptionName.D); - } + if (outputDirectory == null) { + outputDirectory = getJavacOption(options, "-d"); } } diff --git a/src/com/google/java/contract/core/apt/ContractCreation.java b/src/com/google/java/contract/core/apt/ContractCreation.java index 3afbcb0..06a38c8 100644 --- a/src/com/google/java/contract/core/apt/ContractCreation.java +++ b/src/com/google/java/contract/core/apt/ContractCreation.java @@ -18,6 +18,7 @@ */ package com.google.java.contract.core.apt; +import com.google.java.contract.ContractImport; import com.google.java.contract.Ensures; import com.google.java.contract.Requires; import com.google.java.contract.core.model.ClassName; @@ -47,6 +48,7 @@ import java.util.Set; * * @author [email protected] (Nhat Minh Lê) */ +@ContractImport("com.google.java.contract.core.model.ClassName") public class ContractCreation { static final String RAISE_METHOD = "com.google.java.contract.core.runtime.ContractRuntime.raise"; diff --git a/src/com/google/java/contract/core/apt/ContractCreationTrait.java b/src/com/google/java/contract/core/apt/ContractCreationTrait.java index 47daa81..2f214d5 100644 --- a/src/com/google/java/contract/core/apt/ContractCreationTrait.java +++ b/src/com/google/java/contract/core/apt/ContractCreationTrait.java @@ -17,10 +17,9 @@ */ package com.google.java.contract.core.apt; -import com.google.java.contract.AllowUnusedImport; +import com.google.java.contract.ContractImport; import com.google.java.contract.Ensures; import com.google.java.contract.Requires; -import com.google.java.contract.core.model.ClassName; import com.google.java.contract.core.model.ContractAnnotationModel; import com.google.java.contract.core.model.VariableModel; @@ -32,7 +31,7 @@ import java.util.List; * * @author [email protected] (Nhat Minh Lê) */ -@AllowUnusedImport(ClassName.class) +@ContractImport("com.google.java.contract.core.model.ClassName") interface ContractCreationTrait { /** * Called before any of the query methods of this object. Queries diff --git a/src/com/google/java/contract/core/apt/ContractExpressionTransformer.java b/src/com/google/java/contract/core/apt/ContractExpressionTransformer.java index e4218c0..2801de2 100644 --- a/src/com/google/java/contract/core/apt/ContractExpressionTransformer.java +++ b/src/com/google/java/contract/core/apt/ContractExpressionTransformer.java @@ -132,10 +132,6 @@ public class ContractExpressionTransformer { oldId = 0; } - public void setAcceptOld(boolean acceptOld) { - this.acceptOld = acceptOld; - } - @Requires({ "currentBuffer != null", "tokenizer != null", diff --git a/src/com/google/java/contract/core/apt/ContractJavaCompiler.java b/src/com/google/java/contract/core/apt/ContractJavaCompiler.java index c9b26b6..68dbd09 100644 --- a/src/com/google/java/contract/core/apt/ContractJavaCompiler.java +++ b/src/com/google/java/contract/core/apt/ContractJavaCompiler.java @@ -48,6 +48,9 @@ import javax.tools.ToolProvider; "fileManager != null" }) public class ContractJavaCompiler { + private static final String JAVA_VERSION = + System.getProperty("java.specification.version"); + /** * The compiler options passed to the internal compiler. */ @@ -55,7 +58,11 @@ public class ContractJavaCompiler { Arrays.asList( "-g:source,vars", /* Source file name, for debug attributes. */ "-proc:none", /* No further annotations to process. */ - "-implicit:none" /* No class files for implicit dependencies. */ + "-implicit:none", /* No class files for implicit dependencies. */ + /* Target the highest Java version supported by the runtime, instead + * of the highest supported by the compiler. */ + "-source", JAVA_VERSION, + "-target", JAVA_VERSION ); /** diff --git a/src/com/google/java/contract/core/apt/ContractWriter.java b/src/com/google/java/contract/core/apt/ContractWriter.java index 80a5d36..e4a8e89 100644 --- a/src/com/google/java/contract/core/apt/ContractWriter.java +++ b/src/com/google/java/contract/core/apt/ContractWriter.java @@ -18,7 +18,7 @@ */ package com.google.java.contract.core.apt; -import com.google.java.contract.AllowUnusedImport; +import com.google.java.contract.ContractImport; import com.google.java.contract.Ensures; import com.google.java.contract.Invariant; import com.google.java.contract.Requires; @@ -33,8 +33,6 @@ import com.google.java.contract.core.model.TypeName; import com.google.java.contract.core.model.VariableModel; import com.google.java.contract.core.util.ElementScanner; import com.google.java.contract.core.util.Elements; -import com.google.java.contract.util.Iterables; -import com.google.java.contract.util.Predicates; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -57,7 +55,11 @@ import java.util.regex.Pattern; * @author [email protected] (Nhat Minh Lê) * @author [email protected] (Leonardo Chatain) */ -@AllowUnusedImport({ Iterables.class, Predicates.class }) +@ContractImport({ + "com.google.java.contract.core.model.ElementKind", + "com.google.java.contract.util.Iterables", + "com.google.java.contract.util.Predicates" +}) @Invariant({ "getLineNumberMap() != null", "Iterables.all(getLineNumberMap().keySet(), Predicates.between(1L, null))", diff --git a/src/com/google/java/contract/core/apt/MethodContractCreator.java b/src/com/google/java/contract/core/apt/MethodContractCreator.java index ff7d249..3377f0f 100644 --- a/src/com/google/java/contract/core/apt/MethodContractCreator.java +++ b/src/com/google/java/contract/core/apt/MethodContractCreator.java @@ -50,7 +50,9 @@ import java.util.NoSuchElementException; */ @Invariant({ "diagnosticManager != null", - "transformer != null" + "preTransformer != null", + "postTransformer != null", + "postSignalTransformer != null" }) public class MethodContractCreator extends ElementScanner { /** @@ -67,7 +69,6 @@ public class MethodContractCreator extends ElementScanner { @Override public boolean transform(List<String> code, List<Long> lineNumbers, Object sourceInfo) { - transformer.setAcceptOld(false); return super.transform(code, lineNumbers, sourceInfo); } @@ -93,7 +94,6 @@ public class MethodContractCreator extends ElementScanner { public boolean transform(List<String> code, List<Long> lineNumbers, Object sourceInfo) { int id = transformer.getNextOldId(); - transformer.setAcceptOld(true); boolean success = super.transform(code, lineNumbers, sourceInfo); if (success) { @@ -242,7 +242,9 @@ public class MethodContractCreator extends ElementScanner { protected ContractMethodModel postMethod; protected ContractMethodModel postSignalMethod; - protected ContractExpressionTransformer transformer; + protected ContractExpressionTransformer preTransformer; + protected ContractExpressionTransformer postTransformer; + protected ContractExpressionTransformer postSignalTransformer; /** * Constructs a new MethodContractCreator. @@ -254,7 +256,12 @@ public class MethodContractCreator extends ElementScanner { preMethod = null; postMethod = null; postSignalMethod = null; - transformer = new ContractExpressionTransformer(diagnosticManager, true); + preTransformer = + new ContractExpressionTransformer(diagnosticManager, false); + postTransformer = + new ContractExpressionTransformer(diagnosticManager, true); + postSignalTransformer = + new ContractExpressionTransformer(diagnosticManager, true); } @Override @@ -271,14 +278,15 @@ public class MethodContractCreator extends ElementScanner { List<String> code = annotation.getValues(); if (annotation.getKind().equals(ElementKind.REQUIRES)) { - PreMethodCreationTrait trait = new PreMethodCreationTrait(transformer); + PreMethodCreationTrait trait = new PreMethodCreationTrait(preTransformer); preMethod = createContractMethods(trait, preMethod, annotation); } else if (annotation.getKind().equals(ElementKind.ENSURES)) { - PostMethodCreationTrait trait = new PostMethodCreationTrait(transformer); + PostMethodCreationTrait trait = + new PostMethodCreationTrait(postTransformer); postMethod = createContractMethods(trait, postMethod, annotation); } else if (annotation.getKind().equals(ElementKind.THROW_ENSURES)) { PostSignalMethodCreationTrait trait = - new PostSignalMethodCreationTrait(transformer); + new PostSignalMethodCreationTrait(postSignalTransformer); postSignalMethod = createContractMethods(trait, postSignalMethod, annotation); } else { diff --git a/src/com/google/java/contract/core/apt/SourceDependencyParser.java b/src/com/google/java/contract/core/apt/SourceDependencyParser.java index be868b8..86ce231 100644 --- a/src/com/google/java/contract/core/apt/SourceDependencyParser.java +++ b/src/com/google/java/contract/core/apt/SourceDependencyParser.java @@ -17,7 +17,7 @@ */ package com.google.java.contract.core.apt; -import com.google.java.contract.AllowUnusedImport; +import com.google.java.contract.ContractImport; import com.google.java.contract.Ensures; import com.google.java.contract.Invariant; import com.google.java.contract.Requires; @@ -26,8 +26,6 @@ import com.google.java.contract.core.util.BalancedTokenizer; import com.google.java.contract.core.util.JavaTokenizer; import com.google.java.contract.core.util.JavaUtils; import com.google.java.contract.core.util.JavaUtils.ParseException; -import com.google.java.contract.util.Iterables; -import com.google.java.contract.util.Predicates; import java.io.Reader; import java.util.ArrayList; @@ -46,7 +44,10 @@ import java.util.Set; * * @author [email protected] (Nhat Minh Lê) */ -@AllowUnusedImport({ Iterables.class, Predicates.class }) +@ContractImport({ + "com.google.java.contract.util.Iterables", + "com.google.java.contract.util.Predicates" +}) @Invariant({ "!canQueryResults() || getImportNames() != null", "!canQueryResults() || !getImportNames().contains(null)", diff --git a/src/com/google/java/contract/core/model/ContractAnnotationModel.java b/src/com/google/java/contract/core/model/ContractAnnotationModel.java index 424656e..0aa3485 100644 --- a/src/com/google/java/contract/core/model/ContractAnnotationModel.java +++ b/src/com/google/java/contract/core/model/ContractAnnotationModel.java @@ -18,12 +18,10 @@ */ package com.google.java.contract.core.model; -import com.google.java.contract.AllowUnusedImport; +import com.google.java.contract.ContractImport; import com.google.java.contract.Ensures; import com.google.java.contract.Invariant; import com.google.java.contract.Requires; -import com.google.java.contract.util.Iterables; -import com.google.java.contract.util.Predicates; import java.util.ArrayList; import java.util.Collections; @@ -34,7 +32,10 @@ import java.util.List; * * @author [email protected] (Nhat Minh Lê) */ -@AllowUnusedImport({ Iterables.class, Predicates.class }) +@ContractImport({ + "com.google.java.contract.util.Iterables", + "com.google.java.contract.util.Predicates" +}) @Invariant({ "!isWeakVirtual() || isVirtual()", "getValues() != null", diff --git a/src/com/google/java/contract/core/model/ContractKind.java b/src/com/google/java/contract/core/model/ContractKind.java index c3c5ea7..62ee985 100644 --- a/src/com/google/java/contract/core/model/ContractKind.java +++ b/src/com/google/java/contract/core/model/ContractKind.java @@ -75,6 +75,12 @@ public enum ContractKind { ACCESS, /** + * A synthetic lambda method, generated by the Java compiler, used + * in contract methods. + */ + LAMBDA, + + /** * A contract helper, for indirect contract evaluation. * * @see ContractCreator @@ -200,6 +206,8 @@ public enum ContractKind { return "com$google$java$contract$EO"; case ACCESS: return "access"; + case LAMBDA: + return "lambda"; default: throw new IllegalArgumentException(); } diff --git a/src/com/google/java/contract/core/model/ElementModel.java b/src/com/google/java/contract/core/model/ElementModel.java index 29279cf..7920653 100644 --- a/src/com/google/java/contract/core/model/ElementModel.java +++ b/src/com/google/java/contract/core/model/ElementModel.java @@ -18,13 +18,11 @@ */ package com.google.java.contract.core.model; -import com.google.java.contract.AllowUnusedImport; +import com.google.java.contract.ContractImport; import com.google.java.contract.Ensures; import com.google.java.contract.Invariant; import com.google.java.contract.Requires; -import com.google.java.contract.util.Iterables; import com.google.java.contract.util.Predicate; -import com.google.java.contract.util.Predicates; import java.util.ArrayList; import java.util.Collections; @@ -36,7 +34,7 @@ import java.util.List; * * @author [email protected] (Nhat Minh Lê) */ -@AllowUnusedImport({ Iterables.class, Predicates.class }) +@ContractImport("com.google.java.contract.util.Iterables") @Invariant({ "getEnclosingElement() == null " + "|| getEnclosingElement().getEnclosedElements().contains(this)", diff --git a/src/com/google/java/contract/core/util/JavaUtils.java b/src/com/google/java/contract/core/util/JavaUtils.java index 155ba0d..58ae209 100644 --- a/src/com/google/java/contract/core/util/JavaUtils.java +++ b/src/com/google/java/contract/core/util/JavaUtils.java @@ -17,10 +17,9 @@ */ package com.google.java.contract.core.util; -import com.google.java.contract.AllowUnusedImport; +import com.google.java.contract.ContractImport; import com.google.java.contract.Ensures; import com.google.java.contract.Requires; -import com.google.java.contract.core.model.ClassName; import java.io.File; import java.io.InputStream; @@ -37,7 +36,7 @@ import javax.tools.JavaFileObject.Kind; * * @author [email protected] (Nhat Minh Lê) */ -@AllowUnusedImport(ClassName.class) +@ContractImport("com.google.java.contract.core.model.ClassName") public class JavaUtils { /** * A parse error; thrown by the parsing functions whenever they read diff --git a/src/com/google/java/contract/core/util/PatternMap.java b/src/com/google/java/contract/core/util/PatternMap.java index 177f610..4e8685b 100644 --- a/src/com/google/java/contract/core/util/PatternMap.java +++ b/src/com/google/java/contract/core/util/PatternMap.java @@ -17,13 +17,11 @@ */ package com.google.java.contract.core.util; -import com.google.java.contract.AllowUnusedImport; +import com.google.java.contract.ContractImport; import com.google.java.contract.Ensures; import com.google.java.contract.Invariant; import com.google.java.contract.Requires; import com.google.java.contract.core.model.ClassName; -import com.google.java.contract.util.Iterables; -import com.google.java.contract.util.Predicates; import java.util.TreeMap; @@ -46,7 +44,11 @@ import java.util.TreeMap; * @author [email protected] (Nhat Minh Lê) * @param <R> the type of a rule */ -@AllowUnusedImport({ Iterables.class, Predicates.class }) +@ContractImport({ + "com.google.java.contract.core.model.ClassName", + "com.google.java.contract.util.Iterables", + "com.google.java.contract.util.Predicates" +}) @Invariant("root != null") public class PatternMap<R> { /** diff --git a/src/com/google/java/contract/core/util/SyntheticJavaFile.java b/src/com/google/java/contract/core/util/SyntheticJavaFile.java index 08e8bd0..b2553d5 100644 --- a/src/com/google/java/contract/core/util/SyntheticJavaFile.java +++ b/src/com/google/java/contract/core/util/SyntheticJavaFile.java @@ -18,11 +18,9 @@ */ package com.google.java.contract.core.util; -import com.google.java.contract.AllowUnusedImport; +import com.google.java.contract.ContractImport; import com.google.java.contract.Invariant; import com.google.java.contract.Requires; -import com.google.java.contract.util.Iterables; -import com.google.java.contract.util.Predicates; import java.io.ByteArrayInputStream; import java.io.InputStream; @@ -35,7 +33,10 @@ import javax.tools.SimpleJavaFileObject; * * @author [email protected] (Nhat Minh Lê) */ -@AllowUnusedImport({ Iterables.class, Predicates.class }) +@ContractImport({ + "com.google.java.contract.util.Iterables", + "com.google.java.contract.util.Predicates" +}) @Invariant({ "content != null", "lineNumberMap == null " + diff --git a/test/com/google/java/contract/tests/ConstantContracts.java b/test/com/google/java/contract/tests/ConstantContracts.java index 291c759..bb23682 100644 --- a/test/com/google/java/contract/tests/ConstantContracts.java +++ b/test/com/google/java/contract/tests/ConstantContracts.java @@ -20,6 +20,7 @@ package com.google.java.contract.tests; import com.google.java.contract.Ensures; import com.google.java.contract.Requires; +import com.google.java.contract.ThrowEnsures; /** * This class exposes some constant contracts. @@ -67,4 +68,27 @@ class ConstantContracts { @Ensures("true") public void postSuccess3() { } + + @Ensures("old (true)") + public void oldSuccess() { + } + + @Ensures({ "old (true)", "old (true)" }) + public void oldSuccess1() { + } + + @Ensures("old (true)") + @ThrowEnsures({ "RuntimeException", "old (true)" }) + public void oldSuccess2() { + } + + @Ensures("old (true)") + @ThrowEnsures({ "RuntimeException", "old (true)" }) + public void oldSuccess3() { + throw new RuntimeException(); + } + + @Ensures("old (false)") + public void oldFailure() { + } } diff --git a/test/com/google/java/contract/tests/ConstantContractsTest.java b/test/com/google/java/contract/tests/ConstantContractsTest.java index 060afcd..8b78e33 100644 --- a/test/com/google/java/contract/tests/ConstantContractsTest.java +++ b/test/com/google/java/contract/tests/ConstantContractsTest.java @@ -91,4 +91,34 @@ public class ConstantContractsTest extends TestCase { public void testPostSuccess3() { sample.postSuccess3(); } + + public void testOldSuccess() { + sample.oldSuccess(); + } + + public void testOldSuccess1() { + sample.oldSuccess1(); + } + + public void testOldSuccess2() { + sample.oldSuccess2(); + } + + public void testOldSuccess3() { + try { + sample.oldSuccess3(); + fail(); + } catch (RuntimeException expected) { + /* Expected. */ + } + } + + public void testOldFailure() { + try { + sample.oldFailure(); + fail(); + } catch (PostconditionError expected) { + /* Expected. */ + } + } } diff --git a/test/com/google/java/contract/tests/ConstructorTest.java b/test/com/google/java/contract/tests/ConstructorTest.java index 39dd1ef..d15ddd5 100644 --- a/test/com/google/java/contract/tests/ConstructorTest.java +++ b/test/com/google/java/contract/tests/ConstructorTest.java @@ -23,7 +23,6 @@ import com.google.java.contract.PreconditionError; import com.google.java.contract.Requires; import junit.framework.TestCase; -import java.io.FileInputStream; import java.io.FileNotFoundException; /** @@ -82,10 +81,11 @@ public class ConstructorTest extends TestCase { } } - private static class In extends FileInputStream { - @Requires("!name.equals(\"/dev/null\")") + private static class In { + @Requires("!\"/dev/null\".equals(name)") public In(String name) throws FileNotFoundException { - super(name); + if (!"/dev/null".equals(name)) + throw new FileNotFoundException(name); } } @@ -154,7 +154,7 @@ public class ConstructorTest extends TestCase { In in = new In("/dev/null"); fail(); } catch (PreconditionError expected) { - assertEquals("[!name.equals(\"/dev/null\")]", expected.getMessages().toString()); + assertEquals("[!\"/dev/null\".equals(name)]", expected.getMessages().toString()); } } } diff --git a/test/com/google/java/contract/tests/GenericsTest.java b/test/com/google/java/contract/tests/GenericsTest.java index f5f576c..70f9691 100644 --- a/test/com/google/java/contract/tests/GenericsTest.java +++ b/test/com/google/java/contract/tests/GenericsTest.java @@ -107,7 +107,7 @@ public class GenericsTest extends TestCase { } } - private static class T<X extends Throwable> { + private static class TT<X extends Throwable> { /* Test ability to contract methods that throw type parameters. */ @Requires("true") public void f(X x) throws X { @@ -115,7 +115,7 @@ public class GenericsTest extends TestCase { } } - private static class Npe extends T<NullPointerException> { + private static class Npe extends TT<NullPointerException> { @Override @Ensures("true") public void f(NullPointerException x) throws NullPointerException { diff --git a/test/com/google/java/contract/tests/Java8StreamTest.java b/test/com/google/java/contract/tests/Java8StreamTest.java new file mode 100644 index 0000000..e2b2286 --- /dev/null +++ b/test/com/google/java/contract/tests/Java8StreamTest.java @@ -0,0 +1,97 @@ +/* + * Copyright 2014 Nhat Minh Lê + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ +package com.google.java.contract.tests; + +import com.google.java.contract.Invariant; +import com.google.java.contract.InvariantError; +import com.google.java.contract.PreconditionError; +import com.google.java.contract.Requires; +import junit.framework.TestCase; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +/** + * Tests Java 8 lambdas on collections in contracts. + * + * @author [email protected] (Nhat Minh Lê) + */ +public class Java8StreamTest extends TestCase { + @Invariant("xs.stream().allMatch(x -> x >= 0)") + private static class A { + protected List<Integer> xs; + private int k; + + public A(Collection<Integer> xs, int k) { + /* Bogus if xs contains negative elements. */ + this.xs = new ArrayList(xs); + this.k = k; + } + + @Requires("xs.stream().reduce(0, (x, y) -> x + y) % 2 == 0") + public static boolean onlyEven(Collection<Integer> xs) { + return true; + } + + @Requires("xs.stream().reduce(0, (x, y) -> k + x + y) % 2 == 0") + public boolean instanceCapture() { + return true; + } + } + + public void testA() { + A b = new A(Arrays.asList(1, 2, 3, 4), 42); + } + + public void testABogus() { + try { + A b = new A(Arrays.asList(1, 2, 3, -4), 42); + } catch (InvariantError expected) { + assertEquals("[xs.stream().allMatch(x -> x >= 0)]", + expected.getMessages().toString()); + } + } + + public void testOnlyEven() { + boolean b = A.onlyEven(Arrays.asList(2, 3, 3)); + } + + public void testOnlyEvenInvalid() { + try { + boolean b = A.onlyEven(Arrays.asList(2, 3, 4)); + } catch (PreconditionError expected) { + assertEquals("[xs.stream().reduce(0, (x, y) -> x + y) % 2 == 0]", + expected.getMessages().toString()); + } + } + + public void testInstanceCapture() { + boolean b = new A(Arrays.asList(2, 3, 4), 11).instanceCapture(); + } + + public void testInstanceCaptureInvalid() { + try { + boolean b = new A(Arrays.asList(2, 3, 3), 11).instanceCapture(); + } catch (PreconditionError expected) { + assertEquals("[xs.stream().reduce(0, (x, y) -> k + x + y) % 2 == 0]", + expected.getMessages().toString()); + } + } +} -- Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/libcofoja-java.git _______________________________________________ pkg-java-commits mailing list [email protected] http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/pkg-java-commits

