blob: 217a8d44f36a791d6804986eead709de48e5a57d [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{
Logan Chienaf29bad2018-03-12 16:35:58 +080049 neverallow().
50 in("vendor", "device").
51 with("vndk.enabled", "true").
52 without("vendor", "true").
Steven Moreland65b3fd92017-12-06 14:18:35 -080053 because("the VNDK can never contain a library that is device dependent."),
Logan Chienaf29bad2018-03-12 16:35:58 +080054 neverallow().
55 with("vndk.enabled", "true").
56 without("vendor", "true").
57 without("owner", "").
Steven Moreland65b3fd92017-12-06 14:18:35 -080058 because("a VNDK module can never have an owner."),
Neil Fuller1fee9f32018-10-04 11:45:49 +010059 neverallow().notIn("libcore", "development", "external/bouncycastle", "external/conscrypt", "external/okhttp").with("no_standard_libs", "true"),
Steven Moreland65b3fd92017-12-06 14:18:35 -080060
61 // TODO(b/67974785): always enforce the manifest
62 neverallow().
63 without("name", "libhidltransport").
64 with("product_variables.enforce_vintf_manifest.cflags", "*").
65 because("manifest enforcement should be independent of ."),
66
67 // TODO(b/67975799): vendor code should always use /vendor/bin/sh
68 neverallow().
69 without("name", "libc_bionic_ndk").
70 with("product_variables.treble_linker_namespaces.cflags", "*").
71 because("nothing should care if linker namespaces are enabled or not"),
72
73 // Example:
74 // *neverallow().with("Srcs", "main.cpp"),
75}
76
77func neverallowMutator(ctx BottomUpMutatorContext) {
78 m, ok := ctx.Module().(Module)
79 if !ok {
80 return
81 }
82
83 dir := ctx.ModuleDir() + "/"
84 properties := m.GetProperties()
85
86 for _, n := range neverallows {
87 if !n.appliesToPath(dir) {
88 continue
89 }
90
91 if !n.appliesToProperties(properties) {
92 continue
93 }
94
95 ctx.ModuleErrorf("violates " + n.String())
96 }
97}
98
99type ruleProperty struct {
100 fields []string // e.x.: Vndk.Enabled
101 value string // e.x.: true
102}
103
104type rule struct {
105 // User string for why this is a thing.
106 reason string
107
108 paths []string
109 unlessPaths []string
110
111 props []ruleProperty
112 unlessProps []ruleProperty
113}
114
115func neverallow() *rule {
116 return &rule{}
117}
118func (r *rule) in(path ...string) *rule {
119 r.paths = append(r.paths, cleanPaths(path)...)
120 return r
121}
122func (r *rule) notIn(path ...string) *rule {
123 r.unlessPaths = append(r.unlessPaths, cleanPaths(path)...)
124 return r
125}
126func (r *rule) with(properties, value string) *rule {
127 r.props = append(r.props, ruleProperty{
128 fields: fieldNamesForProperties(properties),
129 value: value,
130 })
131 return r
132}
133func (r *rule) without(properties, value string) *rule {
134 r.unlessProps = append(r.unlessProps, ruleProperty{
135 fields: fieldNamesForProperties(properties),
136 value: value,
137 })
138 return r
139}
140func (r *rule) because(reason string) *rule {
141 r.reason = reason
142 return r
143}
144
145func (r *rule) String() string {
146 s := "neverallow"
147 for _, v := range r.paths {
148 s += " dir:" + v + "*"
149 }
150 for _, v := range r.unlessPaths {
151 s += " -dir:" + v + "*"
152 }
153 for _, v := range r.props {
154 s += " " + strings.Join(v.fields, ".") + "=" + v.value
155 }
156 for _, v := range r.unlessProps {
157 s += " -" + strings.Join(v.fields, ".") + "=" + v.value
158 }
159 if len(r.reason) != 0 {
160 s += " which is restricted because " + r.reason
161 }
162 return s
163}
164
165func (r *rule) appliesToPath(dir string) bool {
166 includePath := len(r.paths) == 0 || hasAnyPrefix(dir, r.paths)
167 excludePath := hasAnyPrefix(dir, r.unlessPaths)
168 return includePath && !excludePath
169}
170
171func (r *rule) appliesToProperties(properties []interface{}) bool {
172 includeProps := hasAllProperties(properties, r.props)
173 excludeProps := hasAnyProperty(properties, r.unlessProps)
174 return includeProps && !excludeProps
175}
176
177// assorted utils
178
179func cleanPaths(paths []string) []string {
180 res := make([]string, len(paths))
181 for i, v := range paths {
182 res[i] = filepath.Clean(v) + "/"
183 }
184 return res
185}
186
187func fieldNamesForProperties(propertyNames string) []string {
188 names := strings.Split(propertyNames, ".")
189 for i, v := range names {
190 names[i] = proptools.FieldNameForProperty(v)
191 }
192 return names
193}
194
195func hasAnyPrefix(s string, prefixes []string) bool {
196 for _, prefix := range prefixes {
197 if strings.HasPrefix(s, prefix) {
198 return true
199 }
200 }
201 return false
202}
203
204func hasAnyProperty(properties []interface{}, props []ruleProperty) bool {
205 for _, v := range props {
206 if hasProperty(properties, v) {
207 return true
208 }
209 }
210 return false
211}
212
213func hasAllProperties(properties []interface{}, props []ruleProperty) bool {
214 for _, v := range props {
215 if !hasProperty(properties, v) {
216 return false
217 }
218 }
219 return true
220}
221
222func hasProperty(properties []interface{}, prop ruleProperty) bool {
223 for _, propertyStruct := range properties {
224 propertiesValue := reflect.ValueOf(propertyStruct).Elem()
225 for _, v := range prop.fields {
226 if !propertiesValue.IsValid() {
227 break
228 }
229 propertiesValue = propertiesValue.FieldByName(v)
230 }
231 if !propertiesValue.IsValid() {
232 continue
233 }
234
235 check := func(v string) bool {
236 return prop.value == "*" || prop.value == v
237 }
238
239 if matchValue(propertiesValue, check) {
240 return true
241 }
242 }
243 return false
244}
245
246func matchValue(value reflect.Value, check func(string) bool) bool {
247 if !value.IsValid() {
248 return false
249 }
250
251 if value.Kind() == reflect.Ptr {
252 if value.IsNil() {
253 return check("")
254 }
255 value = value.Elem()
256 }
257
258 switch value.Kind() {
259 case reflect.String:
260 return check(value.String())
261 case reflect.Bool:
262 return check(strconv.FormatBool(value.Bool()))
263 case reflect.Int:
264 return check(strconv.FormatInt(value.Int(), 10))
265 case reflect.Slice:
266 slice, ok := value.Interface().([]string)
267 if !ok {
268 panic("Can only handle slice of string")
269 }
270 for _, v := range slice {
271 if check(v) {
272 return true
273 }
274 }
275 return false
276 }
277
278 panic("Can't handle type: " + value.Kind().String())
279}