blob: d357aec2b0e43d887f4d9a80c331fc01c25ffcf4 [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
Bob Badour103eb0f2022-01-10 13:50:57 -080017import (
18 "sync"
19)
20
Bob Badourc8178452022-01-31 13:05:53 -080021var (
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
Bob Badour9ee7d032021-10-25 16:51:48 -070031// 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//
Bob Badour9ee7d032021-10-25 16:51:48 -070035// 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.
Bob Badour103eb0f2022-01-10 13:50:57 -080041func ResolveBottomUpConditions(lg *LicenseGraph) {
Bob Badourc8178452022-01-31 13:05:53 -080042 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) {
Bob Badour9ee7d032021-10-25 16:51:48 -070050
51 // short-cut if already walked and cached
52 lg.mu.Lock()
Bob Badour103eb0f2022-01-10 13:50:57 -080053 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
Bob Badour9ee7d032021-10-25 16:51:48 -070063 lg.mu.Unlock()
64
Bob Badour103eb0f2022-01-10 13:50:57 -080065 // 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 {
Bob Badourc8178452022-01-31 13:05:53 -080091 target.resolution |= conditionsFn(target)
Bob Badour103eb0f2022-01-10 13:50:57 -080092 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
Bob Badour9ee7d032021-10-25 16:51:48 -0700126 }
127
Bob Badour103eb0f2022-01-10 13:50:57 -0800128 // walk each of the roots
129 for _, rname := range lg.rootFiles {
130 rnode := lg.targets[rname]
131 _ = walk(rnode, rnode.IsContainer())
Bob Badour9ee7d032021-10-25 16:51:48 -0700132 }
Bob Badour9ee7d032021-10-25 16:51:48 -0700133
Bob Badour103eb0f2022-01-10 13:50:57 -0800134 wg.Done()
Bob Badour9ee7d032021-10-25 16:51:48 -0700135}
136
137// ResolveTopDownCondtions performs a top-down walk of the LicenseGraph
Bob Badour103eb0f2022-01-10 13:50:57 -0800138// propagating conditions from target to dependency.
Bob Badour9ee7d032021-10-25 16:51:48 -0700139//
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.
Bob Badour103eb0f2022-01-10 13:50:57 -0800143func ResolveTopDownConditions(lg *LicenseGraph) {
Bob Badourc8178452022-01-31 13:05:53 -0800144 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) {
Bob Badour9ee7d032021-10-25 16:51:48 -0700151
152 // short-cut if already walked and cached
153 lg.mu.Lock()
Bob Badour103eb0f2022-01-10 13:50:57 -0800154 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
Bob Badour9ee7d032021-10-25 16:51:48 -0700164 lg.mu.Unlock()
165
Bob Badour9ee7d032021-10-25 16:51:48 -0700166 // start with the conditions propagated up the graph
Bob Badourc8178452022-01-31 13:05:53 -0800167 TraceBottomUpConditions(lg, conditionsFn)
Bob Badour9ee7d032021-10-25 16:51:48 -0700168
Bob Badour103eb0f2022-01-10 13:50:57 -0800169 // amap contains the set of targets already walked. (guarded by mu)
170 amap := make(map[*TargetNode]struct{})
Bob Badour3a820dd2021-12-07 15:35:07 -0800171
172 // cmap contains the set of targets walked as pure aggregates. i.e. containers
Bob Badour103eb0f2022-01-10 13:50:57 -0800173 // (guarded by mu)
Bob Badour5446a6f2022-01-10 18:44:59 -0800174 cmap := make(map[*TargetNode]struct{})
Bob Badour9ee7d032021-10-25 16:51:48 -0700175
Bob Badour103eb0f2022-01-10 13:50:57 -0800176 // mu guards concurrent access to cmap
177 var mu sync.Mutex
Bob Badour9ee7d032021-10-25 16:51:48 -0700178
Bob Badour103eb0f2022-01-10 13:50:57 -0800179 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()
Bob Badourc8178452022-01-31 13:05:53 -0800184 fnode.resolution |= conditionsFn(fnode)
Bob Badour103eb0f2022-01-10 13:50:57 -0800185 fnode.resolution |= cs
186 amap[fnode] = struct{}{}
Bob Badour3a820dd2021-12-07 15:35:07 -0800187 if treatAsAggregate {
Bob Badour5446a6f2022-01-10 18:44:59 -0800188 cmap[fnode] = struct{}{}
Bob Badour3a820dd2021-12-07 15:35:07 -0800189 }
Bob Badour103eb0f2022-01-10 13:50:57 -0800190 cs = fnode.resolution
191 mu.Unlock()
Bob Badour9ee7d032021-10-25 16:51:48 -0700192 // for each dependency
Bob Badour103eb0f2022-01-10 13:50:57 -0800193 for _, edge := range fnode.edges {
194 func(edge *TargetEdge) {
195 // dcs holds the dpendency conditions inherited from the target
Bob Badourc8178452022-01-31 13:05:53 -0800196 dcs := targetConditionsPropagatingToDep(lg, edge, cs, treatAsAggregate, conditionsFn)
Bob Badour103eb0f2022-01-10 13:50:57 -0800197 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
Bob Badour3a820dd2021-12-07 15:35:07 -0800205
Bob Badour103eb0f2022-01-10 13:50:57 -0800206 // 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)
Bob Badour3a820dd2021-12-07 15:35:07 -0800216 }
Bob Badour9ee7d032021-10-25 16:51:48 -0700217 }
Bob Badour103eb0f2022-01-10 13:50:57 -0800218 // add the conditions to the dependency
219 wg.Add(1)
220 go walk(dnode, dcs, treatAsAggregate && dnode.IsContainer())
221 }(edge)
Bob Badour9ee7d032021-10-25 16:51:48 -0700222 }
223 }
224
225 // walk each of the roots
Bob Badour103eb0f2022-01-10 13:50:57 -0800226 for _, rname := range lg.rootFiles {
227 rnode := lg.targets[rname]
228 wg.Add(1)
Bob Badour9ee7d032021-10-25 16:51:48 -0700229 // add the conditions to the root and its transitive closure
Bob Badour103eb0f2022-01-10 13:50:57 -0800230 go walk(rnode, NewLicenseConditionSet(), rnode.IsContainer())
Bob Badour3a820dd2021-12-07 15:35:07 -0800231 }
Bob Badour103eb0f2022-01-10 13:50:57 -0800232 wg.Done()
233 wg.Wait()
Bob Badourb2855152021-12-08 12:52:59 -0800234}