b285515ca1
Performance optimization means not every path will be traversed. Instead of updating parents via the path, perform a 2nd bottom-up walk after the top-down walk to propagate the new resolutions to parents. Note: the 2nd walk method will add resolutions to statically linked libraries etc. at deeper levels, but those do not affect what gets reported. In particular, note that test data for dumpresolutions changes, but none of the test data for listshare, checkshare etc. changes. Test: m all systemlicense listshare checkshare dumpgraph dumpresolutions Bug: 68860345 Bug: 151177513 Bug: 151953481 Change-Id: I76361c4e33bbadbbea38cbec260430e8f9407628
241 lines
7.2 KiB
Go
241 lines
7.2 KiB
Go
// Copyright 2021 Google LLC
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package compliance
|
|
|
|
// ResolveBottomUpConditions performs a bottom-up walk of the LicenseGraph
|
|
// propagating conditions up the graph as necessary according to the properties
|
|
// of each edge and according to each license condition in question.
|
|
//
|
|
// Subsequent top-down walks of the graph will filter some resolutions and may
|
|
// introduce new resolutions.
|
|
//
|
|
// e.g. if a "restricted" condition applies to a binary, it also applies to all
|
|
// of the statically-linked libraries and the transitive closure of their static
|
|
// dependencies; even if neither they nor the transitive closure of their
|
|
// dependencies originate any "restricted" conditions. The bottom-up walk will
|
|
// not resolve the library and its transitive closure, but the later top-down
|
|
// walk will.
|
|
func ResolveBottomUpConditions(lg *LicenseGraph) *ResolutionSet {
|
|
|
|
// short-cut if already walked and cached
|
|
lg.mu.Lock()
|
|
rs := lg.rsBU
|
|
lg.mu.Unlock()
|
|
|
|
if rs != nil {
|
|
return rs
|
|
}
|
|
|
|
// must be indexed for fast lookup
|
|
lg.indexForward()
|
|
|
|
rs = resolveBottomUp(lg, make(map[*TargetNode]actionSet) /* empty map; no prior resolves */)
|
|
|
|
// if not yet cached, save the result
|
|
lg.mu.Lock()
|
|
if lg.rsBU == nil {
|
|
lg.rsBU = rs
|
|
} else {
|
|
// if we end up with 2, release the later for garbage collection
|
|
rs = lg.rsBU
|
|
}
|
|
lg.mu.Unlock()
|
|
|
|
return rs
|
|
}
|
|
|
|
// ResolveTopDownCondtions performs a top-down walk of the LicenseGraph
|
|
// resolving all reachable nodes for `condition`. Policy establishes the rules
|
|
// for transforming and propagating resolutions down the graph.
|
|
//
|
|
// e.g. For current policy, none of the conditions propagate from target to
|
|
// dependency except restricted. For restricted, the policy is to share the
|
|
// source of any libraries linked to restricted code and to provide notice.
|
|
func ResolveTopDownConditions(lg *LicenseGraph) *ResolutionSet {
|
|
|
|
// short-cut if already walked and cached
|
|
lg.mu.Lock()
|
|
rs := lg.rsTD
|
|
lg.mu.Unlock()
|
|
|
|
if rs != nil {
|
|
return rs
|
|
}
|
|
|
|
// start with the conditions propagated up the graph
|
|
rs = ResolveBottomUpConditions(lg)
|
|
|
|
// rmap maps 'appliesTo' targets to their applicable conditions
|
|
//
|
|
// rmap is the resulting ResolutionSet
|
|
rmap := make(map[*TargetNode]actionSet)
|
|
|
|
// cmap contains the set of targets walked as pure aggregates. i.e. containers
|
|
cmap := make(map[*TargetNode]bool)
|
|
|
|
var walk func(fnode *TargetNode, cs *LicenseConditionSet, treatAsAggregate bool)
|
|
|
|
walk = func(fnode *TargetNode, cs *LicenseConditionSet, treatAsAggregate bool) {
|
|
if _, ok := rmap[fnode]; !ok {
|
|
rmap[fnode] = make(actionSet)
|
|
}
|
|
rmap[fnode].add(fnode, cs)
|
|
if treatAsAggregate {
|
|
cmap[fnode] = true
|
|
}
|
|
// add conditions attached to `fnode`
|
|
cs = cs.Copy()
|
|
for _, fcs := range rs.resolutions[fnode] {
|
|
cs.AddSet(fcs)
|
|
}
|
|
// for each dependency
|
|
for _, edge := range lg.index[fnode.name] {
|
|
e := TargetEdge{lg, edge}
|
|
// dcs holds the dpendency conditions inherited from the target
|
|
dcs := targetConditionsApplicableToDep(e, cs, treatAsAggregate)
|
|
if dcs.IsEmpty() && !treatAsAggregate {
|
|
continue
|
|
}
|
|
dnode := lg.targets[edge.dependency]
|
|
if as, alreadyWalked := rmap[dnode]; alreadyWalked {
|
|
diff := dcs.Copy()
|
|
diff.RemoveSet(as.conditions())
|
|
if diff.IsEmpty() {
|
|
// no new conditions
|
|
|
|
// pure aggregates never need walking a 2nd time with same conditions
|
|
if treatAsAggregate {
|
|
continue
|
|
}
|
|
// non-aggregates don't need walking as non-aggregate a 2nd time
|
|
if _, asAggregate := cmap[dnode]; !asAggregate {
|
|
continue
|
|
}
|
|
// previously walked as pure aggregate; need to re-walk as non-aggregate
|
|
delete(cmap, dnode)
|
|
}
|
|
}
|
|
// add the conditions to the dependency
|
|
walk(dnode, dcs, treatAsAggregate && lg.targets[edge.dependency].IsContainer())
|
|
}
|
|
}
|
|
|
|
// walk each of the roots
|
|
for _, r := range lg.rootFiles {
|
|
rnode := lg.targets[r]
|
|
as, ok := rs.resolutions[rnode]
|
|
if !ok {
|
|
// no conditions in root or transitive closure of dependencies
|
|
continue
|
|
}
|
|
if as.isEmpty() {
|
|
continue
|
|
}
|
|
|
|
// add the conditions to the root and its transitive closure
|
|
walk(rnode, newLicenseConditionSet(), lg.targets[r].IsContainer())
|
|
}
|
|
|
|
// back-fill any bottom-up conditions on targets missed by top-down walk
|
|
for attachesTo, as := range rs.resolutions {
|
|
if _, ok := rmap[attachesTo]; !ok {
|
|
rmap[attachesTo] = as.copy()
|
|
} else {
|
|
rmap[attachesTo].addSet(as)
|
|
}
|
|
}
|
|
|
|
// propagate any new conditions back up the graph
|
|
rs = resolveBottomUp(lg, rmap)
|
|
|
|
// if not yet cached, save the result
|
|
lg.mu.Lock()
|
|
if lg.rsTD == nil {
|
|
lg.rsTD = rs
|
|
} else {
|
|
// if we end up with 2, release the later for garbage collection
|
|
rs = lg.rsTD
|
|
}
|
|
lg.mu.Unlock()
|
|
|
|
return rs
|
|
}
|
|
|
|
// resolveBottomUp implements a bottom-up resolve propagating conditions both
|
|
// from the graph, and from a `priors` map of resolutions.
|
|
func resolveBottomUp(lg *LicenseGraph, priors map[*TargetNode]actionSet) *ResolutionSet {
|
|
rs := newResolutionSet()
|
|
|
|
// cmap contains an entry for every target that was previously walked as a pure aggregate only.
|
|
cmap := make(map[string]bool)
|
|
|
|
var walk func(f string, treatAsAggregate bool) actionSet
|
|
|
|
walk = func(f string, treatAsAggregate bool) actionSet {
|
|
target := lg.targets[f]
|
|
result := make(actionSet)
|
|
result[target] = newLicenseConditionSet()
|
|
result[target].add(target, target.proto.LicenseConditions...)
|
|
if pas, ok := priors[target]; ok {
|
|
result.addSet(pas)
|
|
}
|
|
if preresolved, ok := rs.resolutions[target]; ok {
|
|
if treatAsAggregate {
|
|
result.addSet(preresolved)
|
|
return result
|
|
}
|
|
if _, asAggregate := cmap[f]; !asAggregate {
|
|
result.addSet(preresolved)
|
|
return result
|
|
}
|
|
// previously walked in a pure aggregate context,
|
|
// needs to walk again in non-aggregate context
|
|
delete(cmap, f)
|
|
}
|
|
if treatAsAggregate {
|
|
cmap[f] = true
|
|
}
|
|
|
|
// add all the conditions from all the dependencies
|
|
for _, edge := range lg.index[f] {
|
|
// walk dependency to get its conditions
|
|
as := walk(edge.dependency, treatAsAggregate && lg.targets[edge.dependency].IsContainer())
|
|
|
|
// turn those into the conditions that apply to the target
|
|
as = depActionsApplicableToTarget(TargetEdge{lg, edge}, as, treatAsAggregate)
|
|
|
|
// add them to the result
|
|
result.addSet(as)
|
|
}
|
|
|
|
// record these conditions as applicable to the target
|
|
rs.addConditions(target, result)
|
|
if len(priors) == 0 {
|
|
// on the first bottom-up resolve, parents have their own sharing and notice needs
|
|
// on the later resolve, if priors is empty, there will be nothing new to add
|
|
rs.addSelf(target, result.byName(ImpliesRestricted))
|
|
}
|
|
|
|
// return this up the tree
|
|
return result
|
|
}
|
|
|
|
// walk each of the roots
|
|
for _, r := range lg.rootFiles {
|
|
_ = walk(r, lg.targets[r].IsContainer())
|
|
}
|
|
|
|
return rs
|
|
}
|