检查WildFly-JavaEE应用服务器

image1.png


WildFly(以前的JBoss应用服务器)是JBoss在2008年2月创建的开源JavaEE应用服务器。 WildFly项目的主要目标是提供企业Java应用程序通常需要的一组工具。并且由于服务器是用于开发企业应用程序的,因此最小化代码中的错误数量和可能的漏洞尤为重要。 WildFly现在由一家大型公司Red Hat开发,项目代码的质量保持在较高水平,但是分析器仍然设法在项目中发现许多错误。



我叫Dmitry,最近我以Java程序员的身份加入PVS-Studio团队。如您所知,熟悉代码分析器的最佳方法是在实践中进行尝试,因此决定选择一些有趣的项目,进行检查并根据结果撰写一篇有关它的文章。这就是您现在正在阅读的内容。:)



项目分析



为了进行分析,我使用了GitHub上发布的WildFly项目的源代码Cloc在该项目中计数了60万行Java代码,不包括空格和注释。在代码中查找错误是由PVS-Studio进行的PVS-Studio是用于在用C,C ++,C#和Java编写的程序的源代码中搜索错误和潜在漏洞的工具。使用了IntelliJ IDEA 7.09版的分析器插件。



检查项目的结果是,仅收到491个分析器触发器,表明WildFly代码质量良好。其中,113位为高,146位为中。同时,大部分检测都属于诊断:



  • V6002。switch语句未涵盖枚举的所有值。
  • V6008。潜在的空取消引用。
  • V6021 该值已分配给“ x”变量,但未使用。


在本文中,我没有考虑触发这些诊断程序,因为很难理解它们是否实际上是错误。代码作者可以更好地理解此类警告。



接下来,我们将看一下我发现最有趣的10个分析器触发器。问-为什么10?只是因为数字很喜欢。:)



所以,走吧。



警告N1是无用的条件语句



V6004'then '语句等效于'else'语句。WeldPortableExtensionProcessor.java(61),WeldPortableExtensionProcessor.java(65)。



@Override
public void deploy(DeploymentPhaseContext 
phaseContext) throws DeploymentUnitProcessingException {
    final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
    // for war modules we require a beans.xml to load portable extensions
    if (PrivateSubDeploymentMarker.isPrivate(deploymentUnit)) {
        if (!WeldDeploymentMarker.isPartOfWeldDeployment(deploymentUnit)) {
          return;
        }
    } else {
        // if any deployments have a beans.xml we need 
        // to load portable extensions
        // even if this one does not.
        if (!WeldDeploymentMarker.isPartOfWeldDeployment(deploymentUnit)) {
           return;
        }
    }
}


ifelse 分支中的代码相同,并且条件运算符在当前形式下没有意义。很难想到开发人员以这种方式编写此方法的原因。该错误很可能是由于复制粘贴或重构导致的。



N2警告-重复条件



V6007表达式“ poolStatsSize> 0”始终为true。PooledConnectionFactoryStatisticsService.java(85)



@Override
public void start(StartContext context) throws StartException {
  ....
  if (poolStatsSize > 0) {
    if (registration != null) {
      if (poolStatsSize > 0) {
        ....
      }
    }
  }
}


在这种情况下,存在条件重复。这不会影响程序的结果,但是会降低代码的可读性。但是,第二次检查可能还应该包含其他一些更强的条件。



在WildFly中触发此诊断的其他示例:





警告N3-通过空引用进行引用



V6008取消对“ tc”的空引用。ExternalPooledConnectionFactoryService.java(382)



private void createService(ServiceTarget serviceTarget,
         ServiceContainer container) throws Exception {
   ....
   for (TransportConfiguration tc : connectors) {
     if (tc == null) {
        throw MessagingLogger.ROOT_LOGGER.connectorNotDefined(tc.getName());
     }
   }
   ....
}


他们显然在这里搞砸了。首先,我们确保引用为空,然后在这个非常为空的引用上调用getName方法这将导致NullPointerException而不是connectorNotDefined(...)中的预期异常



警告N4-非常奇怪的代码



V6019检测不到代码。可能存在错误。EJB3Subsystem12Parser.java(79)



V6037循环内的无条件“抛出”。EJB3Subsystem12Parser.java(81)



protected void readAttributes(final XMLExtendedStreamReader reader)
   throws XMLStreamException {
    for (int i = 0; i < reader.getAttributeCount(); i++) {
      ParseUtils.requireNoNamespaceAttribute(reader, i);
      throw ParseUtils.unexpectedAttribute(reader, i);
    }
}


一个非常奇怪的设计,两个诊断程序立即对此作出反应:V6019V6037在此,仅执行循​​环的一次迭代,然后通过无条件throw退出这样,如果读者包含至少一个属性,则readAttributes方法将引发异常此循环可以用等效条件替换:



if(reader.getAttributeCount() > 0) {
  throw ParseUtils.unexpectedAttribute(reader, 0);
}


但是,您可以更深入地研究并查看requireNoNamespaceAttribute(....)方法



public static void requireNoNamespaceAttribute
 (XMLExtendedStreamReader reader, int index) 
  throws XMLStreamException {
   if (!isNoNamespaceAttribute(reader, index)) {
        throw unexpectedAttribute(reader, index);
   }
}


发现此方法在内部引发相同的异常。最有可能的是,readAttributes方法应检查是否所有指定属性都不属于任何名称空间,并且不应该缺少这些属性。我想说的是,这种结构的产生是由于代码重构并将异常抛出到requireNoNamespaceAttribute方法中仅查看提交历史记录,就会显示所有这些代码是同时添加的。



警告N5-将参数传递给构造函数



V6022构造函数体内未使用参数'mechanismName'。DigestAuthenticationMechanism.java(144)



public DigestAuthenticationMechanism(final String realmName,
    final String domain, 
    final String mechanismName,
    final IdentityManager identityManager, 
    boolean validateUri) {
       this(Collections.singletonList(DigestAlgorithm.MD5),
            Collections.singletonList(DigestQop.AUTH), 
            realmName, domain, new SimpleNonceManager(), 
            DEFAULT_NAME, identityManager, validateUri);
}


通常,未使用的变量和函数参数不是很关键:通常,它们在重构后会保留下来,或者在将来添加以实现新功能。但是,此操作对我来说似乎很可疑:



public DigestAuthenticationMechanism
  (final List<DigestAlgorithm> supportedAlgorithms, 
   final List<DigestQop> supportedQops,
   final String realmName, 
   final String domain, 
   final NonceManager nonceManager, 
   final String mechanismName, 
   final IdentityManager identityManager,
   boolean validateUri) {....}


如果查看该类的第二个构造函数,则可以看到mechanizmName字符串被假定为第六个参数第一个构造函数采用与第三个参数同名的字符串,然后调用第二个构造函数。但是,不使用此字符串,而是将常量传递给第二个构造函数。也许这里的代码作者计划将机制名而不是DEFAULT_NAME常量传递给构造函数



警告N6-重复行



V6033具有相同键'org.apache.activemq.artemis.core.remoting.impl.netty的项目。

'TransportConstants.NIO_REMOTING_THREADS_PROPNAME'已被添加。LegacyConnectionFactoryService.java(145),LegacyConnectionFactoryService.java(139)



private static final Map<String, String> 
PARAM_KEY_MAPPING = new HashMap<>();
....
static {
  PARAM_KEY_MAPPING.put(
    org.apache.activemq.artemis.core.remoting.impl.netty
      .TransportConstants.NIO_REMOTING_THREADS_PROPNAME,
      TransportConstants.NIO_REMOTING_THREADS_PROPNAME);
    ....
  PARAM_KEY_MAPPING.put(
    org.apache.activemq.artemis.core.remoting.impl.netty
      .TransportConstants.NIO_REMOTING_THREADS_PROPNAME,
      TransportConstants.NIO_REMOTING_THREADS_PROPNAME);
    ....
}


分析器报告已将具有相同键的两个值添加到字典中。在这种情况下,添加的键值匹配将完全重复。记录的值是TransportConstants类中的常量,可以有两个选择之一:作者不小心复制了代码,或者在复制粘贴期间忘记更改值。在粗略检查期间,我找不到丢失的键和值,因此我认为第一种情况更有可能。



警告N7-变量丢失



V6046格式错误。预计会有不同数量的格式项。缺少参数:2. TxTestUtil.java(80)



public static void addSynchronization(TransactionManager tm,
          TransactionCheckerSingletonRemote checker) {
  try {
    addSynchronization(tm.getTransaction(), checker);
  } catch (SystemException se) {
     throw new RuntimeException(String
      .format("Can't obtain transaction for transaction manager '%s' "
     + "to enlist add test synchronization '%s'"), se);
  }
}


变量丢失(需要)。本来可以将另外两行替换为格式化的字符串,但是代码的作者显然忘记了添加它们。格式化不带适当参数的字符串将引发IllegalFormatException而不是开发人员想要RuntimeException原则上,IllegalFormatException继承自RuntimeException,但是传递给异常的消息将在输出中丢失,并且更难于理解调试时到底出了什么问题。



警告N8-字符串与对象比较



V6058 “等于”功能比较不兼容类型的对象:字符串,模型节点。JaxrsIntegrationProcessor.java(563)




// Send value to RESTEasy only if it's not null, empty string, or the 
// default value.

private boolean isTransmittable(AttributeDefinition attribute,
                                ModelNode modelNode) {
  if (modelNode == null || ModelType
      .UNDEFINED.equals(modelNode.getType())) {
    return false;
  }
  String value = modelNode.asString();
  if ("".equals(value.trim())) {
    return false;
  }
  return !value.equals(attribute.getDefaultValue());        // <=
}


在这种情况下,将字符串与对象进行比较,并且这种比较始终为假。也就是说,如果将等于attribute.getDefaultValue()modelNode值传递给该方法,则该方法的行为将被证明是不正确的,并且尽管有注释,该值也将被视为对发送有效。 他们很可能忘记了调用asString()方法来将attribute.getDefaultValue()表示为字符串。更正后的版本可能如下所示:







return !value.equals(attribute.getDefaultValue().asString());


WildFly还有一个类似的V6058诊断触发



  • V6058“等于”功能比较不兼容类型的对象:字符串,ObjectTypeAttributeDefinition。DataSourceDefinition.java(141)


警告N9-逾期检查



V6060在对null进行验证之前,已使用'dataSourceController'引用。AbstractDataSourceAdd.java(399),AbstractDataSourceAdd.java(297)



static void secondRuntimeStep(OperationContext context, ModelNode operation, 
ManagementResourceRegistration datasourceRegistration, 
ModelNode model, boolean isXa) throws OperationFailedException {
  final ServiceController<?> dataSourceController =    
        registry.getService(dataSourceServiceName);
  ....
  dataSourceController.getService()  
  ....
  if (dataSourceController != null) {....}
  ....
}


分析器发现对象早已在代码中使用过,然后才检查为null,并且它们之间的距离多达102行代码!手动分析代码时,这很难注意到。



警告N10-双重检查锁定



V6082不安全的双重检查锁定。先前分配的对象可以用另一个对象替换。 JspApplicationContextWrapper.java(74),JspApplicationContextWrapper.java(72)



private volatile ExpressionFactory factory;
....
@Override
public ExpressionFactory getExpressionFactory() {
  if (factory == null) {
    synchronized (this) {
      if (factory == null) {
        factory = delegate.getExpressionFactory();
        for (ExpressionFactoryWrapper wrapper : wrapperList) {
          factory = wrapper.wrap(factory, servletContext);
        }
      }
    }
  }
  return factory;
}


这将使用“双重检查锁定”模式,并且该方法返回未完全初始化的变量时可能会发生这种情况。



线程A注意到该值尚未初始化,因此它获取了锁并开始初始化该值。在这种情况下,线程甚至在对象初始化之前就设法将其写入字段。线程B检测到对象已创建并返回该对象,尽管线程A尚未有时间来完成工厂的所有工作



结果,可能会从该方法返回一个对象,在该对象上并未执行所有计划的动作。



结论



尽管该项目是由一家大型公司Red Hat开发的,并且该项目中的代码质量处于较高水平,但是使用PVS-Studio进行的静态分析仍然能够揭示一定数量的错误,这些错误可能以一种或另一种方式影响服务器的运行。而且由于WildFly是为创建企业应用程序而设计的,因此这些错误可能导致极其可悲的后果。



我邀请所有人下载PVS-Studio并检查其项目。为此,您可以申请试用许可证或使用其中一个免费用例。





如果您想与讲英语的读者分享本文,请使用翻译链接:Dmitry Scherbakov。检查WildFly,这是JavaEE应用服务器



All Articles