1// Copyright 2021 Google LLC 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 compliance 16 17import ( 18 "regexp" 19 "strings" 20) 21 22var ( 23 // RecognizedAnnotations identifies the set of annotations that have 24 // meaning for compliance policy. 25 RecognizedAnnotations = map[string]string{ 26 // used in readgraph.go to avoid creating 1000's of copies of the below 3 strings. 27 "static": "static", 28 "dynamic": "dynamic", 29 "toolchain": "toolchain", 30 } 31 32 // SafePathPrefixes maps the path prefixes presumed not to contain any 33 // proprietary or confidential pathnames to whether to strip the prefix 34 // from the path when used as the library name for notices. 35 SafePathPrefixes = map[string]bool{ 36 "external/": true, 37 "art/": false, 38 "build/": false, 39 "cts/": false, 40 "dalvik/": false, 41 "developers/": false, 42 "development/": false, 43 "frameworks/": false, 44 "packages/": true, 45 "prebuilts/": false, 46 "sdk/": false, 47 "system/": false, 48 "test/": false, 49 "toolchain/": false, 50 "tools/": false, 51 } 52 53 // SafePrebuiltPrefixes maps the regular expression to match a prebuilt 54 // containing the path of a safe prefix to the safe prefix. 55 SafePrebuiltPrefixes = make(map[*regexp.Regexp]string) 56 57 // ImpliesUnencumbered lists the condition names representing an author attempt to disclaim copyright. 58 ImpliesUnencumbered = LicenseConditionSet(UnencumberedCondition) 59 60 // ImpliesPermissive lists the condition names representing copyrighted but "licensed without policy requirements". 61 ImpliesPermissive = LicenseConditionSet(PermissiveCondition) 62 63 // ImpliesNotice lists the condition names implying a notice or attribution policy. 64 ImpliesNotice = LicenseConditionSet(UnencumberedCondition | PermissiveCondition | NoticeCondition | ReciprocalCondition | 65 RestrictedCondition | RestrictedClasspathExceptionCondition | WeaklyRestrictedCondition | 66 ProprietaryCondition | ByExceptionOnlyCondition) 67 68 // ImpliesReciprocal lists the condition names implying a local source-sharing policy. 69 ImpliesReciprocal = LicenseConditionSet(ReciprocalCondition) 70 71 // Restricted lists the condition names implying an infectious source-sharing policy. 72 ImpliesRestricted = LicenseConditionSet(RestrictedCondition | RestrictedClasspathExceptionCondition | WeaklyRestrictedCondition) 73 74 // ImpliesProprietary lists the condition names implying a confidentiality policy. 75 ImpliesProprietary = LicenseConditionSet(ProprietaryCondition) 76 77 // ImpliesByExceptionOnly lists the condition names implying a policy for "license review and approval before use". 78 ImpliesByExceptionOnly = LicenseConditionSet(ProprietaryCondition | ByExceptionOnlyCondition) 79 80 // ImpliesPrivate lists the condition names implying a source-code privacy policy. 81 ImpliesPrivate = LicenseConditionSet(ProprietaryCondition) 82 83 // ImpliesShared lists the condition names implying a source-code sharing policy. 84 ImpliesShared = LicenseConditionSet(ReciprocalCondition | RestrictedCondition | RestrictedClasspathExceptionCondition | WeaklyRestrictedCondition) 85) 86 87var ( 88 anyLgpl = regexp.MustCompile(`^SPDX-license-identifier-LGPL.*`) 89 versionedGpl = regexp.MustCompile(`^SPDX-license-identifier-GPL-\p{N}.*`) 90 genericGpl = regexp.MustCompile(`^SPDX-license-identifier-GPL$`) 91 ccBySa = regexp.MustCompile(`^SPDX-license-identifier-CC-BY.*-SA.*`) 92) 93 94func init() { 95 for prefix := range SafePathPrefixes { 96 if prefix == "prebuilts/" { 97 continue 98 } 99 r := regexp.MustCompile("^prebuilts/[^ ]*/" + prefix) 100 SafePrebuiltPrefixes[r] = prefix 101 } 102} 103 104// LicenseConditionSetFromNames returns a set containing the recognized `names` and 105// silently ignoring or discarding the unrecognized `names`. 106func LicenseConditionSetFromNames(tn *TargetNode, names ...string) LicenseConditionSet { 107 cs := NewLicenseConditionSet() 108 for _, name := range names { 109 if name == "restricted" { 110 if 0 == len(tn.LicenseKinds()) { 111 cs = cs.Plus(RestrictedCondition) 112 continue 113 } 114 hasLgpl := false 115 hasClasspath := false 116 hasGeneric := false 117 for _, kind := range tn.LicenseKinds() { 118 if strings.HasSuffix(kind, "-with-classpath-exception") { 119 cs = cs.Plus(RestrictedClasspathExceptionCondition) 120 hasClasspath = true 121 } else if anyLgpl.MatchString(kind) { 122 cs = cs.Plus(WeaklyRestrictedCondition) 123 hasLgpl = true 124 } else if versionedGpl.MatchString(kind) { 125 cs = cs.Plus(RestrictedCondition) 126 } else if genericGpl.MatchString(kind) { 127 hasGeneric = true 128 } else if kind == "legacy_restricted" || ccBySa.MatchString(kind) { 129 cs = cs.Plus(RestrictedCondition) 130 } else { 131 cs = cs.Plus(RestrictedCondition) 132 } 133 } 134 if hasGeneric && !hasLgpl && !hasClasspath { 135 cs = cs.Plus(RestrictedCondition) 136 } 137 continue 138 } 139 if lc, ok := RecognizedConditionNames[name]; ok { 140 cs |= LicenseConditionSet(lc) 141 } 142 } 143 return cs 144} 145 146// Resolution happens in three phases: 147// 148// 1. A bottom-up traversal propagates (restricted) license conditions up to 149// targets from dendencies as needed. 150// 151// 2. For each condition of interest, a top-down traversal propagates 152// (restricted) conditions down from targets into linked dependencies. 153// 154// 3. Finally, a walk of the shipped target nodes attaches resolutions to the 155// ancestor nodes from the root down to and including the first non-container. 156// 157// e.g. If a disk image contains a binary bin1 that links a library liba, the 158// notice requirement for liba gets attached to the disk image and to bin1. 159// Because liba doesn't actually get shipped as a separate artifact, but only 160// as bits in bin1, it has no actions 'attached' to it. The actions attached 161// to the image and to bin1 'act on' liba by providing notice. 162// 163// The behavior of the 3 phases gets controlled by the 3 functions below. 164// 165// The first function controls what happens during the bottom-up propagation. 166// Restricted conditions propagate up all non-toolchain dependencies; except, 167// some do not propagate up dynamic links, which may depend on whether the 168// modules are independent. 169// 170// The second function controls what happens during the top-down propagation. 171// Restricted conditions propagate down as above with the added caveat that 172// inherited restricted conditions do not propagate from pure aggregates to 173// their dependencies. 174// 175// The final function controls which conditions apply/get attached to ancestors 176// depending on the types of dependencies involved. All conditions apply across 177// normal derivation dependencies. No conditions apply across toolchain 178// dependencies. Some restricted conditions apply across dynamic link 179// dependencies. 180// 181// Not all restricted licenses are create equal. Some have special rules or 182// exceptions. e.g. LGPL or "with classpath excption". 183 184// depConditionsPropagatingToTarget returns the conditions which propagate up an 185// edge from dependency to target. 186// 187// This function sets the policy for the bottom-up propagation and how conditions 188// flow up the graph from dependencies to targets. 189// 190// If a pure aggregation is built into a derivative work that is not a pure 191// aggregation, per policy it ceases to be a pure aggregation in the context of 192// that derivative work. The `treatAsAggregate` parameter will be false for 193// non-aggregates and for aggregates in non-aggregate contexts. 194func depConditionsPropagatingToTarget(lg *LicenseGraph, e *TargetEdge, depConditions LicenseConditionSet, treatAsAggregate bool) LicenseConditionSet { 195 result := LicenseConditionSet(0x0000) 196 if edgeIsDerivation(e) { 197 result |= depConditions & ImpliesRestricted 198 return result 199 } 200 if !edgeIsDynamicLink(e) { 201 return result 202 } 203 204 result |= depConditions & LicenseConditionSet(RestrictedCondition) 205 if 0 != (depConditions&LicenseConditionSet(RestrictedClasspathExceptionCondition)) && !edgeNodesAreIndependentModules(e) { 206 result |= LicenseConditionSet(RestrictedClasspathExceptionCondition) 207 } 208 return result 209} 210 211// targetConditionsPropagatingToDep returns the conditions which propagate down 212// an edge from target to dependency. 213// 214// This function sets the policy for the top-down traversal and how conditions 215// flow down the graph from targets to dependencies. 216// 217// If a pure aggregation is built into a derivative work that is not a pure 218// aggregation, per policy it ceases to be a pure aggregation in the context of 219// that derivative work. The `treatAsAggregate` parameter will be false for 220// non-aggregates and for aggregates in non-aggregate contexts. 221func targetConditionsPropagatingToDep(lg *LicenseGraph, e *TargetEdge, targetConditions LicenseConditionSet, treatAsAggregate bool, conditionsFn TraceConditions) LicenseConditionSet { 222 result := targetConditions 223 224 // reverse direction -- none of these apply to things depended-on, only to targets depending-on. 225 result = result.Minus(UnencumberedCondition, PermissiveCondition, NoticeCondition, ReciprocalCondition, ProprietaryCondition, ByExceptionOnlyCondition) 226 227 if !edgeIsDerivation(e) && !edgeIsDynamicLink(e) { 228 // target is not a derivative work of dependency and is not linked to dependency 229 result = result.Difference(ImpliesRestricted) 230 return result 231 } 232 if treatAsAggregate { 233 // If the author of a pure aggregate licenses it restricted, apply restricted to immediate dependencies. 234 // Otherwise, restricted does not propagate back down to dependencies. 235 if !conditionsFn(e.target).MatchesAnySet(ImpliesRestricted) { 236 result = result.Difference(ImpliesRestricted) 237 } 238 return result 239 } 240 if edgeIsDerivation(e) { 241 return result 242 } 243 result = result.Minus(WeaklyRestrictedCondition) 244 if edgeNodesAreIndependentModules(e) { 245 result = result.Minus(RestrictedClasspathExceptionCondition) 246 } 247 return result 248} 249 250// conditionsAttachingAcrossEdge returns the subset of conditions in `universe` 251// that apply across edge `e`. 252// 253// This function sets the policy for attaching actions to ancestor nodes in the 254// final resolution walk. 255func conditionsAttachingAcrossEdge(lg *LicenseGraph, e *TargetEdge, universe LicenseConditionSet) LicenseConditionSet { 256 result := universe 257 if edgeIsDerivation(e) { 258 return result 259 } 260 if !edgeIsDynamicLink(e) { 261 return NewLicenseConditionSet() 262 } 263 264 result &= LicenseConditionSet(RestrictedCondition | RestrictedClasspathExceptionCondition) 265 if 0 != (result&LicenseConditionSet(RestrictedClasspathExceptionCondition)) && edgeNodesAreIndependentModules(e) { 266 result &= LicenseConditionSet(RestrictedCondition) 267 } 268 return result 269} 270 271// edgeIsDynamicLink returns true for edges representing shared libraries 272// linked dynamically at runtime. 273func edgeIsDynamicLink(e *TargetEdge) bool { 274 return e.annotations.HasAnnotation("dynamic") 275} 276 277// edgeIsDerivation returns true for edges where the target is a derivative 278// work of dependency. 279func edgeIsDerivation(e *TargetEdge) bool { 280 isDynamic := e.annotations.HasAnnotation("dynamic") 281 isToolchain := e.annotations.HasAnnotation("toolchain") 282 return !isDynamic && !isToolchain 283} 284 285// edgeNodesAreIndependentModules returns true for edges where the target and 286// dependency are independent modules. 287func edgeNodesAreIndependentModules(e *TargetEdge) bool { 288 return e.target.PackageName() != e.dependency.PackageName() 289} 290