blob: 58547f84d5eae708c9f77d115b5e02c5ab7d305c [file] [log] [blame]
Bob Badour9ee7d032021-10-25 16:51:48 -07001// 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
17// ResolveBottomUpConditions performs a bottom-up walk of the LicenseGraph
18// propagating conditions up the graph as necessary according to the properties
19// of each edge and according to each license condition in question.
20//
21// Subsequent top-down walks of the graph will filter some resolutions and may
22// introduce new resolutions.
23//
24// e.g. if a "restricted" condition applies to a binary, it also applies to all
25// of the statically-linked libraries and the transitive closure of their static
26// dependencies; even if neither they nor the transitive closure of their
27// dependencies originate any "restricted" conditions. The bottom-up walk will
28// not resolve the library and its transitive closure, but the later top-down
29// walk will.
30func ResolveBottomUpConditions(lg *LicenseGraph) *ResolutionSet {
31
32 // short-cut if already walked and cached
33 lg.mu.Lock()
34 rs := lg.rsBU
35 lg.mu.Unlock()
36
37 if rs != nil {
38 return rs
39 }
40
41 // must be indexed for fast lookup
42 lg.indexForward()
43
Bob Badourb2855152021-12-08 12:52:59 -080044 rs = resolveBottomUp(lg, make(map[*TargetNode]actionSet) /* empty map; no prior resolves */)
Bob Badour9ee7d032021-10-25 16:51:48 -070045
46 // if not yet cached, save the result
47 lg.mu.Lock()
48 if lg.rsBU == nil {
49 lg.rsBU = rs
50 } else {
51 // if we end up with 2, release the later for garbage collection
52 rs = lg.rsBU
53 }
54 lg.mu.Unlock()
55
56 return rs
57}
58
59// ResolveTopDownCondtions performs a top-down walk of the LicenseGraph
60// resolving all reachable nodes for `condition`. Policy establishes the rules
61// for transforming and propagating resolutions down the graph.
62//
63// e.g. For current policy, none of the conditions propagate from target to
64// dependency except restricted. For restricted, the policy is to share the
65// source of any libraries linked to restricted code and to provide notice.
66func ResolveTopDownConditions(lg *LicenseGraph) *ResolutionSet {
67
68 // short-cut if already walked and cached
69 lg.mu.Lock()
70 rs := lg.rsTD
71 lg.mu.Unlock()
72
73 if rs != nil {
74 return rs
75 }
76
77 // start with the conditions propagated up the graph
78 rs = ResolveBottomUpConditions(lg)
79
80 // rmap maps 'appliesTo' targets to their applicable conditions
81 //
82 // rmap is the resulting ResolutionSet
83 rmap := make(map[*TargetNode]actionSet)
Bob Badour3a820dd2021-12-07 15:35:07 -080084
85 // cmap contains the set of targets walked as pure aggregates. i.e. containers
86 cmap := make(map[*TargetNode]bool)
Bob Badour9ee7d032021-10-25 16:51:48 -070087
Bob Badour3a820dd2021-12-07 15:35:07 -080088 var walk func(fnode *TargetNode, cs *LicenseConditionSet, treatAsAggregate bool)
Bob Badour9ee7d032021-10-25 16:51:48 -070089
Bob Badour3a820dd2021-12-07 15:35:07 -080090 walk = func(fnode *TargetNode, cs *LicenseConditionSet, treatAsAggregate bool) {
Bob Badourb2855152021-12-08 12:52:59 -080091 if _, ok := rmap[fnode]; !ok {
92 rmap[fnode] = make(actionSet)
Bob Badour9ee7d032021-10-25 16:51:48 -070093 }
Bob Badourb2855152021-12-08 12:52:59 -080094 rmap[fnode].add(fnode, cs)
Bob Badour3a820dd2021-12-07 15:35:07 -080095 if treatAsAggregate {
96 cmap[fnode] = true
97 }
98 // add conditions attached to `fnode`
Bob Badour9ee7d032021-10-25 16:51:48 -070099 cs = cs.Copy()
100 for _, fcs := range rs.resolutions[fnode] {
101 cs.AddSet(fcs)
102 }
103 // for each dependency
Bob Badour3a820dd2021-12-07 15:35:07 -0800104 for _, edge := range lg.index[fnode.name] {
Bob Badour9ee7d032021-10-25 16:51:48 -0700105 e := TargetEdge{lg, edge}
106 // dcs holds the dpendency conditions inherited from the target
107 dcs := targetConditionsApplicableToDep(e, cs, treatAsAggregate)
Bob Badour3a820dd2021-12-07 15:35:07 -0800108 if dcs.IsEmpty() && !treatAsAggregate {
109 continue
110 }
111 dnode := lg.targets[edge.dependency]
112 if as, alreadyWalked := rmap[dnode]; alreadyWalked {
113 diff := dcs.Copy()
114 diff.RemoveSet(as.conditions())
115 if diff.IsEmpty() {
116 // no new conditions
117
118 // pure aggregates never need walking a 2nd time with same conditions
119 if treatAsAggregate {
120 continue
121 }
122 // non-aggregates don't need walking as non-aggregate a 2nd time
123 if _, asAggregate := cmap[dnode]; !asAggregate {
124 continue
125 }
126 // previously walked as pure aggregate; need to re-walk as non-aggregate
127 delete(cmap, dnode)
Bob Badour9ee7d032021-10-25 16:51:48 -0700128 }
129 }
Bob Badour9ee7d032021-10-25 16:51:48 -0700130 // add the conditions to the dependency
Bob Badour3a820dd2021-12-07 15:35:07 -0800131 walk(dnode, dcs, treatAsAggregate && lg.targets[edge.dependency].IsContainer())
Bob Badour9ee7d032021-10-25 16:51:48 -0700132 }
133 }
134
135 // walk each of the roots
136 for _, r := range lg.rootFiles {
Bob Badour3a820dd2021-12-07 15:35:07 -0800137 rnode := lg.targets[r]
138 as, ok := rs.resolutions[rnode]
Bob Badour9ee7d032021-10-25 16:51:48 -0700139 if !ok {
140 // no conditions in root or transitive closure of dependencies
141 continue
142 }
143 if as.isEmpty() {
144 continue
145 }
146
Bob Badour9ee7d032021-10-25 16:51:48 -0700147 // add the conditions to the root and its transitive closure
Bob Badour3a820dd2021-12-07 15:35:07 -0800148 walk(rnode, newLicenseConditionSet(), lg.targets[r].IsContainer())
149 }
150
151 // back-fill any bottom-up conditions on targets missed by top-down walk
152 for attachesTo, as := range rs.resolutions {
153 if _, ok := rmap[attachesTo]; !ok {
154 rmap[attachesTo] = as.copy()
155 } else {
156 rmap[attachesTo].addSet(as)
157 }
Bob Badour9ee7d032021-10-25 16:51:48 -0700158 }
159
Bob Badourb2855152021-12-08 12:52:59 -0800160 // propagate any new conditions back up the graph
161 rs = resolveBottomUp(lg, rmap)
Bob Badour9ee7d032021-10-25 16:51:48 -0700162
163 // if not yet cached, save the result
164 lg.mu.Lock()
165 if lg.rsTD == nil {
166 lg.rsTD = rs
167 } else {
168 // if we end up with 2, release the later for garbage collection
169 rs = lg.rsTD
170 }
171 lg.mu.Unlock()
172
173 return rs
174}
Bob Badourb2855152021-12-08 12:52:59 -0800175
176// resolveBottomUp implements a bottom-up resolve propagating conditions both
177// from the graph, and from a `priors` map of resolutions.
178func resolveBottomUp(lg *LicenseGraph, priors map[*TargetNode]actionSet) *ResolutionSet {
179 rs := newResolutionSet()
180
181 // cmap contains an entry for every target that was previously walked as a pure aggregate only.
182 cmap := make(map[string]bool)
183
184 var walk func(f string, treatAsAggregate bool) actionSet
185
186 walk = func(f string, treatAsAggregate bool) actionSet {
187 target := lg.targets[f]
188 result := make(actionSet)
189 result[target] = newLicenseConditionSet()
190 result[target].add(target, target.proto.LicenseConditions...)
191 if pas, ok := priors[target]; ok {
192 result.addSet(pas)
193 }
194 if preresolved, ok := rs.resolutions[target]; ok {
195 if treatAsAggregate {
196 result.addSet(preresolved)
197 return result
198 }
199 if _, asAggregate := cmap[f]; !asAggregate {
200 result.addSet(preresolved)
201 return result
202 }
203 // previously walked in a pure aggregate context,
204 // needs to walk again in non-aggregate context
205 delete(cmap, f)
206 }
207 if treatAsAggregate {
208 cmap[f] = true
209 }
210
211 // add all the conditions from all the dependencies
212 for _, edge := range lg.index[f] {
213 // walk dependency to get its conditions
214 as := walk(edge.dependency, treatAsAggregate && lg.targets[edge.dependency].IsContainer())
215
216 // turn those into the conditions that apply to the target
217 as = depActionsApplicableToTarget(TargetEdge{lg, edge}, as, treatAsAggregate)
218
219 // add them to the result
220 result.addSet(as)
221 }
222
223 // record these conditions as applicable to the target
224 rs.addConditions(target, result)
225 if len(priors) == 0 {
226 // on the first bottom-up resolve, parents have their own sharing and notice needs
227 // on the later resolve, if priors is empty, there will be nothing new to add
228 rs.addSelf(target, result.byName(ImpliesRestricted))
229 }
230
231 // return this up the tree
232 return result
233 }
234
235 // walk each of the roots
236 for _, r := range lg.rootFiles {
237 _ = walk(r, lg.targets[r].IsContainer())
238 }
239
240 return rs
241}