• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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