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 = []safePathPrefixesType{
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/module_sdk/", true},
46		{"prebuilts/", false},
47		{"sdk/", false},
48		{"system/", false},
49		{"test/", false},
50		{"toolchain/", false},
51		{"tools/", false},
52	}
53
54	// safePrebuiltPrefixes maps the regular expression to match a prebuilt
55	// containing the path of a safe prefix to the safe prefix.
56	safePrebuiltPrefixes []safePrebuiltPrefixesType
57
58	// ImpliesUnencumbered lists the condition names representing an author attempt to disclaim copyright.
59	ImpliesUnencumbered = LicenseConditionSet(UnencumberedCondition)
60
61	// ImpliesPermissive lists the condition names representing copyrighted but "licensed without policy requirements".
62	ImpliesPermissive = LicenseConditionSet(PermissiveCondition)
63
64	// ImpliesNotice lists the condition names implying a notice or attribution policy.
65	ImpliesNotice = LicenseConditionSet(UnencumberedCondition | PermissiveCondition | NoticeCondition | ReciprocalCondition |
66		RestrictedCondition | WeaklyRestrictedCondition | 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 | 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 | WeaklyRestrictedCondition)
85)
86
87type safePathPrefixesType struct {
88	prefix string
89	strip  bool
90}
91
92type safePrebuiltPrefixesType struct {
93	safePathPrefixesType
94	re *regexp.Regexp
95}
96
97var (
98	anyLgpl      = regexp.MustCompile(`^SPDX-license-identifier-LGPL.*`)
99	versionedGpl = regexp.MustCompile(`^SPDX-license-identifier-GPL-\p{N}.*`)
100	genericGpl   = regexp.MustCompile(`^SPDX-license-identifier-GPL$`)
101	ccBySa       = regexp.MustCompile(`^SPDX-license-identifier-CC-BY.*-SA.*`)
102)
103
104func init() {
105	for _, safePathPrefix := range safePathPrefixes {
106		if strings.HasPrefix(safePathPrefix.prefix, "prebuilts/") {
107			continue
108		}
109		r := regexp.MustCompile("^prebuilts/(?:runtime/mainline/)?" + safePathPrefix.prefix)
110		safePrebuiltPrefixes = append(safePrebuiltPrefixes,
111			safePrebuiltPrefixesType{safePathPrefix, r})
112	}
113}
114
115// LicenseConditionSetFromNames returns a set containing the recognized `names` and
116// silently ignoring or discarding the unrecognized `names`.
117func LicenseConditionSetFromNames(names ...string) LicenseConditionSet {
118	cs := NewLicenseConditionSet()
119	for _, name := range names {
120		if lc, ok := RecognizedConditionNames[name]; ok {
121			cs |= LicenseConditionSet(lc)
122		}
123	}
124	return cs
125}
126
127// Resolution happens in three phases:
128//
129// 1. A bottom-up traversal propagates (restricted) license conditions up to
130// targets from dendencies as needed.
131//
132// 2. For each condition of interest, a top-down traversal propagates
133// (restricted) conditions down from targets into linked dependencies.
134//
135// 3. Finally, a walk of the shipped target nodes attaches resolutions to the
136// ancestor nodes from the root down to and including the first non-container.
137//
138// e.g. If a disk image contains a binary bin1 that links a library liba, the
139// notice requirement for liba gets attached to the disk image and to bin1.
140// Because liba doesn't actually get shipped as a separate artifact, but only
141// as bits in bin1, it has no actions 'attached' to it. The actions attached
142// to the image and to bin1 'act on' liba by providing notice.
143//
144// The behavior of the 3 phases gets controlled by the 3 functions below.
145//
146// The first function controls what happens during the bottom-up propagation.
147// Restricted conditions propagate up all non-toolchain dependencies; except,
148// some do not propagate up dynamic links, which may depend on whether the
149// modules are independent.
150//
151// The second function controls what happens during the top-down propagation.
152// Restricted conditions propagate down as above with the added caveat that
153// inherited restricted conditions do not propagate from pure aggregates to
154// their dependencies.
155//
156// The final function controls which conditions apply/get attached to ancestors
157// depending on the types of dependencies involved. All conditions apply across
158// normal derivation dependencies. No conditions apply across toolchain
159// dependencies. Some restricted conditions apply across dynamic link
160// dependencies.
161//
162// Not all restricted licenses are create equal. Some have special rules or
163// exceptions. e.g. LGPL or "with classpath excption".
164
165// depConditionsPropagatingToTarget returns the conditions which propagate up an
166// edge from dependency to target.
167//
168// This function sets the policy for the bottom-up propagation and how conditions
169// flow up the graph from dependencies to targets.
170//
171// If a pure aggregation is built into a derivative work that is not a pure
172// aggregation, per policy it ceases to be a pure aggregation in the context of
173// that derivative work. The `treatAsAggregate` parameter will be false for
174// non-aggregates and for aggregates in non-aggregate contexts.
175func depConditionsPropagatingToTarget(lg *LicenseGraph, e *TargetEdge, depConditions LicenseConditionSet, treatAsAggregate bool) LicenseConditionSet {
176	result := LicenseConditionSet(0x0000)
177	if edgeIsDerivation(e) {
178		result |= depConditions & ImpliesRestricted
179		return result
180	}
181	if !edgeIsDynamicLink(e) {
182		return result
183	}
184
185	result |= depConditions & LicenseConditionSet(RestrictedCondition)
186	return result
187}
188
189// targetConditionsPropagatingToDep returns the conditions which propagate down
190// an edge from target to dependency.
191//
192// This function sets the policy for the top-down traversal and how conditions
193// flow down the graph from targets to dependencies.
194//
195// If a pure aggregation is built into a derivative work that is not a pure
196// aggregation, per policy it ceases to be a pure aggregation in the context of
197// that derivative work. The `treatAsAggregate` parameter will be false for
198// non-aggregates and for aggregates in non-aggregate contexts.
199func targetConditionsPropagatingToDep(lg *LicenseGraph, e *TargetEdge, targetConditions LicenseConditionSet, treatAsAggregate bool, conditionsFn TraceConditions) LicenseConditionSet {
200	result := targetConditions
201
202	// reverse direction -- none of these apply to things depended-on, only to targets depending-on.
203	result = result.Minus(UnencumberedCondition, PermissiveCondition, NoticeCondition, ReciprocalCondition, ProprietaryCondition, ByExceptionOnlyCondition)
204
205	if !edgeIsDerivation(e) && !edgeIsDynamicLink(e) {
206		// target is not a derivative work of dependency and is not linked to dependency
207		result = result.Difference(ImpliesRestricted)
208		return result
209	}
210	if treatAsAggregate {
211		// If the author of a pure aggregate licenses it restricted, apply restricted to immediate dependencies.
212		// Otherwise, restricted does not propagate back down to dependencies.
213		if !conditionsFn(e.target).MatchesAnySet(ImpliesRestricted) {
214			result = result.Difference(ImpliesRestricted)
215		}
216		return result
217	}
218	if edgeIsDerivation(e) {
219		return result
220	}
221	result = result.Minus(WeaklyRestrictedCondition)
222	return result
223}
224
225// conditionsAttachingAcrossEdge returns the subset of conditions in `universe`
226// that apply across edge `e`.
227//
228// This function sets the policy for attaching actions to ancestor nodes in the
229// final resolution walk.
230func conditionsAttachingAcrossEdge(lg *LicenseGraph, e *TargetEdge, universe LicenseConditionSet) LicenseConditionSet {
231	result := universe
232	if edgeIsDerivation(e) {
233		return result
234	}
235	if !edgeIsDynamicLink(e) {
236		return NewLicenseConditionSet()
237	}
238
239	result &= LicenseConditionSet(RestrictedCondition)
240	return result
241}
242
243// edgeIsDynamicLink returns true for edges representing shared libraries
244// linked dynamically at runtime.
245func edgeIsDynamicLink(e *TargetEdge) bool {
246	return e.annotations.HasAnnotation("dynamic")
247}
248
249// edgeIsDerivation returns true for edges where the target is a derivative
250// work of dependency.
251func edgeIsDerivation(e *TargetEdge) bool {
252	isDynamic := e.annotations.HasAnnotation("dynamic")
253	isToolchain := e.annotations.HasAnnotation("toolchain")
254	return !isDynamic && !isToolchain
255}
256