pvs-stdio ue4_华为云:如今PVS-Studio多云

pvs-stdio ue4

Picture 2

Nowadays everyone knows about cloud services. Many companies have cracked this market segment and created their own cloud services of various purposes. Recently our team has also been interested in these services in terms of integrating the PVS-Studio code analyzer into them. Chances are, our regular readers have already guessed what type of project we will check this time. The choice fell on the code of Huawei cloud services.

如今,每个人都知道云服务。 许多公司已经打入这个市场领域,并创建了自己的各种用途的云服务。 最近,我们的团队也对将PVS-Studio代码分析器集成到其中的这些服务感兴趣。 通常,我们的普通读者已经猜到了这次我们将检查哪种类型的项目。 选择取决于华为云服务的代码。

介绍 (Introduction)

If you're following PVS-Studio team posts, you've probably noticed that we had been digging deep in cloud technologies lately. We have already published several articles covering this topic:

如果您关注PVS-Studio团队的帖子,您可能已经注意到我们最近一直在深入研究云技术。 我们已经发表了几篇有关该主题的文章:

Right when I was looking for an unusual project for the upcoming article, I got an email with a job offer from Huawei. After collecting some information about this company, it turned out that they had their own cloud services, but the main thing is that the source code of these services is available on GitHub. This was the main reason for choosing this company for this article. As one Chinese sage said: «The accidents are not accidental».

就在我为即将到来的文章寻找不寻常的项目时,我收到了一封包含华为工作机会的电子邮件。 在收集了有关该公司的一些信息之后,事实证明他们拥有自己的云服务,但主要的是这些服务的源代码可在GitHub上获得。 这是本文选择该公司的主要原因。 正如一位中国圣贤所说:“事故并非偶然”。

Let me give you some details about our analyzer. PVS-Studio is a static analyzer for bug detection in the source code of programs, written in C, C++, C#, and Java. The analyzer works on Windows, Linux, and macOS. In addition to plugins for classic development environments, such as Visual Studio or IntelliJ IDEA, the analyzer has the ability to integrate into SonarQube and Jenkins:

让我给您一些有关我们分析仪的细节。 PVS-Studio是一种静态分析器,用于用C,C ++,C#和Java编写的程序源代码中的错误检测。 该分析仪可在Windows,Linux和macOS上运行。 除了适用于经典开发环境(例如Visual Studio或IntelliJ IDEA)的插件之外,该分析仪还可以集成到SonarQube和Jenkins中:

项目分析 (Project analysis)

When I was doing some research for the article, I found out that Huawei had a developer center with available information, manuals, and sources of their cloud services. A wide variety of programming languages were used to create these services, but languages such as Go, Java and Python were the most prevailing.

在对本文进行一些研究时,我发现华为有一个开发人员中心,其中提供了可用的信息,手册和云服务的来源。 各种各样的编程语言被用来创建这些服务,但是诸如Go,Java和Python之类的语言最为流行。

Since I specialize in Java, the projects have been selected in keeping with my knowledge and skills. You can get project sources analyzed in the article in a GitHub repository huaweicloud.

由于我专门研究Java,因此选择了符合我的知识和技能的项目。 您可以在GitHub存储库huaweicloud中的文章中分析项目源。

To analyze projects, I needed only a few things to do:

要分析项目,我只需要做几件事:

  • Get projects from the repository;

    从资源库中获取项目;
  • Use start-up Java analyzer instructions and run the analysis on each project.

    使用Java启动分析器说明,并在每个项目上运行分析。

Having analyzed the projects, we selected only three of them, which we would like to pay attention to. It is because of the fact that the size of the rest Java projects turned out to be too small.

在分析了项目之后,我们只选择了三个我们要注意的项目。 因为事实证明,其余Java项目的大小过小。

Project analysis results (number of warnings and number of files):

项目分析结果(警告数量和文件数量):

There were few warnings, which tells us about high quality of code, all the more so since not all warnings point at real errors. This is due to the fact that the analyzer sometimes lacks information to distinguish the correct code from the erroneous one. For this reason we tweak analyzer's diagnostics day by day with recourse to the information from users. You're welcome to see the article "The way static analyzers fight against false positives, and why they do it".

很少有警告可以告诉我们有关代码质量的信息,更重要的是,因为并非所有警告都指向实际错误。 这是由于这样的事实,即分析器有时缺乏将正确代码与错误代码区分开的信息。 因此,我们会根据用户的信息,每天对分析仪的诊断进行调整。 欢迎您阅读文章“ 静态分析器与误报的对抗方式以及为什么要这么做 ”。

As of analyzing the project I picked over only the most hotshot warnings, which I'll talk about in this article.

在分析项目时,我仅选择了最热门的警告,本文将对此进行讨论。

字段初始化顺序 (Fields initialization order)

V6050 Class initialization cycle is present. Initialization of 'INSTANCE' appears before the initialization of 'LOG'. UntrustedSSL.java(32), UntrustedSSL.java(59), UntrustedSSL.java(33) V6050存在类初始化周期。 在初始化“ LOG”之前出现“ INSTANCE”的初始化。 UntrustedSSL.java(32),UntrustedSSL.java(59),UntrustedSSL.java(33)
public class UntrustedSSL {
  
  private static final UntrustedSSL INSTANCE = new UntrustedSSL();
  private static final Logger LOG = LoggerFactory.getLogger(UntrustedSSL.class);
  .... 
  private UntrustedSSL() 
  {
    try
    {
      ....
    }
    catch (Throwable t) {
      LOG.error(t.getMessage(), t);           // <=
    }
  }
}

If there is any exception in the UntrustedSSL class constructor, the information about this exception is logged in the catch block using the LOG logger. However, due to the initialization order of static fields, at the moment of initializing the INSTANCE field, LOG isn't initialized yet. Therefore, if you log information about the exception in the constructor, it will result in NullPointerException. This exception is the reason for another exception ExceptionInInitializerError, which is thrown if there had been an exception when the static field had been initialized. What you need to solve this problem is to place LOG initialization before INSTANCE initializing.

如果UntrustedSSL类构造函数中有任何异常,则使用LOG记录器将有关此异常的信息记录在catch块中。 但是,由于静态字段的初始化顺序,在初始化INSTANCE字段时,尚未初始化LOG 。 因此,如果在构造函数中记录有关异常的信息,则将导致NullPointerException 。 此异常是另一个异常ExceptionInInitializerError的原因,如果在初始化静态字段时发生异常 ,则抛出该异常。 您需要解决的问题是将LOG初始化放在INSTANCE初始化之前

不起眼的错字 (Inconspicuous typo)

V6005 The variable 'this.metricSchema' is assigned to itself. OpenTSDBSchema.java(72) V6005变量'this.metricSchema'被分配给它自己。 OpenTSDBSchema.java(72)
public class OpenTSDBSchema
{
  @JsonProperty("metric")
  private List<SchemaField> metricSchema;
  ....
  public void setMetricsSchema(List<SchemaField> metricsSchema)
  {
    this.metricSchema = metricSchema;           // <=
  }
   
  public void setMetricSchema(List<SchemaField> metricSchema)
  {
    this.metricSchema = metricSchema;
  }
  ....
}

Both methods set the metricSchema field, but the method's names differ by one 's' symbol. The programmer named the arguments of these methods according to the name of the method. As a result, in the line the analyzer points to, the metricSchema field isassigned to itself, and the metricsSchema method's argument is not used.

两种方法都设置metricSchema字段,但是方法的名称之间用一个's'符号表示区别。 程序员根据方法的名称来命名这些方法的参数。 结果,在分析器指向的行中,向其自身分配了metricSchema字段,并且未使用metricsSchema方法的参数。

V6005 The variable 'suspend' is assigned to itself. SuspendTransferTaskRequest.java(77) V6005变量“挂起”已分配给自身。 SuspendTransferTaskRequest.java(77)
public class SuspendTransferTaskRequest 
{
  ....
  private boolean suspend;
  ....
  public void setSuspend(boolean suspend)
  {
    suspend = suspend;                        
  }
  .... 
}

Here is a trivial error related to carelessness, because of which the suspend argument is assigned to itself. As a result, the suspend field won't be assigned the value of the obtained argument as implied. The correct version:

这是一个与粗心有关的琐碎错误,因此会将suspend参数分配给它自己。 结果,将不会像隐式那样为suspend字段分配获得的参数的值。 正确的版本:

public void setSuspend(boolean suspend)
{
  this.suspend = suspend;                        
}

条件预先确定 (Conditions predetermination)

As often happens, the V6007 rule breaks ahead in terms of warnings quantity.

经常发生的是, V6007规则在警告数量方面超前。

V6007 Expression 'firewallPolicyId == null' is always false. FirewallPolicyServiceImpl.java(125) V6007表达式“ firewallPolicyId == null”始终为false。 FirewallPolicyServiceImpl.java(125)
public FirewallPolicy
removeFirewallRuleFromPolicy(String firewallPolicyId,
                             String firewallRuleId) 
{
  checkNotNull(firewallPolicyId);
  checkNotNull(firewallRuleId);
  checkState(!(firewallPolicyId == null && firewallRuleId == null),
  "Either a Firewall Policy or Firewall Rule identifier must be set"); 
  .... 
}

In this method arguments are checked for null by the checkNotNull method:

在此方法中,用checkNotNull方法检查参数是否为

@CanIgnoreReturnValue
public static <T> T checkNotNull(T reference) 
{
  if (reference == null) {
    throw new NullPointerException();
  } else {
    return reference;
  }
}

After checking the argument by the checkNotNull method, you can be 100% sure that the argument passed to this method is not equal to null. Since both arguments of the removeFirewallRuleFromPolicy method are checked by the checkNotNull method, their further check for null makes no sense. However, the expression, where firewallPolicyId and firewallRuleId arguments are re-checked for null, is passed as the first argument to the checkState method.

通过checkNotNull方法检查参数后,可以100%确保传递给此方法的参数不等于null 。 由于removeFirewallRuleFromPolicy方法的两个参数都由checkNotNull方法检查,因此进一步检查null是没有意义的。 但是,将重新检查firewallPolicyIdfirewallRuleId参数为null的表达式作为第一个参数传递给checkState方法。

A similar warning is issued for firewallRuleId as well:

也为firewallRuleId发出类似的警告:

  • V6007 Expression 'firewallRuleId == null' is always false. FirewallPolicyServiceImpl.java(125)

    V6007表达式“ firewallRuleId == null”始终为false。 FirewallPolicyServiceImpl.java(125)
V6007 Expression 'filteringParams != null' is always true. NetworkPolicyServiceImpl.java(60) V6007表达式'filteringParams!= null'始终为true。 NetworkPolicyServiceImpl.java(60)
private Invocation<NetworkServicePolicies> buildInvocation(Map<String,
String> filteringParams) 
{
  .... 
  if (filteringParams == null) {
    return servicePoliciesInvocation;
  }
  if (filteringParams != null) {       // <=
    ....
  }
  return servicePoliciesInvocation;
}

In this method, if the filteringParams argument is null, the method returns a value. This is why the check that the analyzer points to will always be true which, in turns, means that this check is meaningless.

在此方法中,如果filteringParams参数为null ,则该方法返回一个值。 这就是为什么分析仪指向的检查将始终为真,这又意味着该检查没有意义。

13 more classes are similar:

还有13个类似的类:

  • V6007 Expression 'filteringParams != null' is always true. PolicyRuleServiceImpl.java(58)

    V6007表达式'filteringParams!= null'始终为true。 PolicyRuleServiceImpl.java(58)
  • V6007 Expression 'filteringParams != null' is always true. GroupServiceImpl.java(58)

    V6007表达式'filteringParams!= null'始终为true。 GroupServiceImpl.java(58)
  • V6007 Expression 'filteringParams != null' is always true. ExternalSegmentServiceImpl.java(57)

    V6007表达式'filteringParams!= null'始终为true。 ExternalSegmentServiceImpl.java(57)
  • V6007 Expression 'filteringParams != null' is always true. L3policyServiceImpl.java(57)

    V6007表达式'filteringParams!= null'始终为true。 L3policyServiceImpl.java(57)
  • V6007 Expression 'filteringParams != null' is always true. PolicyRuleSetServiceImpl.java(58)

    V6007表达式'filteringParams!= null'始终为true。 PolicyRuleSetServiceImpl.java(58)
  • and so on...

    等等...

空参考 (Null reference)

V6008 Potential null dereference of 'm.blockDeviceMapping'. NovaServerCreate.java(390) V6008可能会取消引用'm.blockDeviceMapping'。 NovaServerCreate.java(390)
@Override
public ServerCreateBuilder blockDevice(BlockDeviceMappingCreate blockDevice) {
  if (blockDevice != null && m.blockDeviceMapping == null) {
    m.blockDeviceMapping = Lists.newArrayList();
  }
  m.blockDeviceMapping.add(blockDevice);       // <=
  return this;
}

In this method, the initialization of the m.blockDeviceMapping reference field won't happen if the blockDevice argument is null. This field is initialized only in this method, so when calling the add method from the m.blockDeviceMapping field, a NullPointerException will happen.

在这种方法中,如果blockDevice参数为null,则不会初始化m.blockDeviceMapping参考字段。 仅在此方法中初始化此字段,因此从m.blockDeviceMapping字段调用add方法时,将发生NullPointerException

V6008 Potential null dereference of 'FileId.get(path)' in function '<init>'. TrackedFile.java(140), TrackedFile.java(115) V6008函数'<init>'中'FileId.get(path)'的潜在空引用。 TrackedFile.java(140),TrackedFile.java(115)
public TrackedFile(FileFlow<?> flow, Path path) throws IOException 
{
  this(flow, path, FileId.get(path), ....);
}

The constructor of the TrackedFile class receives the result of the static FileId.get(path) method as a third argument. But this method can return null:

TrackedFile类的构造函数接收静态FileId.get(path)方法的结果作为第三个参数。 但是此方法可以返回null

public static FileId get(Path file) throws IOException
{
  if (!Files.exists(file))
  {
    return null;
  }
  ....
}

In the constructor, called via this, the id argument doesn't change until its first use:

在通过this调用的构造函数中, id参数直到第一次使用才改变:

public TrackedFile(...., ...., FileId id, ....) throws IOException
{
  ....
  FileId newId = FileId.get(path);
  if (!id.equals(newId))
  {
    ....
  }
}

As we can see, if null is passed as the third argument to the method, an exception will occur.

如我们所见,如果将null作为第三个参数传递给该方法,则会发生异常。

Here is another similar case:

这是另一种类似的情况:

  • V6008 Potential null dereference of 'buffer'. PublishingQueue.java(518)

    V6008可能会取消引用“缓冲区”。 PublishingQueue.java(518)
V6008 Potential null dereference of 'dataTmpFile'. CacheManager.java(91) V6008可能取消对“ dataTmpFile”的引用。 CacheManager.java(91)
@Override
public void putToCache(PutRecordsRequest putRecordsRequest)
{
  .... 
  if (dataTmpFile == null || !dataTmpFile.exists())
  {
    try
    {
      dataTmpFile.createNewFile();  // <=
    }
    catch (IOException e)
    {
      LOGGER.error("Failed to create cache tmp file, return.", e);
      return ;
    }
  }
  ....
}

NPE again. A number of checks in the conditional operator allows the zero object dataTmpFile for further dereference. I think there are two typos here and the check should actually look like this:

再次NPE。 条件运算符中的许多检查允许零对象dataTmpFile进一步取消引用。 我认为这里有两种错别字,支票实际上应该是这样的:

if (dataTmpFile != null && !dataTmpFile.exists())

子串和负数 (Substrings and negative numbers)

V6009 The 'substring' function could receive the '-1' value while non-negative value is expected. Inspect argument: 2. RemoveVersionProjectIdFromURL.java(37) V6009 “子字符串”功能可以接收“ -1”值,而预期为非负值。 检查参数:2. RemoveVersionProjectIdFromURL.java(37)
@Override
public String apply(String url) {
  String urlRmovePojectId = url.substring(0, url.lastIndexOf("/"));
  return urlRmovePojectId.substring(0, urlRmovePojectId.lastIndexOf("/"));
}

The implication is that this method gets a URL as a string, which is not validated in any way. Later, this string is cut off several times using the lastIndexOf method. If the method lastIndexOf doesn't find a match in the string, it will return -1. This will lead to StringIndexOutOfBoundsException, as the arguments of the substring method have to be non-negative numbers. For correct method's operation, one has to add an input argument validation or check that the results of the lastIndexOf method are non-negative numbers.

含义是此方法将URL作为字符串获取,而未经任何方式的验证。 稍后,使用lastIndexOf方法将该字符串截断几次。 如果方法lastIndexOf在字符串中找不到匹配项,它将返回-1。 这将导致StringIndexOutOfBoundsException ,因为substring方法的参数必须为非负数。 为了使方法正确运行,必须添加输入参数验证或检查lastIndexOf方法的结果是否为非负数。

Here are some other snippets with a similar way things are:

以下是一些其他类似的片段:

  • V6009 The 'substring' function could receive the '-1' value while non-negative value is expected. Inspect argument: 2. RemoveProjectIdFromURL.java(37)

    V6009“子字符串”功能可以接收“ -1”值,而预期为非负值。 检查参数:2. RemoveProjectIdFromURL.java(37)
  • V6009 The 'substring' function could receive the '-1' value while non-negative value is expected. Inspect argument: 2. RemoveVersionProjectIdFromURL.java(38)

    V6009“子字符串”功能可以接收“ -1”值,而预期为非负值。 检查参数:2. RemoveVersionProjectIdFromURL.java(38)

被遗忘的结果 (Forgotten result)

V6010 The return value of function 'concat' is required to be utilized. AKSK.java(278) V6010需要使用功能“ concat”的返回值。 AKSK.java(278)
public static String buildCanonicalHost(URL url) 
{
  String host = url.getHost();
  int port = url.getPort();
  if (port > -1) {
    host.concat(":" + Integer.toString(port));
  }
  return host;
}

When writing this code, its author didn't take into account that a call of the concat method won't change the host string due to immutability of the String type objects. For correct method's operation, the result of the concat method has to be assigned to the host variable in the if block. The correct version:

在编写此代码时,其作者没有考虑到concat方法的调用不会由于String类型对象的不变性而更改主机字符串。 为了使方法正确运行,必须将concat方法的结果分配给if块中的主机变量。 正确的版本:

if (port > -1) {
  host = host.concat(":" + Integer.toString(port));
}

未使用的变量 (Unused variables)

V6021 Variable 'url' is not used. TriggerV2Service.java(95) V6021不使用变量“ url”。 TriggerV2Service.java(95)
public ActionResponse deleteAllTriggersForFunction(String functionUrn) 
{
  checkArgument(!Strings.isNullOrEmpty(functionUrn), ....);
  String url = ClientConstants.FGS_TRIGGERS_V2 +
               ClientConstants.URI_SEP + 
               functionUrn;
  return deleteWithResponse(uri(triggersUrlFmt, functionUrn)).execute();
}

In this method, the url variable isn't used after its initialization. Most likely, the url variable has to be passed to the uri method as a second argument instead of functionUrn, as the functionUrn variable takes part in the initialization of the url variable.

在此方法中,初始化后不使用url变量。 很有可能,必须将url变量作为第二个参数而不是functionUrn传递给uri方法,因为functionUrn变量会参与url变量的初始化。

参数未使用构造函数 (Argument not used the constructor)

V6022 Parameter 'returnType' is not used inside constructor body. HttpRequest.java(68) V6022构造函数体内未使用参数'returnType'。 HttpRequest.java(68)
public class HttpReQuest<R> 
{
  ....
  Class<R> returnType;
  ....
  public HttpRequest(...., Class<R> returnType) // <=
  {      
    this.endpoint = endpoint;
    this.path = path;
    this.method = method;
    this.entity = entity;
  }
  ....
  public Class<R> getReturnType() 
  {
    return returnType;
  }
  ....
}

In this constructor, the programmer forgot to use the returnType argument, and assign its value to the returnType field. That's why when calling the getReturnType method from the object, created by this constructor, null will be returned by default. But most likely, the programmer intended to get the object, previously passed to the constructor.

在此构造函数中,程序员忘记使用returnType参数,并将其值分配给returnType字段。 这就是为什么当从此构造方法创建的对象中调用getReturnType方法时,默认情况下将返回null的原因 。 但最有可能的是,程序员打算获取对象,该对象先前已传递给构造函数。

相同的功能 (Same functionality)

V6032 It is odd that the body of method 'enable' is fully equivalent to the body of another method 'disable'. ServiceAction.java(32), ServiceAction.java(36) V6032奇怪的是,方法“启用”的主体与另一方法“禁用”的主体完全等效。 ServiceAction.java(32),ServiceAction.java(36)
public class ServiceAction implements ModelEntity 
{    
  private String binary;
  private String host;

  private ServiceAction(String binary, String host) {
    this.binary = binary;
    this.host = host;
  }

  public static ServiceAction enable(String binary, String host) { // <=
    return new ServiceAction(binary, host);
  }

  public static ServiceAction disable(String binary, String host) { // <=
    return new ServiceAction(binary, host);
  }
  ....
}

Having two identical methods is not a mistake, but the fact that two methods perform the same action is at least strange. Looking at the names of the above methods, we can assume that they should perform the opposite actions. In fact, both methods do the same thing — create and return the ServiceAction object. Most likely, the disable method was created by copying the enable method's code, but the method's body remained the same.

拥有两个相同的方法不是一个错误,但是两个方法执行相同操作的事实至少很奇怪。 查看上述方法的名称,我们可以假定它们应该执行相反的操作。 实际上,这两种方法都做同样的事情-创建并返回ServiceAction对象。 最有可能的是, disable方法是通过复制enable方法的代码创建的,但是该方法的主体保持不变。

忘了检查主要的东西 (Forgot to check the main thing)

V6060 The 'params' reference was utilized before it was verified against null. DomainService.java(49), DomainService.java(46) V6060在对null进行验证之前,已使用“ params”参考。 DomainService.java(49),DomainService.java(46)
public Domains list(Map<String, String> params)
{
  Preconditions.checkNotNull(params.get("page_size"), ....);
  Preconditions.checkNotNull(params.get("page_number"), ....);
  Invocation<Domains> domainInvocation = get(Domains.class, uri("/domains"));
  if (params != null) {                                      // <=
    ....
  }
  return domainInvocation.execute(this.buildExecutionOptions(Domains.class));
}

In this method, the author decided to check the contents of a structure of the Map type for null. To do this, the get method is called twice from the params argument. The result of the get method is passed to the checkNotNull method. Everything seems logical, but it's not like that! The params argument is checked for null in if. After this it is expected that the input argument might be null, but before this check, the get method has already been called twice from params. If null is passed as an argument to this method, the first time you call the get method, an exception will be thrown.

在这种方法中,作者决定检查Map类型的结构的内容是否为null 。 为此,从params参数调用get方法两次。 get方法的结果传递给checkNotNull方法。 一切似乎合乎逻辑,但事实并非如此! 在if中检查params参数是否为null 。 此后,预期输入参数可能为null ,但是在此检查之前,已经从params中调用了get方法两次 如果将null作为参数传递给此方法,则在第一次调用get方法时,将引发异常。

A similar situation occurs in three other places:

在其他三个地方也发生了类似的情况:

  • V6060 The 'params' reference was utilized before it was verified against null. DomainService.java(389), DomainService.java(387)

    V6060在对null进行验证之前,已使用“ params”参考。 DomainService.java(389),DomainService.java(387)
  • V6060 The 'params' reference was utilized before it was verified against null. DomainService.java(372), DomainService.java(369)

    V6060在对null进行验证之前,已使用“ params”参考。 DomainService.java(372),DomainService.java(369)
  • V6060 The 'params' reference was utilized before it was verified against null. DomainService.java(353), DomainService.java(350)

    V6060在对null进行验证之前,已使用“ params”参考。 DomainService.java(353),DomainService.java(350)

结论 (Conclusion)

Today's large companies can't do without usage of cloud services. A huge number of people use these services. In this view, even a small error in a service might lead to problems for many people as well as to additional losses, racked up by a company to remedy adverse consequences of this error. Human flaws should always be taken into account especially since sooner or later everyone makes mistakes, as described in this article. This fact substantiates usage of all possible tools to improve the code quality.

当今的大公司离不开使用云服务。 大量的人使用这些服务。 按照这种观点,即使是服务中的小错误,也可能导致许多人面临问题,并导致公司蒙受额外损失,以弥补该错误的不利后果。 如本文所述,尤其应该考虑到人为缺陷,因为每个人迟早都会犯错。 这一事实证实了所有可能工具的使用,以提高代码质量。

PVS-Studio will definitely inform the Huawei company about the results of checking their cloud services so as to Huawei developers could dwell on them, because one-time usage of static code analysis covered by this articles (1, 2) can't fully demonstrate all its advantages. You can download the PVS-Studio analyzer here.

PVS-Studio将肯定通知华为公司有关检查他们的云服务,以华为开发人员可以赘述的结果,因为静态代码分析的一次性使用包括在此文章( 12 )不能充分展示所有的优点。 您可以在此处下载PVS-Studio分析仪。

翻译自: https://habr.com/en/company/pvs-studio/blog/477558/

pvs-stdio ue4

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值