pvs-stdio ue4_云中的PVS-Studio —在Travis CI上运行分析

pvs-stdio ue4

Picture 1

Why do we consider third-party clouds and don't make our own? There is a number of reasons, the main one is that the SaaS implementation is quite an expensive and difficult procedure. In fact, it is a simple and trivial task to directly integrate PVS-Studio analysis into a third-party cloud platform — whether it's open platforms like CircleCI, Travis CI, GitLab, or a specific enterprise solution used only in a certain company. Therefore we can say that PVS-Studio is already available «in the clouds». Another issue is implementing and ensuring access to the infrastructure 24/7. This is a more complicated task. PVS-Studio is not going to provide its own cloud platform directly for running analysis on it.

为什么我们考虑使用第三方云而不自己做? 原因有很多,主要原因是SaaS的实现是一个昂贵且困难的过程。 实际上,将PVS-Studio分析直接集成到第三方云平台中是简单而琐碎的任务-无论是CircleCI,Travis CI,GitLab等开放平台,还是仅在某家公司中使用的特定企业解决方案。 因此,可以说PVS-Studio已经“在云端”可用。 另一个问题是实施和确保对基础设施24/7的访问。 这是一个更复杂的任务。 PVS-Studio不会直接提供自己的云平台来在其上运行分析。

有关二手软件的一些信息 (Some Information about the Used Software)

Travis CI is a service for building and testing software that uses GitHub as a storage. Travis CI doesn't require changing of programming code for using the service. All settings are made in the file Travis CI是用于构建和测试使用GitHub作为存储的软件的服务。 Travis CI不需要更改程序代码即可使用该服务。 所有设置都在存储库根目录中的文件 .travis.yml located in the root of the repository. .travis.yml中进行。

We'll take LXC (Linux Containers) as a test project for PVS-Studio. It is a virtualization system at the operation system level for launching several instances of the Linux OS at one node.

我们将把LXC (Linux容器)作为PVS-Studio的测试项目。 它是操作系统级别的虚拟化系统,用于在一个节点上启动Linux OS的多个实例。

The project is small, but more than enough for demonstration. Output of the cloc command:

该项目很小,但足以进行演示。 cloc命令的输出:

Languagefilesblankcommentcode
C12411937675850836
C/C++ Header65111736763774
语言 档案 空白 评论
C 124 11937 6758 50836
C / C ++标头 65 1117 3676 3774

注意: (Note:)

LXC developers already use Travis CI, so we'll take their configuration file as the basis and edit it for our purposes.

LXC开发人员已经在使用Travis CI,因此我们将以他们的配置文件为基础并对其进行编辑。

组态 (Configuration)

To start working with Travis CI we follow the link and login using a GitHub-account.

要开始使用Travis CI,请单击链接并使用Gi​​tHub帐户登录。

Picture 17

In the open window we need to login Travis CI.

在打开的窗口中,我们需要登录Travis CI。

Picture 16

After authorization, it redirects to the welcome page «First time here? Lets get you started!»

授权后,它将重定向到欢迎页面«第一次来这里? 让您开始吧!»

(,)

where we find a brief description what has to be done afterwards to get started:

在这里,我们可以找到简短的说明,然后才能开始进行操作:

  • enable the repositories;

    启用存储库;
  • add the .travis.yml file in the repository;

    在存储库中添加.travis.yml文件;
  • start the first build.

    开始第一个构建。
Picture 18

Let's start doing these actions.

让我们开始执行这些操作。

To add our repository in Travis CI we go to the profile settings by the link and press «Activate».

要将我们的存储库添加到Travis CI中,请通过链接转到配置文件设置,然后按“激活”。

Picture 19

Once clicked, a window will open to select repositories that the Travis CI app will be given access to.

单击后,将打开一个窗口,以选择可以访问Travis CI应用程序的存储库。

注意: (Note: )

to provide access to the repository, your account must have administrator's rights to do so.

要提供对存储库的访问权限,您的帐户必须具有管理员权限。

Picture 38

After that we choose the right repository, confirm the choice with the «Approve & Install» button, and we'll be redirected back to the profile settings page.

之后,我们选择正确的存储库,单击“批准并安装”按钮确认选择,然后我们将被重定向回配置文件设置页面。

Let's add some variables that we'll use to create the analyzer's license file and send its reports. To do this, we'll go to the settings page — the «Settings» button to the right of the needed repository.

让我们添加一些变量,这些变量将用于创建分析器的许可证文件并发送其报告。 为此,我们将转到设置页面-所需存储库右侧的“设置”按钮。

Picture 39

The settings window will open.

设置窗口将打开。

Picture 41

Brief description of settings;

设置的简要说明;

  • «General» section — configuring auto-start task triggers;

    «常规»部分—配置自动启动任务触发器;
  • «Auto Cancelation» section allows to configure build auto-cancellation;

    «自动取消»部分允许配置构建自动取消;
  • «Environment Variables» section allows to define environment variables that contain both open and confidential information, such as login information, ssh-keys;

    «环境变量»部分允许定义包含开放和机密信息的环境变量,例如登录信息,ssh-key;
  • «Cron Jobs» section is a configuration of task running schedule.

    «Cron Jobs»部分是任务运行时间表的配置。

In the section «Environment Variables» we'll create variables PVS_USERNAME and PVS_KEY containing a username and a license key for the static analyzer respectively. If you don't have a permanent PVS-Studio license, you can request a trial license.

在“环境变量”部分,我们将创建变量PVS_USERNAMEPVS_KEY,分别包含静态分析器的用户名和许可证密钥。 如果您没有永久性的PVS-Studio许可证,则可以申请试用许可证

Picture 5

Right here we'll create variables MAIL_USER and MAIL_PASSWORD, containing a username and an email password, which we'll use for sending reports.

在这里,我们将创建变量MAIL_USERMAIL_PASSWORD ,其中包含用户名和电子邮件密码,将用于发送报告。

Picture 4

When running tasks, Travis CI takes instructions from the .travis.yml file, located in the root of the repository.

运行任务时,Travis CI从存储库根目录中的.travis.yml文件中获取指令。

By using Travis CI, we can run static analysis both directly on the virtual machine and use a preconfigured container to do so. The results of these approaches are no different from each other. However, usage of a pre-configured container can be useful. For example, if we already have a container with some specific environment, inside of which a software product is built and tested and we don't want to restore this environment in Travis CI.

通过使用Travis CI,我们可以直接在虚拟机上运行静态分析,也可以使用预先配置的容器来执行此操作。 这些方法的结果互不相同。 但是,使用预先配置的容器可能会很有用。 例如,如果我们已经有一个带有特定环境的容器,并且在其中构建并测试了软件产品,并且我们不想在Travis CI中还原该环境。

Let's create a configuration to run the analyzer on a virtual machine.

让我们创建一个配置以在虚拟机上运行分析器。

For building and testing we'll use a virtual machine on Ubuntu Trusty, its description is available by the link.

为了构建和测试,我们将在Ubuntu Trusty上使用虚拟机,可通过链接获得其描述。

First of all, we specify that the project is written in C and list compilers that we will use for the build:

首先,我们指定该项目是用C编写的,并列出了将用于构建的编译器:

language: c
compiler:
 - gcc
 - clang

注意: (Note: )

if you specify more than one compiler, the tasks will run simultaneously for each of them. Read more

如果指定多个编译器,则每个任务将同时运行。 阅读更多

here. 在这里

Before the build we need to add the analyzer repository, set dependencies and additional packages:

在构建之前,我们需要添加分析器存储库,设置依赖项和其他软件包:

before_install:
 - sudo add-apt-repository ppa:ubuntu-lxc/daily -y
 - wget -q -O - https://files.viva64.com/etc/pubkey.txt | sudo apt-key add -
 - sudo wget -O /etc/apt/sources.list.d/viva64.list
  https://files.viva64.com/etc/viva64.list
 - sudo apt-get update -qq
 - sudo apt-get install -qq coccinelle parallel 
       libapparmor-dev libcap-dev libseccomp-dev
       python3-dev python3-setuptools docbook2x
       libgnutls-dev libselinux1-dev linux-libc-dev pvs-studio
       libio-socket-ssl-perl libnet-ssleay-perl sendemail 
       ca-certificates

Before we build a project, we need to prepare your environment:

在构建项目之前,我们需要准备您的环境:

script:
 - ./coccinelle/run-coccinelle.sh -i
 - git diff --exit-code
 - export CFLAGS="-Wall -Werror"
 - export LDFLAGS="-pthread -lpthread"
 - ./autogen.sh
 - rm -Rf build
 - mkdir build
 - cd build
 - ../configure --enable-tests --with-distro=unknown

Next, we need to create a license file and start analyzing the project.

接下来,我们需要创建一个许可证文件并开始分析项目。

Then we create a license file for the analyzer by the first command. Data for the $PVS_USERNAME and $PVS_KEY variables is taken from the project settings.

然后,我们通过第一个命令为分析仪创建一个许可证文件。 $ PVS_USERNAME$ PVS_KEY变量的数据来自项目设置。

- pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY -o PVS-Studio.lic

By the next command, we start tracing the project build.

通过下一条命令,我们开始跟踪项目构建。

- pvs-studio-analyzer trace -- make -j4

After that we run static analysis.

之后,我们进行静态分析。

注意: (Note: )

when using a trial license, you need to specify the parameter

使用试用许可证时,需要指定参数

--disableLicenseExpirationCheck. --disableLicenseExpirationCheck
- pvs-studio-analyzer analyze -j2 -l PVS-Studio.lic 
   -o PVS-Studio-${CC}.log 
     --disableLicenseExpirationCheck

The file with the analysis results is converted into the html-report by the last command.

具有分析结果的文件由最后一个命令转换为html-report。

- plog-converter -t html PVS-Studio-${CC}.log 
                 -o PVS-Studio-${CC}.html

Since TravisCI doesn't let you change the format of email notifications, in the last step we'll use the sendemail package for sending reports:

由于TravisCI不允许您更改电子邮件通知的格式,因此在最后一步中,我们将使用sendemail包发送报告:

- sendemail -t mail@domain.com 
            -u "PVS-Studio $CC report, commit:$TRAVIS_COMMIT" 
            -m "PVS-Studio $CC report, commit:$TRAVIS_COMMIT" 
            -s smtp.gmail.com:587 
            -xu $MAIL_USER 
            -xp $MAIL_PASSWORD 
            -o tls=yes 
            -f $MAIL_USER 
            -a PVS-Studio-${CC}.log PVS-Studio-${CC}.html

Here is the full text of the configuration file for running the analyzer on the virtual machine:

这是用于在虚拟机上运行分析器的配置文件的全文:

language: c
compiler:
 - gcc
 - clang
before_install:
 - sudo add-apt-repository ppa:ubuntu-lxc/daily -y
 - wget -q -O - https://files.viva64.com/etc/pubkey.txt | sudo apt-key add -
 - sudo wget -O /etc/apt/sources.list.d/viva64.list
          https://files.viva64.com/etc/viva64.list
 - sudo apt-get update -qq
 - sudo apt-get install -qq coccinelle parallel 
         libapparmor-dev libcap-dev libseccomp-dev
         python3-dev python3-setuptools docbook2x 
         libgnutls-dev libselinux1-dev linux-libc-dev pvs-studio
         libio-socket-ssl-perl libnet-ssleay-perl sendemail 
         ca-certificates

script:
 - ./coccinelle/run-coccinelle.sh -i
 - git diff --exit-code
 - export CFLAGS="-Wall -Werror"
 - export LDFLAGS="-pthread -lpthread"
 - ./autogen.sh
 - rm -Rf build
 - mkdir build
 - cd build
 - ../configure --enable-tests --with-distro=unknown
 - pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY -o PVS-Studio.lic
 - pvs-studio-analyzer trace -- make -j4
 - pvs-studio-analyzer analyze -j2 -l PVS-Studio.lic 
     -o PVS-Studio-${CC}.log 
     --disableLicenseExpirationCheck
 - plog-converter -t html PVS-Studio-${CC}.log -o PVS-Studio-${CC}.html

 - sendemail -t mail@domain.com 
             -u "PVS-Studio $CC report, commit:$TRAVIS_COMMIT" 
             -m "PVS-Studio $CC report, commit:$TRAVIS_COMMIT" 
             -s smtp.gmail.com:587 
             -xu $MAIL_USER 
             -xp $MAIL_PASSWORD 
             -o tls=yes 
             -f $MAIL_USER 
             -a PVS-Studio-${CC}.log PVS-Studio-${CC}.html

To run PVS-Studio in a container, let's pre-create it using the following Dockerfile:

要在容器中运行PVS-Studio,让我们使用以下Dockerfile预先创建它:

FROM docker.io/ubuntu:trusty

ENV CFLAGS="-Wall -Werror"
ENV LDFLAGS="-pthread -lpthread"

RUN apt-get update && apt-get install -y software-properties-common wget \
    && wget -q -O - https://files.viva64.com/etc/pubkey.txt | 
        sudo apt-key add - \
    && wget -O /etc/apt/sources.list.d/viva64.list
       https://files.viva64.com/etc/viva64.list \
    && apt-get update \
    && apt-get install -yqq coccinelle parallel 
       libapparmor-dev libcap-dev libseccomp-dev
       python3-dev python3-setuptools docbook2x
       libgnutls-dev libselinux1-dev linux-libc-dev
       pvs-studio git libtool autotools-dev automake
       pkg-config clang make libio-socket-ssl-perl 
       libnet-ssleay-perl sendemail ca-certificates \
    && rm -rf /var/lib/apt/lists/*

In this case, the configuration file may look like this:

在这种情况下,配置文件可能如下所示:

before_install:
- docker pull docker.io/oandreev/lxc

env:
 - CC=gcc
 - CC=clang

script:
 - docker run 
    --rm 
    --cap-add SYS_PTRACE 
    -v $(pwd):/pvs 
    -w /pvs 
    docker.io/oandreev/lxc
    /bin/bash -c " ./coccinelle/run-coccinelle.sh -i
                  && git diff --exit-code
                  && ./autogen.sh
                  && mkdir build && cd build
                  && ../configure CC=$CC
                  && pvs-studio-analyzer credentials 
                     $PVS_USERNAME $PVS_KEY -o PVS-Studio.lic
                  && pvs-studio-analyzer trace -- make -j4
                  && pvs-studio-analyzer analyze -j2 
                     -l PVS-Studio.lic 
                     -o PVS-Studio-$CC.log 
                     --disableLicenseExpirationCheck
                  && plog-converter -t html 
                     -o PVS-Studio-$CC.html
                     PVS-Studio-$CC.log 
                      
                  && sendemail -t mail@domain.com 
             -u 'PVS-Studio $CC report, commit:$TRAVIS_COMMIT' 
             -m 'PVS-Studio $CC report, commit:$TRAVIS_COMMIT' 
             -s smtp.gmail.com:587 
             -xu $MAIL_USER -xp $MAIL_PASSWORD
             -o tls=yes -f $MAIL_USER
             -a PVS-Studio-${CC}.log PVS-Studio-${CC}.html"

As you can see, in this case we do nothing inside the virtual machine, and all the actions on building and testing the project take place inside the container.

如您所见,在这种情况下,我们在虚拟机内部不执行任何操作,并且构建和测试项目的所有操作均在容器内进行。

注意 (Note)

: when you start the container, you need to specify the parameter

:启动容器时,需要指定参数

--cap-add SYS_PTRACE or --cap-add SYS_PTRACE--security-opt seccomp:unconfined, as a ptrace system call is used for compiler tracing. --security-opt seccomp:unconfined ,因为ptrace系统调用用于编译器跟踪。

Next, we load the configuration file in the root of the repository and see that Travis CI has been notified of changes in the project and has automatically started the build.

接下来,我们将配置文件加载到存储库的根目录中,并看到Travis CI已收到有关项目更改的通知,并已自动开始构建。

Details of the build progress and analyzer check can be seen in the console.

可以在控制台中查看构建进度和分析器检查的详细信息。

Picture 2

After the tests are over, we will receive two emails: the first — with static analysis results for building a project using gcc, and the second — for clang, respectively.

测试结束后,我们将收到两封电子邮件:第一封电子邮件-有关使用gcc构建项目的静态分析结果,第二封电子邮件-用于clang。

简要介绍检查结果 (Briefly About the Check Results)

In general, the project is quite clean, the analyzer issued only 24 high-certainty and 46 medium-certainty warnings. Let's look at a couple of interesting notifications:

总的来说,该项目很干净,分析仪仅发出24个高不确定性警告和46个中等不确定性警告。 让我们看几个有趣的通知:

if中的冗余条件 (Redundant conditions in if)

V590 Consider inspecting the 'ret != (- 1) && ret == 1' expression. The expression is excessive or contains a misprint. attach.c 107 V590考虑检查'ret!=(-1)&& ret == 1'表达式。 表达式过多或打印错误。 附件107
#define EOF -1

static struct lxc_proc_context_info *lxc_proc_get_context_info(pid_t pid)
{
  ....
  while (getline(&line, &line_bufsz, proc_file) != -1)
  {
    ret = sscanf(line, "CapBnd: %llx", &info->capability_mask);
    if (ret != EOF && ret == 1) // <=
    {
      found = true;
      break;
    }
  }
  ....
}

If ret == 1, it is definitely not equal to -1 (EOF). Redundant check, ret != EOF can be removed.

如果ret == 1 ,则绝对不等于-1(EOF)。 冗余检查, ret!= EOF可以删除。

Two similar warnings have been issued:

已发出两个类似的警告:

  • V590 Consider inspecting the 'ret != (- 1) && ret == 1' expression. The expression is excessive or contains a misprint. attach.c 579

    V590考虑检查'ret!=(-1)&& ret == 1'表达式。 表达式过多或打印错误。 附件579
  • V590 Consider inspecting the 'ret != (- 1) && ret == 1' expression. The expression is excessive or contains a misprint. attach.c 583

    V590考虑检查'ret!=(-1)&& ret == 1'表达式。 表达式过多或打印错误。 附件c.583

高位丢失 (Loss of High Bits)

V784 The size of the bit mask is less than the size of the first operand. This will cause the loss of higher bits. conf.c 1879 V784位掩码的大小小于第一个操作数的大小。 这将导致丢失更高的位。 1879年的会议
struct mount_opt
{
  char *name;
  int clear;
  int flag;
};

static void parse_mntopt(char *opt, unsigned long *flags,
                         char **data, size_t size)
{
  struct mount_opt *mo;

  /* If opt is found in mount_opt, set or clear flags.
   * Otherwise append it to data. */

  for (mo = &mount_opt[0]; mo->name != NULL; mo++)
  {
    if (strncmp(opt, mo->name, strlen(mo->name)) == 0)
    {
      if (mo->clear)
      {
        *flags &= ~mo->flag;    // <=
      }
      else
      {
        *flags |= mo->flag;
      }
      return;
    }
  }
  ....
}

Under Linux, long is a 64-bit integer variable, mo->flag is a 32-bit integer variable. Usage of mo->flag as a bit mask will lead to the loss of 32 high bits. A bit mask is implicitly cast to a 64-bit integer variable after bitwise inversion. High bits of this mask may be lost.

在Linux下, long是64位整数变量, mo-> flag是32位整数变量。 使用mo-> flag作为位掩码会导致丢失32个高位。 按位求反后,将位掩码隐式转换为64位整数变量。 此掩码的高位可能会丢失。

I'll show it using an example:

我将通过一个示例展示它:

unsigned long long x;
unsigned y;
....
x &= ~y;
Picture 3

Here is the correct version of code:

这是正确的代码版本:

*flags &= ~(unsigned long)(mo->flag);

The analyzer issued another similar warning:

分析仪发出了另一个类似的警告:

  • V784 The size of the bit mask is less than the size of the first operand. This will cause the loss of higher bits. conf.c 1933

    V784位掩码的大小小于第一个操作数的大小。 这将导致丢失更高的位。 1933年

可疑循环 (Suspicious Loop)

V612 An unconditional 'return' within a loop. conf.c 3477 V612循环内无条件的“返回”。 3477号会议
#define lxc_list_for_each(__iterator, __list) \
  for (__iterator = (__list)->next; __iterator != __list; \
          __iterator = __iterator->next)

static bool verify_start_hooks(struct lxc_conf *conf)
{
  char path[PATH_MAX];
  struct lxc_list *it;

  lxc_list_for_each (it, &conf->hooks[LXCHOOK_START]) {
    int ret;
    char *hookname = it->elem;

    ret = snprintf(path, PATH_MAX, "%s%s",
             conf->rootfs.path ? conf->rootfs.mount : "",
             hookname);
    if (ret < 0 || ret >= PATH_MAX)
      return false;

    ret = access(path, X_OK);
    if (ret < 0) {
      SYSERROR("Start hook \"%s\" not found in container",
         hookname);
      return false;
    }

    return true; // <=
  }

  return true;
}

The loop is started and interrupted on the first iteration. This might have been made intentionally, but in this case the loop could have been omitted.

循环在第一次迭代时开始并中断。 这可能是有意做的,但是在这种情况下,可以省略循环。

数组索引超出范围 (Array Index out of Bounds)

V557 Array underrun is possible. The value of 'bytes — 1' index could reach -1. network.c 2570 V557阵列欠载是可能的。 “字节_1”索引的值可能达到-1。 网络.c 2570
static int lxc_create_network_unpriv_exec(const char *lxcpath,
                                          const char *lxcname,
                                          struct lxc_netdev *netdev, 
                                          pid_t pid,
                                          unsigned int hooks_version)
{
  int bytes;
  char buffer[PATH_MAX] = {0};
  ....
  bytes = lxc_read_nointr(pipefd[0], &buffer, PATH_MAX);
  if (bytes < 0)
  {
    SYSERROR("Failed to read from pipe file descriptor");
    close(pipefd[0]);
  }
  else
  {
    buffer[bytes - 1] = '\0';
  }
  ....
}

Bytes are read in the buffer from the pipe. In case of an error, the lxc_read_nointr function will return a negative value. If all goes successfully, a terminal null is written by the last element. However, if 0 bytes are read, the index will be out of buffer bounds, leading to undefined behavior.

从管道读取缓冲区中的字节。 如果发生错误, lxc_read_nointr函数将返回负值。 如果一切顺利,最后一个元素将写入一个终端null。 但是,如果读取0个字节,则索引将超出缓冲区范围,从而导致未定义的行为。

The analyzer issued another similar warning:

分析仪发出了另一个类似的警告:

  • V557 Array underrun is possible. The value of 'bytes — 1' index could reach -1. network.c 2725

    V557阵列欠载是可能的。 “字节_1”索引的值可能达到-1。 网络.c 2725

缓冲区溢出 (Buffer Overflow)

V576 Incorrect format. Consider checking the third actual argument of the 'sscanf' function. It's dangerous to use string specifier without width specification. Buffer overflow is possible. lxc_unshare.c 205 V576格式错误。 考虑检查“ sscanf”函数的第三个实际参数。 使用不带宽度说明的字符串说明符很危险。 缓冲区可能溢出。 lxc_unshare.c 205
static bool lookup_user(const char *oparg, uid_t *uid)
{
  char name[PATH_MAX];
  ....
  if (sscanf(oparg, "%u", uid) < 1)
  {
    /* not a uid -- perhaps a username */
    if (sscanf(oparg, "%s", name) < 1) // <=
    {
      free(buf);
      return false;
    }
    ....
  }
  ....
}

In this case, usage of sscanf can be dangerous, because if the oparq buffer is larger than the name buffer, the index will be out of bounds when forming the name buffer.

在这种情况下,使用sscanf可能很危险,因为如果oparq缓冲区大于名称缓冲区,则在形成名称缓冲区时索引将超出范围。

结论 (Conclusion)

As we see, it's a quite simple task to configure a static code analyzer check in a cloud. For this, we just need to add one file in a repository and spend little time setting up the CI system. As a result, we'll get a tool to detect problem at the stage of writing code. The tool lets us prevent bugs from getting to the next stages of testing, where their fixing will require much time and efforts.

如我们所见,在云中配置静态代码分析器检查是一个非常简单的任务。 为此,我们只需要在存储库中添加一个文件,而花费很少的时间来设置CI系统。 结果,我们将获得在编写代码阶段检测问题的工具。 该工具使我们能够防止错误进入测试的下一阶段,在该阶段进行修正将需要大量时间和精力。

Of course, PVS-Studio usage with cloud platforms is not only limited to Travis CI. Similar to the method described in the article, with small differences, PVS-Studio analysis can be integrated into other popular cloud CI solutions, such as CircleCI, GitLab, etc.

当然,PVS-Studio在云平台上的使用不仅限于Travis CI。 与本文中描述的方法相似,只有一点点不同,PVS-Studio分析可以集成到其他流行的云CI解决方案中,例如CircleCI,GitLab等。

有用的链接 (Useful links)

  • For additional information on running PVS-Studio on Linux and MacOS, follow the link.

    有关在Linux和MacOS上运行PVS-Studio的其他信息,请单击链接

  • You can also read about creating, setting and using containers with installed PVS-Studio static code analyzer by the link.

    您还可以阅读有关创建,设置和使用的容器由安装PVS-Studio的静态代码分析仪的链接

  • TravisCI documentation.

    TravisCI文档

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

pvs-stdio ue4

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值