| // Copyright 2024 The Bazel Authors. All rights reserved. |
| // |
| // 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 com.google.devtools.build.lib.buildtool; |
| |
| import static com.google.devtools.build.lib.util.ShallowObjectSizeComputer.getArraySize; |
| import static com.google.devtools.build.lib.util.ShallowObjectSizeComputer.getShallowSize; |
| |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Multimap; |
| import com.google.devtools.build.lib.collect.CompactImmutableMap; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSet; |
| import com.google.devtools.build.lib.util.MemoryAccountant; |
| import com.google.devtools.build.lib.util.ObjectGraphTraverser.DomainSpecificTraverser; |
| import com.google.devtools.build.lib.util.ObjectGraphTraverser.Traversal; |
| import java.lang.reflect.Field; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import javax.annotation.Nullable; |
| |
| /** An object traverser that handles common collection classes. */ |
| public class CollectionObjectTraverser |
| implements DomainSpecificTraverser, MemoryAccountant.Measurer { |
| private static final String NESTEDSET_ARRAY = "Object[] NestedSet"; |
| private static final Field NESTEDSET_CHILDREN; |
| |
| static { |
| try { |
| NESTEDSET_CHILDREN = NestedSet.class.getDeclaredField("children"); |
| NESTEDSET_CHILDREN.setAccessible(true); |
| } catch (NoSuchFieldException e) { |
| throw new IllegalStateException(e); |
| } |
| } |
| |
| @Override |
| public boolean isInterned(Object o) { |
| return false; |
| } |
| |
| @Override |
| public long maybeGetShallowSize(Object o) { |
| return switch (o) { |
| case List<?> l -> getShallowSize(l) + getArraySize(l.size(), Object.class); |
| case Set<?> s -> getShallowSize(s) + getArraySize(s.size(), Object.class); |
| case Map<?, ?> m -> |
| // 32 is an estimate for the per-entry overhead of Map and Multimap |
| getShallowSize(m) + m.size() * 32L; |
| case Multimap<?, ?> mm -> getShallowSize(mm) + mm.size() * 32L; |
| case CompactImmutableMap<?, ?> cim -> |
| // For CompactImmutableMap, we ignore OffsetTable: it's interned so it's difficult to |
| // assign to any one SkyValue and there aren't supposed to be many of those anyway. |
| getShallowSize(cim) + getArraySize(cim.size(), Object.class); |
| |
| default -> -1; |
| }; |
| } |
| |
| @Override |
| @SuppressWarnings({"rawtypes", "unchecked"}) |
| public boolean maybeTraverse(Object o, Traversal traversal) { |
| switch (o) { |
| case List<?> l -> { |
| traversal.objectFound(l, "List"); |
| for (Object m : l) { |
| traversal.edgeFound(m, null); |
| } |
| |
| return true; |
| } |
| |
| case Set<?> s -> { |
| traversal.objectFound(s, "Set"); |
| for (Object m : s) { |
| traversal.edgeFound(m, null); |
| } |
| |
| return true; |
| } |
| |
| case Map<?, ?> m -> { |
| traversal.objectFound(m, "Map"); |
| for (Map.Entry<?, ?> e : m.entrySet()) { |
| traversal.edgeFound(e.getKey(), null); |
| traversal.edgeFound(e.getValue(), null); |
| } |
| |
| return true; |
| } |
| |
| case Multimap<?, ?> mm -> { |
| traversal.objectFound(mm, "Multimap"); |
| for (Map.Entry<?, ?> e : mm.entries()) { |
| traversal.edgeFound(e.getKey(), null); |
| traversal.edgeFound(e.getValue(), null); |
| } |
| |
| return true; |
| } |
| |
| case CompactImmutableMap cim -> { |
| traversal.objectFound(cim, "CompactImmutableMap"); |
| for (Object k : cim) { |
| traversal.edgeFound(k, null); |
| traversal.edgeFound(cim.get(k), null); |
| } |
| return true; |
| } |
| |
| case NestedSet<?> ns -> { |
| traversal.objectFound(ns, "NestedSet"); |
| Object children; |
| try { |
| children = NESTEDSET_CHILDREN.get(ns); |
| } catch (IllegalArgumentException | IllegalAccessException e) { |
| throw new IllegalStateException(e); |
| } |
| |
| if (children instanceof Object[]) { |
| traversal.edgeFound(children, NESTEDSET_ARRAY); |
| } else { |
| traversal.edgeFound(children, null); |
| } |
| |
| return true; |
| } |
| |
| default -> { |
| return false; |
| } |
| } |
| } |
| |
| @Override |
| public boolean admit(Object o) { |
| return true; |
| } |
| |
| @Nullable |
| @Override |
| public String contextForArrayItem(Object from, String fromContext, Object to) { |
| return null; |
| } |
| |
| @Nullable |
| @Override |
| public String contextForField(Object from, String fromContext, Field field, Object to) { |
| return null; |
| } |
| |
| @Nullable |
| @Override |
| public ImmutableSet<String> ignoredFields(Class<?> clazz) { |
| return null; |
| } |
| } |