blob: c051ed8bb242b56d1b1edf0c155eb9419354af2a [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
44 rs = newResolutionSet()
45
46 // cmap contains an entry for every target that was previously walked as a pure aggregate only.
47 cmap := make(map[string]bool)
48
49 var walk func(f string, treatAsAggregate bool) actionSet
50
51 walk = func(f string, treatAsAggregate bool) actionSet {
52 target := lg.targets[f]
53 result := make(actionSet)
54 result[target] = newLicenseConditionSet()
55 result[target].add(target, target.proto.LicenseConditions...)
56 if preresolved, ok := rs.resolutions[target]; ok {
57 if treatAsAggregate {
58 result.addSet(preresolved)
59 return result
60 }
61 if _, asAggregate := cmap[f]; !asAggregate {
62 result.addSet(preresolved)
63 return result
64 }
65 // previously walked in a pure aggregate context,
66 // needs to walk again in non-aggregate context
67 delete(cmap, f)
68 }
69 if treatAsAggregate {
70 cmap[f] = true
71 }
72
73 // add all the conditions from all the dependencies
74 for _, edge := range lg.index[f] {
75 // walk dependency to get its conditions
76 as := walk(edge.dependency, treatAsAggregate && lg.targets[edge.dependency].IsContainer())
77
78 // turn those into the conditions that apply to the target
79 as = depActionsApplicableToTarget(TargetEdge{lg, edge}, as, treatAsAggregate)
80
81 // add them to the result
82 result.addSet(as)
83 }
84
85 // record these conditions as applicable to the target
86 rs.addConditions(target, result)
87 rs.addSelf(target, result.byName(ImpliesRestricted))
88
89 // return this up the tree
90 return result
91 }
92
93 // walk each of the roots
94 for _, r := range lg.rootFiles {
95 _ = walk(r, lg.targets[r].IsContainer())
96 }
97
98 // if not yet cached, save the result
99 lg.mu.Lock()
100 if lg.rsBU == nil {
101 lg.rsBU = rs
102 } else {
103 // if we end up with 2, release the later for garbage collection
104 rs = lg.rsBU
105 }
106 lg.mu.Unlock()
107
108 return rs
109}
110
111// ResolveTopDownCondtions performs a top-down walk of the LicenseGraph
112// resolving all reachable nodes for `condition`. Policy establishes the rules
113// for transforming and propagating resolutions down the graph.
114//
115// e.g. For current policy, none of the conditions propagate from target to
116// dependency except restricted. For restricted, the policy is to share the
117// source of any libraries linked to restricted code and to provide notice.
118func ResolveTopDownConditions(lg *LicenseGraph) *ResolutionSet {
119
120 // short-cut if already walked and cached
121 lg.mu.Lock()
122 rs := lg.rsTD
123 lg.mu.Unlock()
124
125 if rs != nil {
126 return rs
127 }
128
129 // start with the conditions propagated up the graph
130 rs = ResolveBottomUpConditions(lg)
131
132 // rmap maps 'appliesTo' targets to their applicable conditions
133 //
134 // rmap is the resulting ResolutionSet
135 rmap := make(map[*TargetNode]actionSet)
Bob Badour3a820dd2021-12-07 15:35:07 -0800136
137 // cmap contains the set of targets walked as pure aggregates. i.e. containers
138 cmap := make(map[*TargetNode]bool)
Bob Badour9ee7d032021-10-25 16:51:48 -0700139
140 path := make([]*dependencyEdge, 0, 32)
141
Bob Badour3a820dd2021-12-07 15:35:07 -0800142 var walk func(fnode *TargetNode, cs *LicenseConditionSet, treatAsAggregate bool)
Bob Badour9ee7d032021-10-25 16:51:48 -0700143
Bob Badour3a820dd2021-12-07 15:35:07 -0800144 walk = func(fnode *TargetNode, cs *LicenseConditionSet, treatAsAggregate bool) {
Bob Badour9ee7d032021-10-25 16:51:48 -0700145 if !cs.IsEmpty() {
146 parentsAllAggregate := true
147 for _, e := range path {
148 target := lg.targets[e.target]
149 if _, ok := rmap[target]; !ok {
150 rmap[target] = make(actionSet)
151 }
152 rmap[target].add(fnode, cs)
153 if !target.IsContainer() {
154 parentsAllAggregate = false
155 break
156 }
157 }
158 if parentsAllAggregate {
159 if _, ok := rmap[fnode]; !ok {
160 rmap[fnode] = make(actionSet)
161 }
162 rmap[fnode].add(fnode, cs)
163 }
164 }
Bob Badour3a820dd2021-12-07 15:35:07 -0800165 if treatAsAggregate {
166 cmap[fnode] = true
167 }
168 // add conditions attached to `fnode`
Bob Badour9ee7d032021-10-25 16:51:48 -0700169 cs = cs.Copy()
170 for _, fcs := range rs.resolutions[fnode] {
171 cs.AddSet(fcs)
172 }
173 // for each dependency
Bob Badour3a820dd2021-12-07 15:35:07 -0800174 for _, edge := range lg.index[fnode.name] {
Bob Badour9ee7d032021-10-25 16:51:48 -0700175 e := TargetEdge{lg, edge}
176 // dcs holds the dpendency conditions inherited from the target
177 dcs := targetConditionsApplicableToDep(e, cs, treatAsAggregate)
Bob Badour3a820dd2021-12-07 15:35:07 -0800178 if dcs.IsEmpty() && !treatAsAggregate {
179 continue
180 }
181 dnode := lg.targets[edge.dependency]
182 if as, alreadyWalked := rmap[dnode]; alreadyWalked {
183 diff := dcs.Copy()
184 diff.RemoveSet(as.conditions())
185 if diff.IsEmpty() {
186 // no new conditions
187
188 // pure aggregates never need walking a 2nd time with same conditions
189 if treatAsAggregate {
190 continue
191 }
192 // non-aggregates don't need walking as non-aggregate a 2nd time
193 if _, asAggregate := cmap[dnode]; !asAggregate {
194 continue
195 }
196 // previously walked as pure aggregate; need to re-walk as non-aggregate
197 delete(cmap, dnode)
Bob Badour9ee7d032021-10-25 16:51:48 -0700198 }
199 }
200 path = append(path, edge)
201 // add the conditions to the dependency
Bob Badour3a820dd2021-12-07 15:35:07 -0800202 walk(dnode, dcs, treatAsAggregate && lg.targets[edge.dependency].IsContainer())
Bob Badour9ee7d032021-10-25 16:51:48 -0700203 path = path[:len(path)-1]
204 }
205 }
206
207 // walk each of the roots
208 for _, r := range lg.rootFiles {
Bob Badour3a820dd2021-12-07 15:35:07 -0800209 rnode := lg.targets[r]
210 as, ok := rs.resolutions[rnode]
Bob Badour9ee7d032021-10-25 16:51:48 -0700211 if !ok {
212 // no conditions in root or transitive closure of dependencies
213 continue
214 }
215 if as.isEmpty() {
216 continue
217 }
218
219 path = path[:0]
220 // add the conditions to the root and its transitive closure
Bob Badour3a820dd2021-12-07 15:35:07 -0800221 walk(rnode, newLicenseConditionSet(), lg.targets[r].IsContainer())
222 }
223
224 // back-fill any bottom-up conditions on targets missed by top-down walk
225 for attachesTo, as := range rs.resolutions {
226 if _, ok := rmap[attachesTo]; !ok {
227 rmap[attachesTo] = as.copy()
228 } else {
229 rmap[attachesTo].addSet(as)
230 }
Bob Badour9ee7d032021-10-25 16:51:48 -0700231 }
232
233 rs = &ResolutionSet{rmap}
234
235 // if not yet cached, save the result
236 lg.mu.Lock()
237 if lg.rsTD == nil {
238 lg.rsTD = rs
239 } else {
240 // if we end up with 2, release the later for garbage collection
241 rs = lg.rsTD
242 }
243 lg.mu.Unlock()
244
245 return rs
246}