面向对象第三单元总结
作业要求
一、JML语言理论基础及其工具链
1、JML语言理论基础
JML是用于对Java程序进行规格化设计的一种表示语言,是一种行为接口规格语言,基于Larch方法构建,BISL提供了对方法和类型的规格定义手段。
使用JML来声明性的描述一个方法或类的预期行为可以显著提高整体的开发进程,JML引入了大量用于描述方法行为的结构,例如:模型域、量词、断言可视范围、预处理、后出路、条件继承、正常行为规范以及异常行为规范等。将这些建模标记,以JAVA注释的方式加入到程序代码中,对正常编译没有影响,并且能够精确地描述代码、减少bug的出现以及规范用户对类与方法的使用。JML可用于开展规格化设计,在代码工作开始前规范代码行为,也可针对已有的代码实现书写其相应规格,从而提高可维护性。
2、JML工具链
可以使用开源的JML编译器来编译含有JML标记的代码,所生成的类文件会在运行时自动检查JML规范,若程序未实现规范中规定的事情,JML运行期断言检查编译器会抛出一个unchecked exception来说明程序违背了哪一条规范。JMLdoc工具与Javadoc工具类似,可在生成的HTML格式文档中包含JML规范,JMLUnit可以生成一个Java类文件测试的框架。SMT Solver工具可以以静态方式来检查代码实现对规格的满足情况。
二、部署SMT Solver验证
OpenJML使用SMT Solver来对检查程序实现是否满足所设计的规格(specification)。
1)OpenJML配置
在eclipse的help栏中选择install New Software ,输入网址http://jmlspecs.sourceforge.net/openjml-updatesite进行OpenJML插件的下载。
2)SMT Solver配置
参看讨论区给出的配置教程。(戳我)
3)进行验证
右击xxx.java,选择check ESC栏对代码进行静态检查。
三、部署JMLUnitNG验证
1)配置
更详细过程参考讨论区
1、首先安装OpenJML
- OpenJML 可以在 OpenJML 官方 github 仓库的 Releases 界面处获取
- 解压后将.jar文件和Solover-Windows(在Windows系统下)放在同一文件夹内(我放在F:\jmlunitng文件夹下)
- 在该文件夹下使用命令:
$ java -jar openjml.jar "$@"
完成安装
$ java -jar openjml.jar "$@"
JML options:
-dir Process all files, recursively, within this directory
-dirs Process all files, recursively, within these directories (listed as separate arguments, up to an argument that begins with a - sign)
-- Terminates option processing - all remaining arguments are files
-keys Identifiers for optional JML comments
-command The command to execute (check,esc,rac,compile)
-check Does a JML syntax check [-command=check]
-compile Does a Java-only compile [-command=compile]
-rac Enables generating code instrumented with runtime assertion checks [-command=rac]
-esc Enables static checking [-command=esc]
-boogie Enables static checking with boogie
-java When on, the tool uses only the underlying javac or javadoc compiler (must be the first option)
-jml When on, the JML compiler is used and all JML constructs are ignored; use -no-jml to use OpenJML but ignore JML annotations
-lang Set the language variant to use: jml, javelyn, or jml+ (the default)
-extensions Extension packages and classes (comma-separated qualified names)
-stopIfParseErrors When enabled, stops after parsing if any files have parsing errors
-method Comma-separated list of method name patterns on which to run ESC
-exclude Comma-separated list of method name patterns to exclude from ESC
-prover The prover to use to check verification conditions
-exec The prover executable to use
-logic The SMT logic to use (default ALL)
-nonnullByDefault Makes references non_null by default [-nullableByDefault=false]
-nullableByDefault Makes references nullable by default
-code-math Arithmetic mode for Java code (code, safe, bigint)
-spec-math Arithmetic mode for specifications (code, safe, bigint)
-checkAccessible When on (the default), JML accessible clauses are checked
-specspath Specifies the directory path to search for specification files
-checkSpecsPath When on (the default), warnings for non-existent specification path directories are issued
-purityCheck When on (off by default), warnings for use of impure methods from system libraries are issued
-internalSpecs When on (the default), automatically appends the internal specs directory to the specification path
-internalRuntime When on (the default), automatically appends the internal JML runtime library to the classpath
-timeout Number of seconds to limit any individual proof attempt (default infinite)
-showNotImplemented When on (off by default), warnings about unimplemented constructs are issued
-showNotExecutable When on (off by default), warnings about non-executable constructs are issued
-verboseness Level of verboseness (0=quiet...4=debug)
-quiet Only output warnings and errors [-verboseness=0]
-normal Limited output [-verboseness=1]
-progress Shows progress through compilation phases [-verboseness=2]
-skipped Shows methods whose proofs are skipped
-jmlverbose Like -verbose, but only jml information and not as much [-verboseness=3]
-jmldebug When on, the program emits lots of output (includes -progress) [-verboseness=4]
-showOptions When enabled, the values of options and properties are printed, for debugging
-jmltesting Only used to generate tracing information during testing
-trace ESC: Enables tracing of counterexamples
-show Show intermediate programs
-split Split proof into sections
-escBV ESC: If enabled, use bit-vector arithmetic (auto, true, false)
-triggers ESC: Enable quantifier triggers in SMT encoding (default true)
-escExitInfo ESC: Show exit location for postconditions (default true)
-escMaxWarnings ESC: Maximum number of warnings to find per method
-escMaxWarningsPath ESC: If true, find all counterexample paths to each invalid assert
-counterexample ESC: Enables output of complete, raw counterexample
-ce ESC: Enables output of complete, raw counterexample [-counterexample]
-subexpressions ESC: Enables tracing with subexpressions
-checkFeasibility ESC: Check feasibility of assumptions
-benchmarks ESC: Collects solver communications
-minQuant ESC: Minimizes using quantifications, in favor of inlining
-typeQuants ESC: Introduces quantified assertions for type variables (true, false, or auto)
-modelFieldNoRep RAC action when a model field has no represents clause (zero,ignore,warn)
-racShowSource RAC: Error messages will include source information
-racCheckAssumptions RAC: Enables runtime checking that assumptions hold
-racJavaChecks RAC: Enables explicit checking of Java language checks
-racCompileToJavaAssert RAC: Compiles JML checks as Java asserts
-racPreconditionEntry RAC: Distinguishes Precondition failures on entry calls
-racMissingModelFieldRepSource RAC: action when a model field has no representation (zero,warn,skip)
-racMissingModelFieldRepBinary RAC: action when a model field for a binary class has no representation (zero,warn,skip)
-properties Specifies the path to the properties file
-properties-default Specifies the path to the default properties file
-defaults Specifies various default behaviors: constructor:pure|everything
-staticInitWarning Warns about missing static_initializer clauses
-determinism Experimental: enables better determinism
-osname Name of OS to use in selecting solver executable
-inline-function-literal Whether to inline function literals
-infer Infer missing contracts (postconditions (default), preconditions) [-command=infer]
-infer-debug Enable debugging of contract inference
-infer-tag If true, inferred specifications are tagged with the key INFERRED
-infer-preconditions If not specified, the precondition of methods lacking preconditions will be set to true (otherwise inference is skipped).
-noexit Infer contracts (suppress exiting) [-command=infer-no-exit]
-infer-minimize-expressions Minimize expressions where possible.
-infer-dump-graphs Dump any specification that would have been inferred to a file for offline analysis
-infer-persist Persist inferred specs. If "java" specs are written to the source files. If "jml" (default) they are written to seperate .jml files (defaults to location of class source and can be overridden with -infer-persist-path and -specspath)
-infer-persist-path Specify output directory of specifications (overrides -specspath)
-infer-max-depth The largest CFG we will agree to process
-infer-timeout Give up inference after this many seconds. A value of -1 will wait indefinitely
-infer-dev-mode Special features for developers.
-infer-analysis-types Enables specific analysis types. Takes a comma seperated list of analysis types. Support kinds are: REDUNDANT, UNSAT, TAUTOLOGIES, FRAMES, PURITY, and VISIBILITY
2、安装JMLUnitTNG
- 下载jar包
- 将jar包放在第一步建立的文件夹中,调用命令
$ java -jar jmlunitng.jar "$@"
完成安装
$ java -jar jmlunitng.jar "$@"
JMLUnitNG - Generate TestNG Classes for JML-Annotated Java
java -jar jmlunitng.jar [OPTION] ... path-list
Generates unit tests for all Java source files listed in,
or recursively contained in directories listed in, path-list.
-d, --dest [DIRECTORY] : Use DIRECTORY as the output directory for
generated classes.
-cp , --classpath : Use the given
list of directories and Jar files (formatted as for javac) as the
classpath during parsing (CLASSPATH environment variable, by default).
-sp , --specspath : Use the given
list of directories and Jar files (formatted as for javac) as the
specspath during parsing. (SPECSPATH environment variable, by default).
--rac-version : Generate RAC handling code
for the specified JML RAC version; the default value is 'openjml'
for OpenJML RAC. The other supported values are 'jml2' and 'jml4'
for JML2 and JML4 RAC (respectively).
--deprecation : Generate tests for deprecated methods.
--inherited : Generate tests for inherited methods.
--public : Generate tests only for public methods (default).
--protected : Generate tests for protected and public methods.
--package : Generate tests for package (no protection modifier),
protected and public methods.
--parallel : Generate data providers that default to running in parallel.
This allows multiple tests of the same method to run concurrently, and
can be changed in the test classes after generation.
--reflection : Generate test data reflectively. This can be changed
in the strategy classes after generation.
--children : For all parameters, generate test data using not only the
parameter class but also any children of that class that are explored
when generating the tests. This allows many methods that take
interface/abstract class parameters to be tested automatically.
--literals : Use literals found in classes and methods as default data
values for testing those classes and methods (literals found outside
methods, e.g. in static fields, are used for all methods).
--spec-literals : Use literals found in class and method specifications
as default data values for testing those classes and methods (literals
found in class specifications are used for all methods).
--clean : Remove from the destination path all old JMLUnitNG-
generated files, including any manual modifications. If no
destination path is set, all files and directories in path-list
are cleaned.
--prune : Remove from the destination path any old JMLUnitNG-
generated files for path-list that do not conform to the current
API of the classes under test and the current JMLUnitNG options.
If no destination path is set, all files and directories in
path-list are pruned.
--no-gen : Do not generate tests, use in conjunction with --clean
or --prune to remove unwanted JMLUnitNG-generated files.
--dry-run : Display status/progress information about the operations
that would be performed but do not modify the filesystem.
-v, --verbose : Display status/progress information.
-h, --help : Display this message.
Version: 1.4 (116/OpenJML-20131218-REV3178)
3、文件夹内容
2)进行验证
由于openjml可能还不支持对\exists和\forall表达式的分析和验证问题,以下规格中均不包含\exists和\forall。
step1、创建源文件
以检验Path接口的getNode方法和size()方法为例,在配置过程的demo文件夹下创建Demo.java:
public class Demo {
public ArrayList nodes = new ArrayList<>();
// @ public normal_behaviour
// @ ensures \result = nodes.size();
public /*@pure@*/ int size(){
return nodes.size();
}
// @ public normal_behaviour
// @ requires index >= 0 && index < size();
// @ assignable \nothing;
// @ ensures \result == nodes.get(index);
public /*@pure@*/ int getNode(int index) {
if (index < 0 || index >= size()) {
return -1;
}
return nodes.get(index);
}
public static void main(String[] args) {
Demo example = new Demo();
example.nodes.add(1);
example.nodes.add(2);
int x = example.size();
System.out.println(x);
int y = example.getNode(1);
System.out.println(y);
}
}
step2、生成测试文件
- 生成前的文件树
demo
└── Demo.java
- 调用命令
$ java -jar jmlunitng.jar demo/Demo.java
- 生成后的文件树
demo
├── Demo_InstanceStrategy.java
├── Demo.java
├── Demo_JML_Data
│ ├── ClassStrategy_int.java
│ ├── ClassStrategy_java_lang_String1DArray.java
│ ├── ClassStrategy_java_lang_String.java
│ ├── compare__int_lhs__int_rhs__0__lhs.java
│ ├── compare__int_lhs__int_rhs__0__rhs.java
│ └── main__String1DArray_args__10__args.java
├── Demo_JML_Test.java
├── PackageStrategy_int.java
├── PackageStrategy_java_lang_String1DArray.java
└── PackageStrategy_java_lang_String.java
step3、编译
- 用 javac 编译 JMLUnitNG 的生成文件
执行命令$ javac -cp jmlunitng.jar demo/**/**.java
- 用 jmlc 编译自己的文件,生成带有运行时检查的 class 文件
执行命令$ java -jar openjml.jar -rac demo/Demo.java
- 执行命令
$ javac -cp jmlunitng.jar demo/**.java
step4、运行
- 执行命令
java -cp jmlunitng.jar demo/Demo_JML_Test
- 测试结果的第一行是 racEnabled 的测试,意在检测我们的主文件是否带有 JML 的运行时检查,若没有则跳过所有测试。
验证通过
四、架构设计
1、第一部分--Pathcontainer
mypath构造点集,通过hash值判断点集中是否含有某个节点要比arraylist遍历更快。而且获取path中不同节点个数时,可以直接返回点集的大小。
mypath同样维护一个点集,每次add或remove路径时,更新点集。同样可以通过直接返回点集的大小来获得不同节点的个数。由于set本身的性质,可保证点集中节点互不相等。2、第二部分--Graph
为了通过path来得到图的结构,我采取邻接矩阵的方式来存储整个图,并且在每次图更新操作之后,我都会重新遍历pathlist来重新构造新的邻接矩阵,而不是动态的去修改我的邻接矩阵,这点有待改进。为了构造邻接矩阵,我同时还建立了边集来存储pathlist中所有不同的边。通过边集构造出邻接矩阵后,通过floy算法构造出所有节点的最短路径矩阵。同样因为图更改指令较少,从而使得因为重构所造成的cpu运行时长代价较少,所有的查询指令只需要访问现成的数据结构即可。
3、第三部分--Railwaysystem
1)连通块个数
通过第二部分构造出的距离矩阵,由高等代数的知识我们可知,经过行列变换,距离矩阵可以转化为对角块矩阵,其中对角块的个数就是图中连通块的个数。具体的算法:先用距离矩阵第一行不可达的节点初始化集合,然后从集合中拿出某一个节点,访问其所在行数,删除集合中这个节点可达的节点,以此类推知道集合为空。尤其要注意的是,不能一边遍历集合一边删除,否则会报错。
2)最小换乘次数
通过pathlist构建一张新图,在这张新图里面,每个path中的节点之间的长度均为1(与自己的距离是0)。在这张图中,每两个点间的最小距离-1,即为两节点最小换乘次数了。
3)最少票价
同样是通过pathlist构建一张新图。首先在每个path中算出同一path各个节点的最短距离,然后在所有path的最短距离矩阵中取各个节点距离的最小值构造出初始邻接矩阵。在初始邻接矩阵上,在所有可达节点间路径权值上+2(换乘代价)。然后利用floyd算法计算出节点的最短距离,两点最小票价即为最后所得距离矩阵中权值-2。现在我来说明一下此算法的正确性:初始矩阵得到是同一条path中(即不用换乘的两节点)的最小票价。在此基础上+2,是因为在floy的第一次迭代过程中,如果在两同一path的节点中新加一节点,比较的是+2和+2+2,就可以把换乘的代价考虑进去了,之后的迭代也是这个道理。
4)最少不满意度
与最小票价算法一致,不同的是在每个path中构造最小不满意度矩阵。然后在所有path的最小不满意度矩阵中取各个节点不满意度的最小值构造出初始邻接矩阵。在初始邻接矩阵的基础上+32,然后调用floyd算法。在最终的距离矩阵中查询结果-32即可。
五、bug与修复
1、第一部分
没有仔细考虑算法复杂度的设计。在查询mypath和mypathcontainer中不同节点个数时,采取循环遍历的方式搜索,导致cpu运行时间过长。
2、第二部分
经过第一部分bug的教训,我在图变更指令中就构造出mypath和mypathcontainer的节点集合,以及各节点最短路径矩阵。由于图变更指令占比较少,图中所有信息的构造时间就相对较少。所有的查询指令耗时就比较少。所以我没有出现cpu超时的现象。但是由于我错把^2认为是平方操作,导致我hash值计算出错,从而导致某一条边如果交换节点查询的话,程序会认为不包含此边。所幸造成的影响不是灾难性的。
3、第三部分
经过前两部分的“洗礼”,第三部分未出现bug。
六、心得体会
通过第三单元三节课的学习,明白了类规格,方法规格,数据规格的写法,以及为什么要写规格。方法规格则由前置条件,后置条件和副作用组成,告知方法的实现者如何去实现该方法。规格不需要关注方法具体的实现,即我需要采用怎样的数据结构,怎样的算法。而数据规格则是类有效性的控制条件,constraint和invariant分别定义了数据状态需要满足的条件和数据修改需要满足的条件。
有了规格后,我们可以把一个项目拆分为很多很多的小项目。如果规格撰写是完全正确的话,我们只需要按照各个规格去相应的实现,组织在一起就能满足整体上的需求。
然而,去撰写一个项目的规格是很困难的,我们需要考虑各个方法的前置条件、后置条件和副作用组成,这需要我们考虑到方法可能接受到的各种输入。但是一旦有了正确的规格,项目代码的实现就碎片化了,我们只需要专注每个方法的实现了。理解规格使用规格化设计,能撰写一个完备无误的规格,对项目的开发十分重要。