|  | // Copyright 2017 Google Inc. All rights reserved. | 
|  | // | 
|  | // 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 | 
|  |  | 
|  | import ( | 
|  | "path/filepath" | 
|  | "reflect" | 
|  | "strconv" | 
|  | "strings" | 
|  |  | 
|  | "github.com/google/blueprint/proptools" | 
|  | ) | 
|  |  | 
|  | // "neverallow" rules for the build system. | 
|  | // | 
|  | // This allows things which aren't related to the build system and are enforced | 
|  | // for sanity, in progress code refactors, or policy to be expressed in a | 
|  | // straightforward away disjoint from implementations and tests which should | 
|  | // work regardless of these restrictions. | 
|  | // | 
|  | // A module is disallowed if all of the following are true: | 
|  | // - it is in one of the "in" paths | 
|  | // - it is not in one of the "notIn" paths | 
|  | // - it has all "with" properties matched | 
|  | // - - values are matched in their entirety | 
|  | // - - nil is interpreted as an empty string | 
|  | // - - nested properties are separated with a '.' | 
|  | // - - if the property is a list, any of the values in the list being matches | 
|  | //     counts as a match | 
|  | // - it has none of the "without" properties matched (same rules as above) | 
|  |  | 
|  | func registerNeverallowMutator(ctx RegisterMutatorsContext) { | 
|  | ctx.BottomUp("neverallow", neverallowMutator).Parallel() | 
|  | } | 
|  |  | 
|  | var neverallows = []*rule{ | 
|  | neverallow(). | 
|  | in("vendor", "device"). | 
|  | with("vndk.enabled", "true"). | 
|  | without("vendor", "true"). | 
|  | because("the VNDK can never contain a library that is device dependent."), | 
|  | neverallow(). | 
|  | with("vndk.enabled", "true"). | 
|  | without("vendor", "true"). | 
|  | without("owner", ""). | 
|  | because("a VNDK module can never have an owner."), | 
|  | neverallow().notIn("libcore", "development").with("no_standard_libs", "true"), | 
|  |  | 
|  | // TODO(b/67974785): always enforce the manifest | 
|  | neverallow(). | 
|  | without("name", "libhidltransport"). | 
|  | with("product_variables.enforce_vintf_manifest.cflags", "*"). | 
|  | because("manifest enforcement should be independent of ."), | 
|  |  | 
|  | // TODO(b/67975799): vendor code should always use /vendor/bin/sh | 
|  | neverallow(). | 
|  | without("name", "libc_bionic_ndk"). | 
|  | with("product_variables.treble_linker_namespaces.cflags", "*"). | 
|  | because("nothing should care if linker namespaces are enabled or not"), | 
|  |  | 
|  | // Example: | 
|  | // *neverallow().with("Srcs", "main.cpp"), | 
|  | } | 
|  |  | 
|  | func neverallowMutator(ctx BottomUpMutatorContext) { | 
|  | m, ok := ctx.Module().(Module) | 
|  | if !ok { | 
|  | return | 
|  | } | 
|  |  | 
|  | dir := ctx.ModuleDir() + "/" | 
|  | properties := m.GetProperties() | 
|  |  | 
|  | for _, n := range neverallows { | 
|  | if !n.appliesToPath(dir) { | 
|  | continue | 
|  | } | 
|  |  | 
|  | if !n.appliesToProperties(properties) { | 
|  | continue | 
|  | } | 
|  |  | 
|  | ctx.ModuleErrorf("violates " + n.String()) | 
|  | } | 
|  | } | 
|  |  | 
|  | type ruleProperty struct { | 
|  | fields []string // e.x.: Vndk.Enabled | 
|  | value  string   // e.x.: true | 
|  | } | 
|  |  | 
|  | type rule struct { | 
|  | // User string for why this is a thing. | 
|  | reason string | 
|  |  | 
|  | paths       []string | 
|  | unlessPaths []string | 
|  |  | 
|  | props       []ruleProperty | 
|  | unlessProps []ruleProperty | 
|  | } | 
|  |  | 
|  | func neverallow() *rule { | 
|  | return &rule{} | 
|  | } | 
|  | func (r *rule) in(path ...string) *rule { | 
|  | r.paths = append(r.paths, cleanPaths(path)...) | 
|  | return r | 
|  | } | 
|  | func (r *rule) notIn(path ...string) *rule { | 
|  | r.unlessPaths = append(r.unlessPaths, cleanPaths(path)...) | 
|  | return r | 
|  | } | 
|  | func (r *rule) with(properties, value string) *rule { | 
|  | r.props = append(r.props, ruleProperty{ | 
|  | fields: fieldNamesForProperties(properties), | 
|  | value:  value, | 
|  | }) | 
|  | return r | 
|  | } | 
|  | func (r *rule) without(properties, value string) *rule { | 
|  | r.unlessProps = append(r.unlessProps, ruleProperty{ | 
|  | fields: fieldNamesForProperties(properties), | 
|  | value:  value, | 
|  | }) | 
|  | return r | 
|  | } | 
|  | func (r *rule) because(reason string) *rule { | 
|  | r.reason = reason | 
|  | return r | 
|  | } | 
|  |  | 
|  | func (r *rule) String() string { | 
|  | s := "neverallow" | 
|  | for _, v := range r.paths { | 
|  | s += " dir:" + v + "*" | 
|  | } | 
|  | for _, v := range r.unlessPaths { | 
|  | s += " -dir:" + v + "*" | 
|  | } | 
|  | for _, v := range r.props { | 
|  | s += " " + strings.Join(v.fields, ".") + "=" + v.value | 
|  | } | 
|  | for _, v := range r.unlessProps { | 
|  | s += " -" + strings.Join(v.fields, ".") + "=" + v.value | 
|  | } | 
|  | if len(r.reason) != 0 { | 
|  | s += " which is restricted because " + r.reason | 
|  | } | 
|  | return s | 
|  | } | 
|  |  | 
|  | func (r *rule) appliesToPath(dir string) bool { | 
|  | includePath := len(r.paths) == 0 || hasAnyPrefix(dir, r.paths) | 
|  | excludePath := hasAnyPrefix(dir, r.unlessPaths) | 
|  | return includePath && !excludePath | 
|  | } | 
|  |  | 
|  | func (r *rule) appliesToProperties(properties []interface{}) bool { | 
|  | includeProps := hasAllProperties(properties, r.props) | 
|  | excludeProps := hasAnyProperty(properties, r.unlessProps) | 
|  | return includeProps && !excludeProps | 
|  | } | 
|  |  | 
|  | // assorted utils | 
|  |  | 
|  | func cleanPaths(paths []string) []string { | 
|  | res := make([]string, len(paths)) | 
|  | for i, v := range paths { | 
|  | res[i] = filepath.Clean(v) + "/" | 
|  | } | 
|  | return res | 
|  | } | 
|  |  | 
|  | func fieldNamesForProperties(propertyNames string) []string { | 
|  | names := strings.Split(propertyNames, ".") | 
|  | for i, v := range names { | 
|  | names[i] = proptools.FieldNameForProperty(v) | 
|  | } | 
|  | return names | 
|  | } | 
|  |  | 
|  | func hasAnyPrefix(s string, prefixes []string) bool { | 
|  | for _, prefix := range prefixes { | 
|  | if strings.HasPrefix(s, prefix) { | 
|  | return true | 
|  | } | 
|  | } | 
|  | return false | 
|  | } | 
|  |  | 
|  | func hasAnyProperty(properties []interface{}, props []ruleProperty) bool { | 
|  | for _, v := range props { | 
|  | if hasProperty(properties, v) { | 
|  | return true | 
|  | } | 
|  | } | 
|  | return false | 
|  | } | 
|  |  | 
|  | func hasAllProperties(properties []interface{}, props []ruleProperty) bool { | 
|  | for _, v := range props { | 
|  | if !hasProperty(properties, v) { | 
|  | return false | 
|  | } | 
|  | } | 
|  | return true | 
|  | } | 
|  |  | 
|  | func hasProperty(properties []interface{}, prop ruleProperty) bool { | 
|  | for _, propertyStruct := range properties { | 
|  | propertiesValue := reflect.ValueOf(propertyStruct).Elem() | 
|  | for _, v := range prop.fields { | 
|  | if !propertiesValue.IsValid() { | 
|  | break | 
|  | } | 
|  | propertiesValue = propertiesValue.FieldByName(v) | 
|  | } | 
|  | if !propertiesValue.IsValid() { | 
|  | continue | 
|  | } | 
|  |  | 
|  | check := func(v string) bool { | 
|  | return prop.value == "*" || prop.value == v | 
|  | } | 
|  |  | 
|  | if matchValue(propertiesValue, check) { | 
|  | return true | 
|  | } | 
|  | } | 
|  | return false | 
|  | } | 
|  |  | 
|  | func matchValue(value reflect.Value, check func(string) bool) bool { | 
|  | if !value.IsValid() { | 
|  | return false | 
|  | } | 
|  |  | 
|  | if value.Kind() == reflect.Ptr { | 
|  | if value.IsNil() { | 
|  | return check("") | 
|  | } | 
|  | value = value.Elem() | 
|  | } | 
|  |  | 
|  | switch value.Kind() { | 
|  | case reflect.String: | 
|  | return check(value.String()) | 
|  | case reflect.Bool: | 
|  | return check(strconv.FormatBool(value.Bool())) | 
|  | case reflect.Int: | 
|  | return check(strconv.FormatInt(value.Int(), 10)) | 
|  | case reflect.Slice: | 
|  | slice, ok := value.Interface().([]string) | 
|  | if !ok { | 
|  | panic("Can only handle slice of string") | 
|  | } | 
|  | for _, v := range slice { | 
|  | if check(v) { | 
|  | return true | 
|  | } | 
|  | } | 
|  | return false | 
|  | } | 
|  |  | 
|  | panic("Can't handle type: " + value.Kind().String()) | 
|  | } |