blob: 261f2eeedd3fa42699e2b5271a611c9b137bd15d [file] [log] [blame]
Steven Moreland65b3fd92017-12-06 14:18:35 -08001// Copyright 2017 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package android
16
17import (
18 "path/filepath"
19 "reflect"
20 "strconv"
21 "strings"
22
23 "github.com/google/blueprint/proptools"
24)
25
26// "neverallow" rules for the build system.
27//
28// This allows things which aren't related to the build system and are enforced
29// for sanity, in progress code refactors, or policy to be expressed in a
30// straightforward away disjoint from implementations and tests which should
31// work regardless of these restrictions.
32//
33// A module is disallowed if all of the following are true:
34// - it is in one of the "in" paths
35// - it is not in one of the "notIn" paths
36// - it has all "with" properties matched
37// - - values are matched in their entirety
38// - - nil is interpreted as an empty string
39// - - nested properties are separated with a '.'
40// - - if the property is a list, any of the values in the list being matches
41// counts as a match
42// - it has none of the "without" properties matched (same rules as above)
43
44func registerNeverallowMutator(ctx RegisterMutatorsContext) {
45 ctx.BottomUp("neverallow", neverallowMutator).Parallel()
46}
47
48var neverallows = []*rule{
49 neverallow().in("vendor", "device").with("vndk.enabled", "true").
50 because("the VNDK can never contain a library that is device dependent."),
51 neverallow().with("vndk.enabled", "true").without("owner", "").
52 because("a VNDK module can never have an owner."),
53 neverallow().notIn("libcore").with("no_standard_libs", "true"),
54
55 // TODO(b/67974785): always enforce the manifest
56 neverallow().
57 without("name", "libhidltransport").
58 with("product_variables.enforce_vintf_manifest.cflags", "*").
59 because("manifest enforcement should be independent of ."),
60
61 // TODO(b/67975799): vendor code should always use /vendor/bin/sh
62 neverallow().
63 without("name", "libc_bionic_ndk").
64 with("product_variables.treble_linker_namespaces.cflags", "*").
65 because("nothing should care if linker namespaces are enabled or not"),
66
67 // Example:
68 // *neverallow().with("Srcs", "main.cpp"),
69}
70
71func neverallowMutator(ctx BottomUpMutatorContext) {
72 m, ok := ctx.Module().(Module)
73 if !ok {
74 return
75 }
76
77 dir := ctx.ModuleDir() + "/"
78 properties := m.GetProperties()
79
80 for _, n := range neverallows {
81 if !n.appliesToPath(dir) {
82 continue
83 }
84
85 if !n.appliesToProperties(properties) {
86 continue
87 }
88
89 ctx.ModuleErrorf("violates " + n.String())
90 }
91}
92
93type ruleProperty struct {
94 fields []string // e.x.: Vndk.Enabled
95 value string // e.x.: true
96}
97
98type rule struct {
99 // User string for why this is a thing.
100 reason string
101
102 paths []string
103 unlessPaths []string
104
105 props []ruleProperty
106 unlessProps []ruleProperty
107}
108
109func neverallow() *rule {
110 return &rule{}
111}
112func (r *rule) in(path ...string) *rule {
113 r.paths = append(r.paths, cleanPaths(path)...)
114 return r
115}
116func (r *rule) notIn(path ...string) *rule {
117 r.unlessPaths = append(r.unlessPaths, cleanPaths(path)...)
118 return r
119}
120func (r *rule) with(properties, value string) *rule {
121 r.props = append(r.props, ruleProperty{
122 fields: fieldNamesForProperties(properties),
123 value: value,
124 })
125 return r
126}
127func (r *rule) without(properties, value string) *rule {
128 r.unlessProps = append(r.unlessProps, ruleProperty{
129 fields: fieldNamesForProperties(properties),
130 value: value,
131 })
132 return r
133}
134func (r *rule) because(reason string) *rule {
135 r.reason = reason
136 return r
137}
138
139func (r *rule) String() string {
140 s := "neverallow"
141 for _, v := range r.paths {
142 s += " dir:" + v + "*"
143 }
144 for _, v := range r.unlessPaths {
145 s += " -dir:" + v + "*"
146 }
147 for _, v := range r.props {
148 s += " " + strings.Join(v.fields, ".") + "=" + v.value
149 }
150 for _, v := range r.unlessProps {
151 s += " -" + strings.Join(v.fields, ".") + "=" + v.value
152 }
153 if len(r.reason) != 0 {
154 s += " which is restricted because " + r.reason
155 }
156 return s
157}
158
159func (r *rule) appliesToPath(dir string) bool {
160 includePath := len(r.paths) == 0 || hasAnyPrefix(dir, r.paths)
161 excludePath := hasAnyPrefix(dir, r.unlessPaths)
162 return includePath && !excludePath
163}
164
165func (r *rule) appliesToProperties(properties []interface{}) bool {
166 includeProps := hasAllProperties(properties, r.props)
167 excludeProps := hasAnyProperty(properties, r.unlessProps)
168 return includeProps && !excludeProps
169}
170
171// assorted utils
172
173func cleanPaths(paths []string) []string {
174 res := make([]string, len(paths))
175 for i, v := range paths {
176 res[i] = filepath.Clean(v) + "/"
177 }
178 return res
179}
180
181func fieldNamesForProperties(propertyNames string) []string {
182 names := strings.Split(propertyNames, ".")
183 for i, v := range names {
184 names[i] = proptools.FieldNameForProperty(v)
185 }
186 return names
187}
188
189func hasAnyPrefix(s string, prefixes []string) bool {
190 for _, prefix := range prefixes {
191 if strings.HasPrefix(s, prefix) {
192 return true
193 }
194 }
195 return false
196}
197
198func hasAnyProperty(properties []interface{}, props []ruleProperty) bool {
199 for _, v := range props {
200 if hasProperty(properties, v) {
201 return true
202 }
203 }
204 return false
205}
206
207func hasAllProperties(properties []interface{}, props []ruleProperty) bool {
208 for _, v := range props {
209 if !hasProperty(properties, v) {
210 return false
211 }
212 }
213 return true
214}
215
216func hasProperty(properties []interface{}, prop ruleProperty) bool {
217 for _, propertyStruct := range properties {
218 propertiesValue := reflect.ValueOf(propertyStruct).Elem()
219 for _, v := range prop.fields {
220 if !propertiesValue.IsValid() {
221 break
222 }
223 propertiesValue = propertiesValue.FieldByName(v)
224 }
225 if !propertiesValue.IsValid() {
226 continue
227 }
228
229 check := func(v string) bool {
230 return prop.value == "*" || prop.value == v
231 }
232
233 if matchValue(propertiesValue, check) {
234 return true
235 }
236 }
237 return false
238}
239
240func matchValue(value reflect.Value, check func(string) bool) bool {
241 if !value.IsValid() {
242 return false
243 }
244
245 if value.Kind() == reflect.Ptr {
246 if value.IsNil() {
247 return check("")
248 }
249 value = value.Elem()
250 }
251
252 switch value.Kind() {
253 case reflect.String:
254 return check(value.String())
255 case reflect.Bool:
256 return check(strconv.FormatBool(value.Bool()))
257 case reflect.Int:
258 return check(strconv.FormatInt(value.Int(), 10))
259 case reflect.Slice:
260 slice, ok := value.Interface().([]string)
261 if !ok {
262 panic("Can only handle slice of string")
263 }
264 for _, v := range slice {
265 if check(v) {
266 return true
267 }
268 }
269 return false
270 }
271
272 panic("Can't handle type: " + value.Kind().String())
273}