开发新的静态分析仪:PVS-Studio Java

图片3

PVS-Studio静态分析仪在C,C ++和C#领域广为人知,可作为检测错误和潜在漏洞的工具。 但是,由于来自Java和IBM RPG(!)的需求,我们在金融领域的客户很少。 但是我们一直想与企业界保持紧密联系,因此,经过一番思考,我们决定开始创建Java分析器。

引言


当然,有人担心。 在IBM RPG中轻松占领分析仪市场。 我完全不确定是否有不错的工具可以对这种语言进行静态分析。 在Java世界中,情况完全不同。 已经有一系列用于静态分析的工具,并且要取得成功,您需要创建一个功能强大且非常酷的分析器。

尽管如此,我们公司还是有使用几种工具进行Java静态分析的经验,并且我们确信我们可以做得更好。

另外,我们有一个想法,如何在Java分析器中使用C ++分析器的全部功能。 但是首先是第一件事。


图片6


首先,有必要决定如何获取语法树和语义模型。

语法树是构建分析器的基本元素。 执行检查时,分析器将遍历语法树并检查其各个节点。 没有这样的树,几乎不可能进行认真的静态分析。 例如,使用正则表达式搜索错误是没有希望的

值得注意的是,仅语法树是不够的。 分析器还需要语义信息。 例如,我们需要知道树的所有元素的类型,能够去变量声明,等等。

我们研究了获取语法树和语义模型的几种方法:


我们几乎放弃了使用ANTLR的想法,因为这将不必要地使分析器的开发复杂化(语义分析将必须由我们自己实施)。 最后,我们决定停在Spoon库中:

  • 它不仅是解析器,而且是整个生态系统-它不仅提供了解析树,而且还提供了语义分析的机会,例如,它使您可以获得有关变量类型的信息,进入变量声明,获取有关父类的信息等等。
  • 它基于Eclipse JDT并能够编译代码。
  • 它支持Java的最新版本,并且会不断更新。
  • 好的文档和清晰的API。

这是Spoon提供的元模型的示例,在创建诊断规则时我们将使用它:

图片10


此元模型对应于以下代码:
class TestClass { void test(int a, int b) { int x = (a + b) * 4; System.out.println(x); } } 

Spoon的优点之一是它简化了语法树(删除和添加节点),使其更易于使用。 同时,保证了原始简化元模型的语义等效性。

对于我们来说,这意味着,例如,我们不再需要担心在遍历树时跳过多余的括号。 此外,每个表达式都放在一个块中,显示导入,并进行一些其他类似的简化。

例如,如下代码:

 for (int i = ((0)); (i < 10); i++) if (cond) return (((42))); 

将显示如下:

 for (int i = 0; i < 10; i++) { if (cond) { return 42; } } 

基于语法树,执行所谓的基于模式的分析。 这是使用众所周知的错误代码模式来搜索程序源代码中的错误的步骤。 在最简单的情况下,分析器会根据相应诊断中描述的规则在树中查找看起来像错误的位置。 这种模式的数量很大,并且它们的复杂性可以有很大的不同。

通过基于模式的分析检测到的错误的最简单示例是jMonkeyEngine项目中的以下代码:

 if (p.isConnected()) { log.log(Level.FINE, "Connection closed:{0}.", p); } else { log.log(Level.FINE, "Connection closed:{0}.", p); } 

if语句thenelse块相同;很可能存在逻辑错误。

这是Hive项目中的另一个类似示例:

 if (obj instanceof Number) { // widening conversion return ((Number) obj).doubleValue(); } else if (obj instanceof HiveDecimal) { // <= return ((HiveDecimal) obj).doubleValue(); } else if (obj instanceof String) { return Double.valueOf(obj.toString()); } else if (obj instanceof Timestamp) { return new TimestampWritable((Timestamp)obj).getDouble(); } else if (obj instanceof HiveDecimal) { // <= return ((HiveDecimal) obj).doubleValue(); } else if (obj instanceof BigDecimal) { return ((BigDecimal) obj).doubleValue(); } 

该代码以if(....)else if(....)else if(....)的形式包含两个相同的条件。 值得检查代码的此部分是否存在逻辑错误,或删除重复的代码。

数据流分析


除了语法树和语义模型之外,分析器还需要一种机制来分析数据流

通过数据流分析,您可以计算程序中每个点的变量和表达式的有效值,并借此发现错误。 我们称这些有效值为虚值。

首次提及时,将为变量,类字段,方法参数和其他内容创建虚拟值。 如果这是一个赋值,则数据流机制将通过分析右侧的表达式来计算虚拟值,否则将此变量的整个有效值范围视为虚拟值。 例如:

 void func(byte x) // x: [-128..127] { int y = 5; // y: [5] ... } 

每次变量值更改时,数据流引擎都会重新计算虚拟值。 例如:

 void func() { int x = 5; // x: [5] x += 7; // x: [12] ... } 

数据流引擎还处理控制语句:

 void func(int x) // x: [-2147483648..2147483647] { if (x > 3) { // x: [4..2147483647] if (x < 10) { // x: [4..9] } } else { // x: [-2147483648..3] } ... } 

在此示例中,当输入函数时,没有关于变量x的值范围的信息,因此根据变量的类型(从-2147483648到2147483647)进行设置。 然后,第一个条件块施加一个约束x > 3,并且合并范围。 结果,在then块中, x的值范围是4到2147483647,在else块中是-2147483648到3。以相同的方式处理第二个条件x <10。

另外,您必须能够执行纯符号计算。 最简单的例子:

 void f1(int a, int b, int c) { a = c; b = c; if (a == b) // <= always true .... } 

在此,变量a被赋值为c ,变量b也被赋值为c ,之后比较ab 。 在这种情况下,要找到错误,只需记住与右侧相对应的那一块木头即可。

这是一个稍复杂的字符计算示例:

 void f2(int a, int b, int c) { if (a < b) { if (b < c) { if (c < a) // <= always false .... } } } 

在这种情况下,必须解决符号形式的不等式系统。

数据流机制可帮助分析仪找到使用基于模式的分析很难发现的错误。

这些错误包括:

  • 溢出;
  • 出国阵列;
  • 通过空引用或可能为空的引用进行访问;
  • 无意义的条件(总是对/错);
  • 内存和资源泄漏;
  • 除以0;
  • 还有一些。

搜索漏洞时,数据流分析尤其重要。 例如,如果某个程序接收到来自用户的输入,则很可能该输入将用于导致拒绝服务或获得对系统的控制。 示例包括导致某些输入缓冲区溢出(例如,SQL注入)的错误。 在这两种情况下,为了使静态分析器能够检测到此类错误和漏洞,有必要监视数据流和变量的可能值。

我必须说,数据流分析机制是一个复杂而广泛的机制,在本文中,我仅涉及了非常基础的内容。

让我们看一些可以使用数据流机制检测到的错误的示例。

蜂巢项目:

 public static boolean equal(byte[] arg1, final int start1, final int len1, byte[] arg2, final int start2, final int len2) { if (len1 != len2) { // <= return false; } if (len1 == 0) { return true; } .... if (len1 == len2) { // <= .... } } 

由于上面已经执行了相反的检查,因此始终满足条件len1 == len2

来自同一项目的另一个示例:

 if (instances != null) { // <= Set<String> oldKeys = new HashSet<>(instances.keySet()); if (oldKeys.removeAll(latestKeys)) { .... } this.instances.keySet().removeAll(oldKeys); this.instances.putAll(freshInstances); } else { this.instances.putAll(freshInstances); // <= } 

在此,在else块中,确保发生空指针的解除引用。 注意:这里的实例this.instances相同。

JMonkeyEngine项目的示例:

 public static int convertNewtKey(short key) { .... if (key >= 0x10000) { return key - 0x10000; } return 0; } 

此处将变量与数字65536进行比较,但是它的类型为short ,并且short的最大可能值为32767。因此,永远不会满足该条件。

Jenkins项目的一个示例:
 public final R getSomeBuildWithWorkspace() { int cnt = 0; for (R b = getLastBuild(); cnt < 5 && b ! = null; b = b.getPreviousBuild()) { FilePath ws = b.getWorkspace(); if (ws != null) return b; } return null; } 

在此代码中引入了cnt变量,以将通过次数限制为五次,但是却忘记增加它的次数,因此检查无效。

注释机制


另外,分析器需要注释机制。 批注是一种标记系统,除了可以通过分析其签名获得的信息之外,还为分析器提供有关所用方法和类的其他信息。 标记是手动完成的,这是一个漫长而费力的过程,因为要获得最佳结果,必须注释大量Java语言的标准类和方法。 注释流行的库也很有意义。 通常,注释可以被视为分析器有关标准方法和类的合同的知识库。

这是一个可以使用注释检测到的错误的小示例:

 int test(int a, int b) { ... return Math.max(a, a); } 

在此示例中,由于输入错误,将相同的变量作为第二个参数作为第一个参数传递给Math.max方法。 这样的表达是毫无意义和可疑的。

知道Math.max方法的参数应始终不同,因此静态分析器将能够对此类代码发出警告。

展望未来,我将给出一些内置类和方法(C ++代码)标记的示例:

 Class("java.lang.Math") - Function("abs", Type::Int32) .Pure() .Set(FunctionClassification::NoDiscard) .Returns(Arg1, [](const Int &v) { return v.Abs(); }) - Function("max", Type::Int32, Type::Int32) .Pure() .Set(FunctionClassification::NoDiscard) .Requires(NotEquals(Arg1, Arg2) .Returns(Arg1, Arg2, [](const Int &v1, const Int &v2) { return v1.Max(v2); }) Class("java.lang.String", TypeClassification::String) - Function("split", Type::Pointer) .Pure() .Set(FunctionClassification::NoDiscard) .Requires(NotNull(Arg1)) .Returns(Ptr(NotNullPointer)) Class("java.lang.Object") - Function("equals", Type::Pointer) .Pure() .Set(FunctionClassification::NoDiscard) .Requires(NotEquals(This, Arg1)) Class("java.lang.System") - Function("exit", Type::Int32) .Set(FunctionClassification::NoReturn) 

说明:

  • -带注释的类;
  • 功能 -带注释的类的方法;
  • -注释表明该方法是干净的,即 确定性且无副作用;
  • 设置 -为该方法设置一个任意标志。
  • FunctionClassification :: NoDiscard-一个标志,表示必须使用方法的返回值;
  • FunctionClassification :: NoReturn-一个标志,指示方法不返回控制;
  • Arg1Arg2...ArgN-方法的参数;
  • 返回 -方法的返回值;
  • 需要 -该方法的合同。

值得注意的是,除了手动标记外,还有另一种注释方法-基于字节码的合同自动输出。 显然,此方法仅允许您显示某些类型的合同,但是它使得从所有依赖项(而不是仅从手动注释的依赖项)中获取一般信息成为可能。

顺便说一句,已经有一个工具可以显示基于字节码-FABA的合同,如@NullableNotNull 。 据我了解,IntelliJ IDEA中使用了FABA衍生物。

现在,我们还考虑添加字节码分析以获取所有方法的协定,因为这些协定可以很好地补充我们的手动注释。

工作中的诊断规则通常引用注释。 除了诊断之外,注释还使用数据流机制。 例如,使用java.lang.Math.abs方法的注释,它可以准确地计算数字模块的值。 同时,您不必编写任何其他代码-只需正确标记方法即可。

考虑一个可以通过注释检测到的Hibernate项目错误示例:

 public boolean equals(Object other) { if (other instanceof Id) { Id that = (Id) other; return purchaseSequence.equals(this.purchaseSequence) && that.purchaseNumber == this.purchaseNumber; } else { return false; } } 

在此代码中, equals()方法将PurchaseSequence对象与其自身进行比较。 当然这是一个错字,右边应该是那个。purchaseSequence ,而不是purchaseSequence

Frankenstein博士如何从零件组装分析仪


图片2


由于数据流和注释本身的机制与特定语言的联系不是很紧密,因此决定从我们的C ++分析器中重用这些机制。 这使我们能够快速获得Java分析器中C ++分析器内核的所有功能。 此外,该决定还受到以下事实的影响:这些机制是用现代C ++编写的,具有大量的元编程和模板魔术,因此并不十分适合转换为另一种语言。

为了将Java部分与C ++内核相关联,我们决定使用SWIG(简化包装器和接口生成器) -一种自动生成包装器和接口的工具,用于将C和C ++程序与用其他语言编写的程序链接起来。 对于Java,SWIG生成JNI(Java本机接口)代码。

SWIG非常适合需要将大量C ++代码集成到Java项目中的情况。

我将举一个使用SWIG的简单示例。 假设我们有一个要在Java项目中使用的C ++类:

CoolClass.h

 class CoolClass { public: int val; CoolClass(int val); void printMe(); }; 

CoolClass.cpp

 #include <iostream> #include "CoolClass.h" CoolClass::CoolClass(int v) : val(v) {} void CoolClass::printMe() { std::cout << "val: " << val << '\n'; } 

首先,您需要创建一个SWIG接口文件 ,其中包含所有导出函数和类的描述。 同样,在此文件中,如有必要,还会进行其他设置。

范例

 %module MyModule %{ #include "CoolClass.h" %} %include "CoolClass.h" 

之后,您可以运行SWIG:

 $ swig -c++ -java Example.i 

它将生成以下文件:

  • CoolClass.java-我们将直接在Java项目中使用的类;
  • MyModule.java-在其中放置所有自由函数和变量的模块类;
  • MyModuleJNI.java-Java包装器;
  • Example_wrap.cxx-C ++包装器。

现在,您只需要将生成的.java文件添加到Java项目中,并将.cxx文件添加到C ++项目中。

最后,您需要将C ++项目编译为动态库,并使用System.loadLibary()将其加载到Java项目中:

App.java

 class App { static { System.loadLibary("example"); } public static void main(String[] args) { CoolClass obj = new CoolClass(42); obj.printMe(); } } 

可以用以下方式表示:

图片8


当然,在一个真实的项目中,一切并不是那么简单,您需要付出更多的努力:

  • 为了使用C ++中的模板类和方法,必须使用%template指令为所有接受的模板参数实例化它们。
  • 在某些情况下,您可能需要捕获Java部分中C ++部分抛出的异常。 默认情况下,SWIG不会捕获来自C ++的异常(发生段错误),但是可以使用%exception指令执行此操作。
  • SWIG允许您使用%extend指令在Java端扩展plus代码。 例如,在我们的项目中,我们将toString()方法添加到虚拟值,以便我们可以在Java调试器中查看它们。
  • 为了从C ++模拟RAII行为,在所有感兴趣的类中都实现了AutoClosable接口。
  • 导演机制允许使用跨语言多态性;
  • 对于仅在C ++内部(在其内存池中)分配的类型,将删除构造函数和终结器以提高性能。 垃圾收集器将忽略这些类型。

您可以在SWIG文档中阅读有关所有这些机制的更多信息。

我们的分析器是使用gradle构建的,该gradle调用CMake,后者依次调用SWIG并编译C ++部分。 对于程序员而言,这几乎是无法察觉的,因此我们在开发过程中不会遇到任何特殊的麻烦。

我们的C ++分析器的核心是在Windows,Linux,macOS下构建的,因此Java分析器也可以在这些OS中使用。

什么是诊断规则?


诊断程序本身和分析代码均使用Java编写。 这是由于与Spoon的紧密交互。 每个诊断规则都是一个访问者,其方法很繁琐,其中规避了我们感兴趣的元素:

图片9

例如,V6004诊断框架如下所示:

 class V6004 extends PvsStudioRule { .... @Override public void visitCtIf(CtIf ifElement) { // if ifElement.thenStatement statement is equivalent to // ifElement.elseStatement statement => add warning V6004 } } 

外挂程式


为了将静态分析器最轻松地集成到项目中,我们为Maven和Gradle组装系统开发了插件。 用户只能将我们的插件添加到项目中。

对于Gradle:

 .... apply plugin: com.pvsstudio.PvsStudioGradlePlugin pvsstudio { outputFile = 'path/to/output.json' .... } 

对于Maven:

 .... <plugin> <groupId>com.pvsstudio</groupId> <artifactId>pvsstudio-maven-plugin</artifactId> <version>0.1</version> <configuration> <analyzer> <outputFile>path/to/output.json</outputFile> .... </analyzer> </configuration> </plugin> 

之后,插件将独立接收项目结构并开始分析。

此外,我们还为IntelliJ IDEA开发了原型插件。

图片1

此插件还可以在Android Studio中使用。

Eclipse的插件目前正在开发中。

增量分析


我们提供了一种增量分析模式,该模式允许您仅检查已修改的文件,从而大大减少了代码分析所需的时间。 因此,开发人员将能够根据需要经常运行分析。

增量分析包括以下几个阶段:

  • 勺子元模型缓存;
  • 重建元模型的更改部分;
  • 分析更改的文件。

我们的测试系统


为了在实际项目上测试Java分析器,我们编写了一个特殊的工具包,使您可以使用开放项目的数据库。 它是用^ W Python + Tkinter编写的,是跨平台的。

其工作方式如下:

  • 从GitHub上的存储库下载特定版本的测试项目;
  • 该项目正在组装中;
  • 我们的插件被添加到pom.xmlbuild.gradle (使用git apply);
  • 使用插件启动静态分析器;
  • 将生成的报告与该项目的基准进行比较。

这种方法可确保不会因更改分析器代码而损失良好的响应。 以下是我们的测试实用程序的界面。

图片11

报告中与标准有任何差异的项目用红色标记。 批准按钮允许您将报告的当前版本保存为参考。

错误示例


按照惯例,我会引用Java分析器发现的各种打开的项目中的一些错误。 将来,计划编写有关每个项目的文章以及更详细的报告。

休眠项目


PVS-Studio警告: V6009函数“等于”接收到奇数参数。 检查参数:this,1. PurchaseRecord.java 57

 public boolean equals(Object other) { if (other instanceof Id) { Id that = (Id) other; return purchaseSequence.equals(this.purchaseSequence) && that.purchaseNumber == this.purchaseNumber; } else { return false; } } 

在此代码中, equals()方法将PurchaseSequence对象与其自身进行比较。 最有可能的是这是一个错字,右边应该是that.purchaseSequence ,而不是purchaseSequence

PVS-Studio警告: V6009函数“等于”接收到奇数参数。 检查参数:this,1. ListHashcodeChangeTest.java 232

 public void removeBook(String title) { for( Iterator<Book> it = books.iterator(); it.hasNext(); ) { Book book = it.next(); if ( title.equals( title ) ) { it.remove(); } } } 

与上一个操作类似的操作 -右侧应该是book.title ,而不是title

蜂巢项目


PVS-Studio警告 V6007表达式'colOrScalar1.equals(“ Column”)'始终为false。 GenVectorCode.java 2768

PVS-Studio警告 V6007表达式'colOrScalar1.equals(“ Scalar”)'始终为false。 GenVectorCode.java 2774

PVS-Studio警告 V6007表达式'colOrScalar1.equals(“ Column”)'始终为false。 GenVectorCode.java 2785

 String colOrScalar1 = tdesc[4]; .... if (colOrScalar1.equals("Col") && colOrScalar1.equals("Column")) { .... } else if (colOrScalar1.equals("Col") && colOrScalar1.equals("Scalar")) { .... } else if (colOrScalar1.equals("Scalar") && colOrScalar1.equals("Column")) { .... } 

此处的运算符显然很混乱,而不是“ ||” 用' &&'

JavaParser项目


PVS-Studio警告 V6001在'&&'运算符的左侧和右侧有相同的子表达式'tokenRange.getBegin()。GetRange()。IsPresent()'。 节点java 213

 public Node setTokenRange(TokenRange tokenRange) { this.tokenRange = tokenRange; if (tokenRange == null || !(tokenRange.getBegin().getRange().isPresent() && tokenRange.getBegin().getRange().isPresent())) { range = null; } else { range = new Range( tokenRange.getBegin().getRange().get().begin, tokenRange.getEnd().getRange().get().end); } return this; } 

分析器发现,相同的表达式在&&运算符的左侧和右侧(而调用链中的所有方法都是干净的)。 在第二种情况下,很有可能需要使用tokenRange.getEnd()而不是tokenRange.getBegin()

PVS-Studio警告: V6016通过循环内的常量索引可疑访问'typeDeclaration.getTypeParameters()'对象的元素。ResolvedReferenceType.java 265

 if (!isRawType()) { for (int i=0; i<typeDeclaration.getTypeParams().size(); i++) { typeParametersMap.add( new Pair<>(typeDeclaration.getTypeParams().get(0), typeParametersValues().get(i))); } } 

分析器在循环内部以恒定索引检测到可疑访问收集项目的情况。该代码可能有错误。

詹金斯计划


PVS-Studio警告 V6007表达式'cnt <5'始终为true。抽象项目.java 557

 public final R getSomeBuildWithWorkspace() { int cnt = 0; for (R b = getLastBuild(); cnt < 5 && b ! = null; b = b.getPreviousBuild()) { FilePath ws = b.getWorkspace(); if (ws != null) return b; } return null; } 

在此代码中引入了cnt变量,以将通过次数限制为五次,但是却忘记增加它的次数,因此检查无效。

星火计划


PVS-Studio警告 V6007表达式'sparkApplications!= Null'始终为true。SparkFilter.java 127

 if (StringUtils.isNotBlank(applications)) { final String[] sparkApplications = applications.split(","); if (sparkApplications != null && sparkApplications.length > 0) { ... } } 

检查split方法返回结果是否为是没有意义的,因为此方法始终返回一个集合,而从不返回null

勺子项目


PVS-Studio警告 V6001在'&&'运算符的左侧和右侧有相同的子表达式'!M.getSimpleName()。StartsWith(“ set”)'。SpoonTestHelpers.java 108

 if (!m.getSimpleName().startsWith("set") && !m.getSimpleName().startsWith("set")) { continue; } 

在这段代码中,相同的表达式位于&&运算符的左侧和右侧(调用链中的所有方法都是干净的)。该代码很可能包含逻辑错误。

PVS-Studio警告 V6007表达式'idxOfScopeBoundTypeParam> = 0'始终为true。MethodTypingContext.java 243

 private boolean isSameMethodFormalTypeParameter(....) { .... int idxOfScopeBoundTypeParam = getIndexOfTypeParam(....); if (idxOfScopeBoundTypeParam >= 0) { // <= int idxOfSuperBoundTypeParam = getIndexOfTypeParam(....); if (idxOfScopeBoundTypeParam >= 0) { // <= return idxOfScopeBoundTypeParam == idxOfSuperBoundTypeParam; } } .... } 

在这里,它们在第二种条件下密封,而不是idxOfSuperBoundTypeParam编写idxOfScopeBoundTypeParam

春季安全项目


PVS-Studio警告: V6001在“ ||”的左侧和右侧有相同的子表达式 操作员。检查行:38,39。AnyRequestMatcher.java 38

 @Override @SuppressWarnings("deprecation") public boolean equals(Object obj) { return obj instanceof AnyRequestMatcher || obj instanceof security.web.util.matcher.AnyRequestMatcher; } 

该操作类似于上一个操作-在这里,同一类的名称以不同的方式编写。

PVS-Studio警告: V6006已创建对象,但未使用该对象。'throw'关键字可能丢失。DigestAuthenticationFilter.java 434

 if (!expectedNonceSignature.equals(nonceTokens[1])) { new BadCredentialsException( DigestAuthenticationFilter.this.messages .getMessage("DigestAuthenticationFilter.nonceCompromised", new Object[] { nonceAsPlainText }, "Nonce token compromised {0}")); } 

在这段代码中,他们忘记在异常之前添加throw结果,抛出BadCredentialsException异常对象,但未以任何方式使用该对象,即 不会引发异常。

PVS-Studio警告: V6030该方法位于“ |”右侧 无论左操作数的值如何,都将调用运算符。也许最好使用“ ||”。RedirectUrlBuilder.java 38

 public void setScheme(String scheme) { if (!("http".equals(scheme) | "https".equals(scheme))) { throw new IllegalArgumentException("..."); } this.scheme = scheme; } 

在此代码中,|的使用 这是不合理的,因为使用时将计算右侧,即使左侧已经为真。在这种情况下,这没有任何实际意义,因此运算符| 值得替换为||

IntelliJ IDEA项目


PVS-Studio警告: V6008可能会取消引用“编辑器”。IntroduceVariableBase.java:609

 final PsiElement nameSuggestionContext = editor == null ? null : file.findElementAt(...); // <= final RefactoringSupportProvider supportProvider = LanguageRefactoringSupport.INSTANCE.forLanguage(...); final boolean isInplaceAvailableOnDataContext = supportProvider != null && editor.getSettings().isVariableInplaceRenameEnabled() && // <= ... 

分析器检测到在此代码中可能会取消引用编辑器的空指针值得添加额外的支票。

PVS-Studio警告: V6007表达式始终为false。RefResolveServiceImpl.java:814

 @Override public boolean contains(@NotNull VirtualFile file) { .... return false & !myProjectFileIndex.isUnderSourceRootOfType(....); } 

我很难说出作者的想法,但是这样的代码看起来非常可疑。即使这里突然没有错误,我也认为有必要重写此地方,以免混淆分析仪和其他程序员。

PVS-Studio警告 V6007表达式'结果[0]'始终为false。CopyClassesHandler.java:298

 final boolean[] result = new boolean[] {false}; // <= Runnable command = () -> { PsiDirectory target; if (targetDirectory instanceof PsiDirectory) { target = (PsiDirectory)targetDirectory; } else { target = WriteAction.compute(() -> ((MoveDestination)targetDirectory).getTargetDirectory( defaultTargetDirectory)); } try { Collection<PsiFile> files = doCopyClasses(classes, map, copyClassName, target, project); if (files != null) { if (openInEditor) { for (PsiFile file : files) { CopyHandler.updateSelectionInActiveProjectView( file, project, selectInActivePanel); } EditorHelper.openFilesInEditor( files.toArray(PsiFile.EMPTY_ARRAY)); } } } catch (IncorrectOperationException ex) { Messages.showMessageDialog(project, ex.getMessage(), RefactoringBundle.message("error.title"), Messages.getErrorIcon()); } }; CommandProcessor processor = CommandProcessor.getInstance(); processor.executeCommand(project, command, commandName, null); if (result[0]) { // <= ToolWindowManager.getInstance(project).invokeLater(() -> ToolWindowManager.getInstance(project) .activateEditorComponent()); } 

我怀疑他们在这里忘记了以某种方式改变结果的价值因此,分析仪报告检查(结果[0])是否没有意义。

结论


Java方向非常灵活-它是台式机,Android,Web等等,因此我们有很多活动空间。当然,首先,我们将开发最需要的那些领域。

这是我们不久的将来的计划:

  • 根据字节码输出注释;
  • 集成到Ant的项目中(其他人会在2018年使用它吗?);
  • Eclipse插件(正在开发中);
  • 甚至更多的诊断和注释;
  • 改进数据流机制。

我还建议那些希望在Java分析器的alpha版本可用时参与测试的人。要做到这一点,写信给我们支持我们将您的联系人添加到列表中,并在准备第一个Alpha版本时给您写信。


如果您想与讲英语的读者分享这篇文章,请使用翻译链接:Egor Bredikhin。开发新的静态分析仪:

您阅读文章并有疑问吗?
通常我们的文章会被问同样的问题。我们在这里收集了答案读者对有关PVS-Studio版本2015的文章回答请参阅清单。

Source: https://habr.com/ru/post/zh-CN414669/


All Articles