• 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	"sync"
19)
20
21var (
22	// AllResolutions is a TraceConditions function that resolves all
23	// unfiltered license conditions.
24	AllResolutions = TraceConditions(func(tn *TargetNode) LicenseConditionSet { return tn.licenseConditions })
25)
26
27// TraceConditions is a function that returns the conditions to trace for each
28// target node `tn`.
29type TraceConditions func(tn *TargetNode) LicenseConditionSet
30
31// ResolveBottomUpConditions performs a bottom-up walk of the LicenseGraph
32// propagating conditions up the graph as necessary according to the properties
33// of each edge and according to each license condition in question.
34//
35// e.g. if a "restricted" condition applies to a binary, it also applies to all
36// of the statically-linked libraries and the transitive closure of their static
37// dependencies; even if neither they nor the transitive closure of their
38// dependencies originate any "restricted" conditions. The bottom-up walk will
39// not resolve the library and its transitive closure, but the later top-down
40// walk will.
41func ResolveBottomUpConditions(lg *LicenseGraph) {
42	TraceBottomUpConditions(lg, AllResolutions)
43}
44
45// TraceBottomUpConditions performs a bottom-up walk of the LicenseGraph
46// propagating trace conditions from `conditionsFn` up the graph as necessary
47// according to the properties of each edge and according to each license
48// condition in question.
49func TraceBottomUpConditions(lg *LicenseGraph, conditionsFn TraceConditions) {
50
51	// short-cut if already walked and cached
52	lg.mu.Lock()
53	wg := lg.wgBU
54
55	if wg != nil {
56		lg.mu.Unlock()
57		wg.Wait()
58		return
59	}
60	wg = &sync.WaitGroup{}
61	wg.Add(1)
62	lg.wgBU = wg
63	lg.mu.Unlock()
64
65	// amap identifes targets previously walked. (guarded by mu)
66	amap := make(map[*TargetNode]struct{})
67
68	// cmap identifies targets previously walked as pure aggregates. i.e. as containers
69	// (guarded by mu)
70	cmap := make(map[*TargetNode]struct{})
71	var mu sync.Mutex
72
73	var walk func(target *TargetNode, treatAsAggregate bool) LicenseConditionSet
74
75	walk = func(target *TargetNode, treatAsAggregate bool) LicenseConditionSet {
76		priorWalkResults := func() (LicenseConditionSet, bool) {
77			mu.Lock()
78			defer mu.Unlock()
79
80			if _, alreadyWalked := amap[target]; alreadyWalked {
81				if treatAsAggregate {
82					return target.resolution, true
83				}
84				if _, asAggregate := cmap[target]; !asAggregate {
85					return target.resolution, true
86				}
87				// previously walked in a pure aggregate context,
88				// needs to walk again in non-aggregate context
89				delete(cmap, target)
90			} else {
91				target.resolution |= conditionsFn(target)
92				amap[target] = struct{}{}
93			}
94			if treatAsAggregate {
95				cmap[target] = struct{}{}
96			}
97			return target.resolution, false
98		}
99		cs, alreadyWalked := priorWalkResults()
100		if alreadyWalked {
101			return cs
102		}
103
104		c := make(chan LicenseConditionSet, len(target.edges))
105		// add all the conditions from all the dependencies
106		for _, edge := range target.edges {
107			go func(edge *TargetEdge) {
108				// walk dependency to get its conditions
109				cs := walk(edge.dependency, treatAsAggregate && edge.dependency.IsContainer())
110
111				// turn those into the conditions that apply to the target
112				cs = depConditionsPropagatingToTarget(lg, edge, cs, treatAsAggregate)
113
114				c <- cs
115			}(edge)
116		}
117		for i := 0; i < len(target.edges); i++ {
118			cs |= <-c
119		}
120		mu.Lock()
121		target.resolution |= cs
122		mu.Unlock()
123
124		// return conditions up the tree
125		return cs
126	}
127
128	// walk each of the roots
129	for _, rname := range lg.rootFiles {
130		rnode := lg.targets[rname]
131		_ = walk(rnode, rnode.IsContainer())
132	}
133
134	wg.Done()
135}
136
137// ResolveTopDownCondtions performs a top-down walk of the LicenseGraph
138// propagating conditions from target to dependency.
139//
140// e.g. For current policy, none of the conditions propagate from target to
141// dependency except restricted. For restricted, the policy is to share the
142// source of any libraries linked to restricted code and to provide notice.
143func ResolveTopDownConditions(lg *LicenseGraph) {
144	TraceTopDownConditions(lg, AllResolutions)
145}
146
147// TraceTopDownCondtions performs a top-down walk of the LicenseGraph
148// propagating trace conditions returned by `conditionsFn` from target to
149// dependency.
150func TraceTopDownConditions(lg *LicenseGraph, conditionsFn TraceConditions) {
151
152	// short-cut if already walked and cached
153	lg.mu.Lock()
154	wg := lg.wgTD
155
156	if wg != nil {
157		lg.mu.Unlock()
158		wg.Wait()
159		return
160	}
161	wg = &sync.WaitGroup{}
162	wg.Add(1)
163	lg.wgTD = wg
164	lg.mu.Unlock()
165
166	// start with the conditions propagated up the graph
167	TraceBottomUpConditions(lg, conditionsFn)
168
169	// amap contains the set of targets already walked. (guarded by mu)
170	amap := make(map[*TargetNode]struct{})
171
172	// cmap contains the set of targets walked as pure aggregates. i.e. containers
173	// (guarded by mu)
174	cmap := make(map[*TargetNode]struct{})
175
176	// mu guards concurrent access to cmap
177	var mu sync.Mutex
178
179	var walk func(fnode *TargetNode, cs LicenseConditionSet, treatAsAggregate bool)
180
181	walk = func(fnode *TargetNode, cs LicenseConditionSet, treatAsAggregate bool) {
182		defer wg.Done()
183		mu.Lock()
184		fnode.resolution |= conditionsFn(fnode)
185		fnode.resolution |= cs
186		amap[fnode] = struct{}{}
187		if treatAsAggregate {
188			cmap[fnode] = struct{}{}
189		}
190		cs = fnode.resolution
191		mu.Unlock()
192		// for each dependency
193		for _, edge := range fnode.edges {
194			func(edge *TargetEdge) {
195				// dcs holds the dpendency conditions inherited from the target
196				dcs := targetConditionsPropagatingToDep(lg, edge, cs, treatAsAggregate, conditionsFn)
197				dnode := edge.dependency
198				mu.Lock()
199				defer mu.Unlock()
200				depcs := dnode.resolution
201				_, alreadyWalked := amap[dnode]
202				if !dcs.IsEmpty() && alreadyWalked {
203					if dcs.Difference(depcs).IsEmpty() {
204						// no new conditions
205
206						// pure aggregates never need walking a 2nd time with same conditions
207						if treatAsAggregate {
208							return
209						}
210						// non-aggregates don't need walking as non-aggregate a 2nd time
211						if _, asAggregate := cmap[dnode]; !asAggregate {
212							return
213						}
214						// previously walked as pure aggregate; need to re-walk as non-aggregate
215						delete(cmap, dnode)
216					}
217				}
218				// add the conditions to the dependency
219				wg.Add(1)
220				go walk(dnode, dcs, treatAsAggregate && dnode.IsContainer())
221			}(edge)
222		}
223	}
224
225	// walk each of the roots
226	for _, rname := range lg.rootFiles {
227		rnode := lg.targets[rname]
228		wg.Add(1)
229		// add the conditions to the root and its transitive closure
230		go walk(rnode, NewLicenseConditionSet(), rnode.IsContainer())
231	}
232	wg.Done()
233	wg.Wait()
234}
235