Refer to the article: zhuanlan.zhihu.com/p/307382854 zhuanlan.zhihu.com/p/43609710

Demo project: github.com/TokenYc/Lin… Demo has some built-in detectors for detection.

Lint is used to detect static code and resources and find places in them that do not conform to predefined rules.

Lint can be used to detect user privacy methods in the SDK. The biggest pitfall with custom Lint is that it can fail to implement Lint because of the different dependency versions in the project. Therefore, if you write your own version number, you should use the demo version number, and then change the version according to the project when the test works properly. Include:

  • Lint – API version
CompileOnly com. Android. "tools. Lint: lint - API: 27.1.3"Copy the code
  • build tool
The classpath "com. Android. Tools. Build: gradle: 4.1.3." "Copy the code
  • Yes, you read that right.
Implementation 'androidx. Appcompat: appcompat: 1.2.0'Copy the code

End result: You can see which SDKS call privacy-related methods.

Custom Lint

If you’re new to Lint, it’s recommended to follow the steps before you understand them.

1. Create a Java Module

Add it in build.gradle of the Java Module

Dependencies {compileOnly "com. Android. Tools. Lint: lint - API: 27.1.3"}Copy the code

2. Create Detector and Issue

First, a Detector, which stands for a Detector. Inherit Detector and then implement ClassScanner. Just put the files in the Java Module you created. Why ClassScanner? There are actually several scanners available, but so far only one Scanner has been tested that can scan three SDKS. Directly to the code, here to detect the call to obtain IMEI method as an example (part of the method internal code has been omitted) :

package com.qianfanyun.lintlib.detector import com.android.tools.lint.detector.api.* import org.objectweb.asm.Opcodes import org.objectweb.asm.tree.AbstractInsnNode import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.MethodInsnNode import org.objectweb.asm.tree.MethodNode class StealImeiDetector : Detector (), ClassScanner {/ * * * returns the ASM instructions * / override of the Detector for fun getApplicableAsmNodeTypes () : IntArray? {// We are interested in instructions related to method calls, Return intArrayOf(AbstractinsnNode. METHOD_INSN)} /** * Override fun checkInstruction(context: ClassContext, classNode: classNode, method: MethodNode, instruction: override fun checkInstruction(context: ClassContext, classNode: classNode, method: MethodNode, instruction: AbstractInsnNode) { } }Copy the code

Two methods are overridden:

  • GetApplicableAsmNodeTypes said this approach, the scanner to scan what instructions. So what we’re going to return is Method, which is a Method instruction.
  • The checkInstruction method provides a description of some methods to facilitate filtering.

Once you understand the overall structure, add the logic:

package com.qianfanyun.lintlib.detector import com.android.tools.lint.detector.api.* import org.objectweb.asm.Opcodes import org.objectweb.asm.tree.AbstractInsnNode import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.MethodInsnNode import org.objectweb.asm.tree.MethodNode /** * @date on 2019-12-23 14:31 * @author  ArcherYc * @mail [email protected] */ class StealImeiDetector : Detector (), ClassScanner {/ * * * returns the ASM instructions * / override of the Detector for fun getApplicableAsmNodeTypes () : IntArray? {// We are interested in instructions related to method calls, Return intArrayOf(AbstractinsnNode. METHOD_INSN)} /** * Override fun checkInstruction(context: ClassContext, classNode: classNode, method: MethodNode, instruction: override fun checkInstruction(context: ClassContext, classNode: classNode, method: MethodNode, instruction: AbstractInsnNode) { if (instruction.opcode ! = Opcodes.INVOKEVIRTUAL) { return } val callerMethodSig = classNode.name + "." + method.name + method.desc val MethodInsn = instruction as MethodInsnNode Call any methods in NfcAdapter will report abnormal if (methodInsn. Name = = "getDeviceId && methodInsn. The owner = =" android/telephony/TelephonyManager ") {val message = "SDK $callerMethodSig calls the" + "${methodInsn. Owner. SubstringAfterLast ('/')}. ${methodInsn. Name}, Be careful!" context.report(ISSUE, method, methodInsn, context.getLocation(methodInsn), message) } } }Copy the code

The logical code is easier to understand, comparing the name of the method with the class of the method to find the method we want to detect. Lint is then added to its detection report using the report method.

Then Issue. The Detector represents a Detector, and Issure represents a problem, which can be interpreted as a description of the Detector. Typically placed directly in the Detector as a static member. Create the method is relatively fixed, the specific parameter represents what, run again to know.

package com.qianfanyun.lintlib.detector import com.android.tools.lint.detector.api.* import org.objectweb.asm.Opcodes import org.objectweb.asm.tree.AbstractInsnNode import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.MethodInsnNode import org.objectweb.asm.tree.MethodNode /** * @date on 2019-12-23 14:31 * @author  ArcherYc * @mail [email protected] */ class StealImeiDetector : Detector(), ClassScanner {companion object {val ISSUE = issue.create ("StealImei",// problem Id "",// simple description of problem, Will be overridden by description of incoming report interface "",// detailed description of the problem category. CORRECTNESS,// Problem type 6,// Problem severity, 0~10, Serious Severity. The greater the Severity ERROR, the problem of / / / / the Detector and the Scope of the corresponding relation between Implementation (StealImeiDetector: : class. Java, Scope. ALL_CLASSES_AND_LIBRARIES))} / * * * returns the ASM instructions * / override of the Detector for fun getApplicableAsmNodeTypes () : IntArray? {// We are interested in instructions related to method calls, Return intArrayOf(AbstractinsnNode. METHOD_INSN)} /** * Override fun checkInstruction(context: ClassContext, classNode: classNode, method: MethodNode, instruction: override fun checkInstruction(context: ClassContext, classNode: classNode, method: MethodNode, instruction: AbstractInsnNode) { if (instruction.opcode ! = Opcodes.INVOKEVIRTUAL) { return } val callerMethodSig = classNode.name + "." + method.name + method.desc val MethodInsn = instruction as MethodInsnNode Call any methods in NfcAdapter will report abnormal if (methodInsn. Name = = "getDeviceId && methodInsn. The owner = =" android/telephony/TelephonyManager ") {val message = "SDK $callerMethodSig calls the" + "${methodInsn. Owner. SubstringAfterLast ('/')}. ${methodInsn. Name}, Be careful!" context.report(ISSUE, method, methodInsn, context.getLocation(methodInsn), message) } } }Copy the code

At this point, the most important part is done.

3. Create a Registry

(1) You need to register an Issue with Lint through a Registry. The code is very simple, the issues that need to be registered can be directly loaded into the list. API returns the fixed CURRENT_API.

package com.qianfanyun.lintlib

import com.android.tools.lint.client.api.IssueRegistry
import com.android.tools.lint.detector.api.CURRENT_API
import com.android.tools.lint.detector.api.Issue
import com.qianfanyun.lintlib.detector.*
import java.util.*


class DangerIssueRegistry : IssueRegistry() {

    override val issues: List<Issue>
        get() {
            return Arrays.asList(
                StealImeiDetector.ISSUE,
            )
        }

    override val api: Int
        get() = CURRENT_API
}
Copy the code

(2) Add it to build.gradle in the Java Module

jar {
    manifest {
        attributes("Lint-Registry-v2": "com.qianfanyun.lintlib.DangerIssueRegistry")
    }
}
Copy the code

4. Build. Gradle in your app

Build. Gradle in your app

dependencies {
    lintChecks project(':lintlib')
}
Copy the code

5. Perform

Execute on the command line

gradle app:lintDebug
Copy the code

If you add Varint to your project, you need to add the corresponding name between lintDebug. The corresponding command becomes

gradle app:lintxxxxDebug
Copy the code

After execution, if all goes well, two links appear, one in HTML format and one in XML format.Click to check it out. HTML can be viewed in a browser. Something like thatIn this way, you can find code in third-party SDKS that involves privacy policies.