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