summaryrefslogtreecommitdiff
path: root/SafetyCenter/ConfigLintChecker/java/android/safetycenter/lint/ParserExceptionDetector.kt
blob: d8656f889c3c17a69f371ec40d1263d23410a4a9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.safetycenter.lint

import android.content.res.Resources
import com.android.SdkConstants.ATTR_NAME
import com.android.SdkConstants.TAG_STRING
import com.android.modules.utils.build.SdkLevel
import com.android.resources.ResourceFolderType
import com.android.safetycenter.config.ParseException
import com.android.safetycenter.config.SafetyCenterConfigParser
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Context
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.Location
import com.android.tools.lint.detector.api.OtherFileScanner
import com.android.tools.lint.detector.api.Scope
import com.android.tools.lint.detector.api.Severity
import com.android.tools.lint.detector.api.XmlContext
import com.android.tools.lint.detector.api.XmlScanner
import java.util.EnumSet
import org.w3c.dom.Element
import org.w3c.dom.Node

/** Lint check for detecting invalid Safety Center configs */
class ParserExceptionDetector : Detector(), OtherFileScanner, XmlScanner {

    companion object {
        val ISSUE =
            Issue.create(
                id = "InvalidSafetyCenterConfig",
                briefDescription = "The Safety Center config parser detected an error",
                explanation =
                    """The Safety Center config must follow all constraints defined in \
                safety_center_config.xsd. Check the error message to find out the specific \
                constraint not met by the current config.""",
                category = Category.CORRECTNESS,
                severity = Severity.ERROR,
                implementation =
                    Implementation(
                        ParserExceptionDetector::class.java,
                        EnumSet.of(Scope.RESOURCE_FILE, Scope.OTHER)
                    ),
                androidSpecific = true
            )

        val STRING_MAP_BUILD_PHASE = 1
        val CONFIG_PARSE_PHASE = 2
    }

    override fun appliesTo(folderType: ResourceFolderType): Boolean {
        return folderType == ResourceFolderType.RAW || folderType == ResourceFolderType.VALUES
    }

    override fun afterCheckEachProject(context: Context) {
        context.driver.requestRepeat(this, Scope.OTHER_SCOPE)
    }

    /** Implements XmlScanner and builds a map of string resources in the first phase */
    val mNameToIndex: MutableMap<String, Int> = mutableMapOf()
    val mIndexToValue: MutableMap<Int, String> = mutableMapOf()
    var mIndex = 1000

    override fun getApplicableElements(): Collection<String>? {
        return listOf(TAG_STRING)
    }

    override fun visitElement(context: XmlContext, element: Element) {
        if (
            context.driver.phase != STRING_MAP_BUILD_PHASE ||
                context.resourceFolderType != ResourceFolderType.VALUES
        ) {
            return
        }
        val name = element.getAttribute(ATTR_NAME)
        var value = ""
        for (index in 0 until element.childNodes.length) {
            val child = element.childNodes.item(index)
            if (child.nodeType == Node.TEXT_NODE) {
                value = child.nodeValue
                break
            }
        }
        mNameToIndex[name] = mIndex
        mIndexToValue[mIndex] = value
        mIndex++
    }

    /** Implements OtherFileScanner and parses the XML config in the second phase */
    override fun run(context: Context) {
        if (
            context.driver.phase != CONFIG_PARSE_PHASE ||
                context.file.name != "safety_center_config.xml"
        ) {
            return
        }
        val minSdk = FileSdk.getSdkQualifier(context.file)
        val maxSdk = maxOf(minSdk, FileSdk.getMaxSdkVersion())
        // Test the parser at the SDK level for which the config was designed.
        // Then test parsers at higher SDK levels for backward compatibility.
        // This is slightly inefficient if a parser at a higher SDK level has no behavioral changes
        // compared to one at a lower SDK level, but doing an exhaustive search is safer.
        for (sdk in minSdk..maxSdk) {
            synchronized(SdkLevel::class.java) {
                SdkLevel.setSdkInt(sdk)
                try {
                    SafetyCenterConfigParser.parseXmlResource(
                        context.file.inputStream(),
                        // Note: using a map of the string resources present in the APK under
                        // analysis is necessary in order to get the value of string resources that
                        // are resolved and validated at parse time. The drawback of this is that
                        // the linter cannot be used on overlay packages that refer to resources in
                        // the target package or on packages that refer to Android global resources.
                        // However, we cannot use a custom linter with the default soong overlay
                        // build rule regardless.
                        Resources(context.project.`package`, mNameToIndex, mIndexToValue)
                    )
                } catch (e: ParseException) {
                    context.report(
                        ISSUE,
                        Location.create(context.file),
                        "Parser exception at sdk=$sdk: \"${e.message}\", cause: " +
                            "\"${e.cause?.message}\""
                    )
                }
            }
        }
    }
}