blob: 9962e684920523d2eeed5c73b6504daf6ca8ef65 [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)
136 for attachesTo, as := range rs.resolutions {
137 rmap[attachesTo] = as.copy()
138 }
139
140 path := make([]*dependencyEdge, 0, 32)
141
142 var walk func(f string, cs *LicenseConditionSet, treatAsAggregate bool)
143
144 walk = func(f string, cs *LicenseConditionSet, treatAsAggregate bool) {
145 fnode := lg.targets[f]
146 if !cs.IsEmpty() {
147 parentsAllAggregate := true
148 for _, e := range path {
149 target := lg.targets[e.target]
150 if _, ok := rmap[target]; !ok {
151 rmap[target] = make(actionSet)
152 }
153 rmap[target].add(fnode, cs)
154 if !target.IsContainer() {
155 parentsAllAggregate = false
156 break
157 }
158 }
159 if parentsAllAggregate {
160 if _, ok := rmap[fnode]; !ok {
161 rmap[fnode] = make(actionSet)
162 }
163 rmap[fnode].add(fnode, cs)
164 }
165 }
166 // add conditions attached to `f`
167 cs = cs.Copy()
168 for _, fcs := range rs.resolutions[fnode] {
169 cs.AddSet(fcs)
170 }
171 // for each dependency
172 for _, edge := range lg.index[f] {
173 e := TargetEdge{lg, edge}
174 // dcs holds the dpendency conditions inherited from the target
175 dcs := targetConditionsApplicableToDep(e, cs, treatAsAggregate)
176 if dcs.IsEmpty() {
177 if !treatAsAggregate || (!edgeIsDerivation(e) && !edgeIsDynamicLink(e)) {
178 continue
179 }
180 }
181 path = append(path, edge)
182 // add the conditions to the dependency
183 walk(edge.dependency, dcs, treatAsAggregate && lg.targets[edge.dependency].IsContainer())
184 path = path[:len(path)-1]
185 }
186 }
187
188 // walk each of the roots
189 for _, r := range lg.rootFiles {
190 as, ok := rs.resolutions[lg.targets[r]]
191 if !ok {
192 // no conditions in root or transitive closure of dependencies
193 continue
194 }
195 if as.isEmpty() {
196 continue
197 }
198
199 path = path[:0]
200 // add the conditions to the root and its transitive closure
201 walk(r, newLicenseConditionSet(), lg.targets[r].IsContainer())
202 }
203
204 rs = &ResolutionSet{rmap}
205
206 // if not yet cached, save the result
207 lg.mu.Lock()
208 if lg.rsTD == nil {
209 lg.rsTD = rs
210 } else {
211 // if we end up with 2, release the later for garbage collection
212 rs = lg.rsTD
213 }
214 lg.mu.Unlock()
215
216 return rs
217}