Program static analysis

Program Static Analysis refers to a code Analysis technology that scans Program code through lexical Analysis, syntax Analysis, control flow Analysis, data flow Analysis and other technologies without running the code to verify whether the code meets norms, security, reliability, maintainability and other indicators. (From Baidu Encyclopedia)

Lexical analysis, grammar analysis and other work is carried out by the compiler, so the iOS project in order to complete the static analysis, we need to use the compiler. OC language static analysis can be completely through Clang, Swift static analysis in addition to Clange also need the help of SourceKit.

The static analysis tool corresponding to Swift is SwiftLint, and the static analysis tool corresponding to OC is Infer and OCLitn. Below is an introduction to the installation and use of each static analysis tool.

SwiftLint

SwiftLint
AST

Included in the Swift project’s main repository, SourceKit is a toolset that supports most of Swift’s source code manipulation features: source code parsing, syntax highlighting, typography, auto-completion, cross-language header generation, and more.

The installation

There are two ways to install it, one of which is Homebrew

$ brew install swiftlint
Copy the code

This is a global installation and can be used by each application. Method 2: Use CocoaPods

pod 'SwiftLint', :configurations => ['Debug']
Copy the code

This approach is equivalent to integrating SwiftLint into the project as a tripartite library, and since it is only a debugging tool, we should specify that it only works in the Debug environment.

Integrated into the Xcode

We need to add a Run Script Phase to the Build Phases in the project. If installed via Homebrew, your script should look like this.

if which swiftlint >/dev/null; then
  swiftlint
else
  echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint"
fi
Copy the code

If you’re installing through Cocoapods, your script should look like this:

"${PODS_ROOT}/SwiftLint/swiftlint"
Copy the code

Run SwiftLint

Type CMD + B to compile the project. After compiling, the script we just added will be run. After that, we will see the warning message for a large chunk of the project. Sometimes build information is not included in the project code and can be viewed in the build log.

custom

There are so many SwiftLint rules that we can configure SwfitLint if we don’t want to enforce a rule, or if we want to filter out analysis of the Pods library.

Create a new.swiftlint.yml file in the project root directory and fill it with the following:

disabled_rules: # rule identifiers to exclude from running
  - colon
  - trailing_whitespace
  - vertical_whitespace
  - function_body_length
opt_in_rules: # some rules are only opt-in
  - empty_count
  # Find all the available rules by running:
  # swiftlint rules
included: # paths to include during linting. `--path` is ignored if present.
  - Source
excluded: # paths to ignore during linting. Takes precedence over `included`.
  - Carthage
  - Pods
  - Source/ExcludedFolder
  - Source/ExcludedFile.swift
  - Source/*/ExcludedFile.swift # Exclude files with a wildcard
analyzer_rules: # Rules run by `swiftlint analyze` (experimental)
  - explicit_self

# configurable rules can be customized from this configuration file
# binary rules can set their severity level
force_cast: warning # implicitly
force_try:
  severity: warning # explicitly
# rules that have both warning and error levels, can set just the warning level
# implicitly
line_length: 110
# they can set both implicitly with an array
type_body_length:
  - 300 # warning
  - 400 # error
# or they can set both explicitly
file_length:
  warning: 500
  error: 1200
# naming rules can set warnings/errors for min_length and max_length
# additionally they can set excluded names
type_name:
  min_length: 4 # only warning
  max_length: # warning and error
    warning: 40
    error: 50
  excluded: iPhone # excluded via string
  allowed_symbols: (" _ ") # these are allowed in type names
identifier_name:
  min_length: # only min_length
    error: 4 # only error
  excluded: # excluded via string array
    - id
    - URL
    - GlobalAPIKey
reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit, html, emoji, sonarqube, markdown)
Copy the code

A rules prompt reads as follows, and its rules name is function_body_length.

! Function Body Length Violation: Function body should span 40 lines or less excluding comments and whitespace: currently spans 43 lines (function_body_length)
Copy the code

Disabled_rules fills in the rules we don’t want to follow.

Excluded sets the directories we want to skip checking. Carthage, Pod, and SubModule can generally be filtered out.

Others, such as file length (file_length) and type name length (type_name), can be adjusted by setting specific values.

In addition, SwiftLint also supports custom rules, so you can define your own rules according to your own requirements.

Generate a report

If we wanted to generate a report from this analysis, we could (this is via Homebrew installed swiftLint) :

# reporter type (xcode, json, csv, checkstyle, junit, html, emoji, sonarqube, markdown)
$ swiftlint lint --reporter html > swiftlint.html
Copy the code

xcodebuild

Xcodebuild is a build command built into Xcode that we can use to build and package our iOS projects. Infer and OCLint are all analyzed based on xcodeBuild builds, so it’s worth mentioning a little bit about it.

To compile a project, we need to specify the project name, configuration, scheme, SDK, etc. The following are some simple commands and instructions.

#Project without POD, target named TargetName, under Debug, specify simulator SDK environment to compile
xcodebuild -target TargetName -configuration Debug -sdk iphonesimulator
#For projects with POD, the workspace name is targetname.xcworkspace, and under Release, Scheme is TargetName, specifying the real machine environment to compile. Do not specify that the emulator environment validates the certificate
xcodebuild -workspace WorkspaceName.xcworkspace -scheme SchemeName Release
#Understand the compiled product of the project
xcodebuild -workspace WorkspaceName.xcworkspace -scheme SchemeName Release clean
Copy the code

Subsequent use of the XcodeBuild command requires these parameters to be replaced with your own project parameters.

Infer

Infer

  • Resource leaks and memory leaks
  • Non-null detection of variables and parameters
  • A circular reference
  • Premature nil operation

Custom rules are not supported.

Installation and use

$ brew install infer
Copy the code

Run infer

$ cd projectDir
#Skip the Pods analysis
$ infer run --skip-analysis-in-path Pods -- xcodebuild -workspace "Project.xcworkspace" -scheme "Scheme" -configuration Debug -sdk iphonesimulator
Copy the code

We will get a infer-out folder, which contains various code analysis files in TXT, JSON and other file formats. When this is not easy to view, we can convert them to HTML format:

$ infer explore --html
Copy the code

Click Trace to see the context of the code in question.

Infer is built increments by default and will only analyze changing code, so if we want to build as a whole, we need to clean up the project:

$ xcodebuild -workspace "Project.xcworkspace" -scheme "Scheme" -configuration Debug -sdk iphonesimulator clean
Copy the code

Run Infer again to compile.

$ infer run --skip-analysis-in-path Pods -- xcodebuild -workspace "Project.xcworkspace" -scheme "Scheme" -configuration Debug -sdk iphonesimulator
Copy the code

Infer: The general principles

Infer’s static analysis can be divided into two stages:

1. Capture stage

Infer captures the build commands and translates the files into the Infer internal intermediate language.

Infer takes information from the compilation process and translates it. Infer — clang -c file.c, Infer — javac file.java. The result is that the file will compile as usual and be Infer translated into the intermediate language, leaving it for the second stage. In particular, if no files are compiled, then no files are analyzed.

Infer stores the intermediate files in the resulting folder. Normally, this folder will be created under the same directory as Infer run and named Infer -out/.

2. Analysis stage

In the analysis phase, Infer analyzes all files under Infer -out/. When analyzing, each method and function is analyzed separately.

If an error is found while analyzing a function, the analysis is stopped, but it does not affect the analysis of other functions.

So when you check problems and fix output errors, you need to run Infer to check and confirm that all problems have been fixed.

In addition to being shown in standard output, errors are also output to the file Infer -out/bug.txt. We filter these problems and show only the most likely ones.

In the infer out result folder, there is also a CSV file return.csv, which contains all the information generated by infer, including errors, warnings and information.

OCLint

OCLint is a library written based on Clange Tooling, which supports expansion and has a larger scope of detection than Infer. As well as hidden bugs, some normative issues such as naming and function complexity are also covered.

Install OCLint

OCLint is typically installed through Homebrew

$ brew tap oclint/formulae   
$ brew install oclint
Copy the code

The version installed via Hombrew is 0.13.

$oclint --version LLVM (http://llvm.org/): LLVM version 5.0.0 SVN-R313528 Optimized build. Default target: X86_64-apple-darwin19.0.0 Host CPU: Skylake OCLint (http://oclint.org/):) OCLint Version 0.13. Built Sep 18 2017 (08:58:40)Copy the code

I have run OCLint on two projects with Xcode11 respectively. One instance project runs normally, while the other complex project fails, and the following error is reported:

1 error generated 1 error generated ... oclint: error: cannot open report output file .... /onlintReport.htmlCopy the code

I don’t know why, but if you want to see if 0.13 works, skip to installing xcpretty. If you are also experiencing this problem, you can come back and install oclint version 0.15.

OCLint0.15

I found this problem and the corresponding solution here at Oclint Issuse #547.

We need to update Oclint to version 0.15. The latest version is 0.13 on Brew and 0.15 on Github. I downloaded release0.15 on github, but the package was not compiled, so I had to manually compile it. Since compiling downloads LLVM and Clange, which are both large packages, I uploaded the compiled package directly to CodeChecker.

If you don’t care about the build process, download the compiled package and skip to setting environment variables.

Compile OCLint

1. Install CMake and Ninja

$ brew install cmake ninja
Copy the code

2. Clone OCLint Project

$ git clone https://github.com/oclint/oclint
Copy the code

3. Go to the Oclint-scripts directory and run the make command

$ ./make
Copy the code

After success, the build folder appears, which contains an Oclint-release, which is the oclint tool that compiled successfully.

Set the environment variables for the Oclint tool

Environment variables are set up so that we can access them quickly. Then we need to configure the PATH environment variable, noting that the PATH of OCLint_PATH is the PATH where you stored the Oclint-Release. Add it to the end of the.zshrc or.bash_profile file:

OCLint_PATH=/Users/zhangferry/oclint/build/oclint-release
export PATH=$OCLint_PATH/bin:$PATH
Copy the code

Execute source.zshrc, refresh the environment variables, and verify that OClint is installed successfully:

$oclint --version oclint (http://oclint.org/): oclint version 0.15. Built May 19 2020 (11:48:49).Copy the code

The presence of this introduction indicates that we have completed the installation.

Install xcpretty

Xcpretty is a scripting tool that formats xcodeBuild output, and oclint parsing depends on its output. It is installed as follows:

$ gem install xcpretty
Copy the code

The use of OCLint

Before you can use OCLint, you need to set the COMPILER_INDEX_STORE_ENABLE to NO.

  • Under Building Settings in Project and TargetsCOMPILER_INDEX_STORE_ENABLESet toNO
  • Add the following script before target’ target’ do in your podfile to change the compilation configuration of each pod to this option as well
post_install do |installer|
  installer.pods_project.targets.each do |target|
      target.build_configurations.each do |config|
          config.build_settings['COMPILER_INDEX_STORE_ENABLE'] = "NO"
      end
  end
end
Copy the code

use

1. Go to the root directory of the project and run the following script:

$xcodebuild -workspace ProjectName.xcworkspace -scheme ProjectScheme -configuration Debug -sdk iphonesimulator | xcpretty  -r json-compilation-database -o compile_commands.json
Copy the code

Json file. If we see this file in the project root directory and there is something in it, we have done the first step.

2. We convert the JSON file into HTML for easy viewing, filter out the Pods file analysis, and limit the number of lines to prevent an upper limit:

$ oclint-json-compilation-database -e Pods -- -report-type html -o oclintReport.html -rc LONG_LINE=9999 -max-priority-1=9999 -max-priority-2=9999 -max-priority-3=9999
Copy the code

The result is an oclintreport.html file.

OCLint supports custom rules because its own rules are already rich, and the need for custom rules should be minimal and not attempted.

Encapsulation script

As with Infer, OCLint is executed by running several scripting languages. These commands can be packaged into a script file. For example, OCLint is Infer:

#! /bin/bash
# mark sure you had install the oclint and xcpretty

# You need to replace these values with your own project configuration
workspace_name="WorkSpaceName.xcworkspace"
scheme_name="SchemeName"

# remove history
rm compile_commands.json
rm oclint_result.xml
# clean project
# -sdk iphonesimulator means run simulator
xcodebuild -workspace $workspace_name -scheme $scheme_name -configuration Debug -sdk iphonesimulator clean || (echo "command failed"; exit 1);

# export compile_commands.json
xcodebuild -workspace $workspace_name -scheme $scheme_name -configuration Debug -sdk iphonesimulator \
| xcpretty -r json-compilation-database -o compile_commands.json \
|| (echo "command failed"; exit 1);

# export report html
# you can run `oclint -help` to see all USAGE
oclint-json-compilation-database -e Pods -- -report-type html -o oclintReport.html \
-disable-rule ShortVariableName \
-rc LONG_LINE=1000 \
|| (echo "command failed"; exit 1);

open -a "/Applications/Safari.app" oclintReport.html
Copy the code

Oclint-json-compilation-database

-e Indicates the files that need to be ignored. The warnings of these files do not appear in the report

-rc Specifies the threshold of the rule to be overwritten. You can customize the threshold of the item, the default threshold

-enable-rule Specifies the rule that is supported by Oclint by default. You can use the combination of -disable-rule to filter out some rule lists

-disable-rule Specifies the rule to be ignored. Set this parameter based on project requirements

Use OCLint in Xcode

Because OCLint provides an output style in Xcode format, we can put it in Xcode as a script.

1. Under TARGETS of the project, click the “+” below to select Aggregate under cross-platform. Enter a name, in this case OCLint

Select the Target, go to Build Phases, add the Run Script, and write the following Script:

# Type a script or drag a script file from your workspace to insert its path.
#Built-in variables
cd ${SRCROOT}
xcodebuild clean 
xcodebuild | xcpretty -r json-compilation-database
oclint-json-compilation-database -e Pods -- -report-type xcode
Copy the code

As you can see, this script is the same as the previous one, except that the -report-type of the oclint-json-compilation-database command is changed from HTML to Xcode. OCLint as a target itself runs in a specific environment, so XCodeBuild can omit configuration parameters.

CMD + B: run CMD + B: run CMD + B: run CMD + B

conclusion

The following is a comparison of these static analysis schemes. We can choose our own static analysis schemes according to our requirements.

SwiftLint Infer OCLint
Support language Swift C, C++, OC, Java C, C++, OC
Ease of use simple The simpler The simpler
Can it be integrated into Xcode can Cannot integrate into Xcode can
Richness of built-in rules More, including code specifications Relatively few, mainly detection of potential problems More, including code specifications
Rule extensibility can Can not be can

reference

OCLint Code Review – To improve the quality of your Code

Using OCLint in Xcode

Infer’s working mechanism

Getting started with LLVM & Clang