Skip to content

Commit 2867504

Browse files
j2objc-copybaracopybara-github
authored andcommitted
Separate the summarization action from TreeShaker.
PiperOrigin-RevId: 553916303
1 parent 22200f9 commit 2867504

File tree

5 files changed

+196
-28
lines changed

5 files changed

+196
-28
lines changed

tree_shaker/src/main/java/com/google/devtools/treeshaker/Options.java

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.google.common.io.Resources;
2323
import com.google.devtools.j2objc.util.SourceVersion;
2424
import com.google.devtools.j2objc.util.Version;
25+
import com.google.protobuf.ExtensionRegistry;
2526
import java.io.BufferedReader;
2627
import java.io.File;
2728
import java.io.FileReader;
@@ -64,6 +65,8 @@ class Options {
6465
private boolean stripReflection = false;
6566
private File treeShakerRoots;
6667
private File outputFile = new File("tree-shaker-report.txt");
68+
private LibraryInfo summary;
69+
private String summaryOutputFile;
6770

6871
// The default source version number if not passed with -source is determined from the system
6972
// properties of the running java version after parsing the argument list.
@@ -125,6 +128,18 @@ public File getOutputFile() {
125128
return outputFile;
126129
}
127130

131+
public LibraryInfo getSummary() {
132+
return summary;
133+
}
134+
135+
public void setSummary(LibraryInfo summary) {
136+
this.summary = summary;
137+
}
138+
139+
public String getSummaryOutputFile() {
140+
return summaryOutputFile;
141+
}
142+
128143
private void addManifest(String manifestFile) throws IOException {
129144
BufferedReader in = new BufferedReader(new FileReader(new File(manifestFile)));
130145
try {
@@ -174,14 +189,6 @@ public static void version() {
174189
public static Options parse(String[] args) throws IOException {
175190
Options options = new Options();
176191
processArgs(args, options);
177-
178-
if (options.treeShakerRoots == null) {
179-
usage("--tree_shaker_roots not set");
180-
}
181-
if (options.sourceFiles.isEmpty()) {
182-
usage("no source files");
183-
}
184-
185192
return options;
186193
}
187194

@@ -213,6 +220,13 @@ private static void processArgs(String[] args, Options options) throws IOExcepti
213220
usage("-classpath requires an argument");
214221
}
215222
options.classpath = args[nArg];
223+
} else if (arg.equals("-summary")) {
224+
if (++nArg == args.length) {
225+
usage("-summary requires an argument");
226+
}
227+
options.setSummary(
228+
LibraryInfo.parseFrom(
229+
Files.toByteArray(new File(args[nArg])), ExtensionRegistry.getGeneratedRegistry()));
216230
} else if (arg.equals("--sourcefilelist") || arg.equals("-s")) {
217231
if (++nArg == args.length) {
218232
usage("--sourcefilelist requires an argument");
@@ -228,6 +242,11 @@ private static void processArgs(String[] args, Options options) throws IOExcepti
228242
usage("--output-file");
229243
}
230244
options.outputFile = new File(args[nArg]);
245+
} else if (arg.equals("--output-summary")) {
246+
if (++nArg == args.length) {
247+
usage("--output-summary");
248+
}
249+
options.summaryOutputFile = args[nArg];
231250
} else if (arg.startsWith(XBOOTCLASSPATH)) {
232251
// TODO(malvania): Enable the bootclasspath option when we have a class file AST
233252
// parser that can use class jars.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* https://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package com.google.devtools.treeshaker;
15+
16+
import com.google.common.io.Files;
17+
import com.google.devtools.j2objc.util.ErrorUtil;
18+
import java.io.File;
19+
import java.io.IOException;
20+
21+
/** A tool for creating type information summaries of a Java program. */
22+
public class Summarizer {
23+
24+
private Summarizer() {}
25+
26+
public static void main(String[] args) {
27+
try {
28+
Options options = Options.parse(args);
29+
TreeShaker treeShaker = new TreeShaker(options);
30+
LibraryInfo summary = treeShaker.createLibraryInfo();
31+
File summaryFile = new File(options.getSummaryOutputFile());
32+
summaryFile.createNewFile();
33+
Files.write(summary.toByteArray(), summaryFile);
34+
} catch (IOException e) {
35+
ErrorUtil.error(e.getMessage());
36+
}
37+
}
38+
39+
}

tree_shaker/src/main/java/com/google/devtools/treeshaker/TreeShaker.java

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.jspecify.nullness.Nullable;
3838

3939
/** A tool for finding unused code in a Java program. */
40+
@SuppressWarnings("FloggerRedundantIsEnabled")
4041
public class TreeShaker {
4142
private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
4243
private final Options options;
@@ -144,9 +145,8 @@ private File stripIncompatible(List<String> sourceFileNames, Parser parser) thro
144145
return strippedDir;
145146
}
146147

147-
@Nullable
148148
@VisibleForTesting
149-
CodeReferenceMap findUnusedCode() throws IOException {
149+
@Nullable CodeReferenceMap findUnusedCode() throws IOException {
150150
TypeGraphBuilder tgb = createTypeGraphBuilder();
151151
if (tgb == null) {
152152
return null;
@@ -155,7 +155,7 @@ CodeReferenceMap findUnusedCode() throws IOException {
155155
logger.atFine().log("External Types: %s", String.join(", ", tgb.getExternalTypeReferences()));
156156
}
157157
Collection<String> unknownMethodReferences = tgb.getUnknownMethodReferences();
158-
if (!unknownMethodReferences.isEmpty() && logger.atWarning().isEnabled()) {
158+
if (!unknownMethodReferences.isEmpty()) {
159159
logger.atWarning().log("Unknown Methods: %s", String.join(", ", unknownMethodReferences));
160160
}
161161
if (options.useClassHierarchyAnalyzer()) {
@@ -166,9 +166,21 @@ CodeReferenceMap findUnusedCode() throws IOException {
166166
}
167167

168168
private TypeGraphBuilder createTypeGraphBuilder() throws IOException {
169-
UsedCodeMarker.Context context =
170-
new UsedCodeMarker.Context(
171-
ProGuardUsageParser.parseDeadCodeFile(options.getTreeShakerRoots()));
169+
if (options.getSummary() != null) {
170+
LibraryInfo info = options.getSummary();
171+
LibraryInfo markedInfo = UsedCodeMarker.mark(info, options.getTreeShakerRoots());
172+
return new TypeGraphBuilder(markedInfo);
173+
}
174+
return new TypeGraphBuilder(createLibraryInfo());
175+
}
176+
177+
@Nullable LibraryInfo createLibraryInfo() throws IOException {
178+
UsedCodeMarker.Context context;
179+
if (options.getTreeShakerRoots() == null) {
180+
context = new UsedCodeMarker.Context();
181+
} else {
182+
context = new UsedCodeMarker.Context(ProGuardUsageParser.parseDeadCodeFile(options.getTreeShakerRoots()));
183+
}
172184
Parser parser = createParser(options);
173185
List<String> sourceFiles = getSourceFiles();
174186
if (ErrorUtil.errorCount() > 0) {
@@ -189,7 +201,7 @@ public void handleParsedUnit(String path, CompilationUnit unit) {
189201
if (ErrorUtil.errorCount() > 0) {
190202
return null;
191203
}
192-
return new TypeGraphBuilder(context.getLibraryInfo());
204+
return context.getLibraryInfo();
193205
}
194206

195207
private List<String> getSourceFiles() {

tree_shaker/src/main/java/com/google/devtools/treeshaker/UsedCodeMarker.java

Lines changed: 110 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,12 @@
5858
import com.google.devtools.j2objc.ast.VariableDeclarationFragment;
5959
import com.google.devtools.j2objc.util.CodeReferenceMap;
6060
import com.google.devtools.j2objc.util.ElementUtil;
61+
import com.google.devtools.j2objc.util.ProGuardUsageParser;
6162
import com.google.devtools.j2objc.util.TypeUtil;
63+
import java.io.File;
6264
import java.lang.reflect.Modifier;
6365
import java.util.ArrayDeque;
66+
import java.util.ArrayList;
6467
import java.util.Deque;
6568
import java.util.HashMap;
6669
import java.util.HashSet;
@@ -494,6 +497,9 @@ private static Annotations getAnnotations(AnnotatedConstruct annotatedConstruct)
494497
private void startTypeScope(
495498
String typeName, String superName, List<String> interfaces, boolean isExported) {
496499
Integer id = getTypeId(typeName);
500+
if (!context.currentTypeInfoScope.isEmpty()) {
501+
context.currentTypeInfoScope.peek().addInnerTypes(id);
502+
}
497503
Integer eid = getTypeId(superName);
498504
ImmutableList<Integer> iids =
499505
interfaces.stream().map(this::getTypeId).collect(toImmutableList());
@@ -626,6 +632,104 @@ private void popClinit() {
626632
context.referencedTypesScope.pop();
627633
}
628634

635+
private static ImmutableSet<String> getExportedClasses(CodeReferenceMap rootSet) {
636+
return rootSet == null ? ImmutableSet.of() : rootSet.getReferencedClasses();
637+
}
638+
639+
private static ImmutableSet<String> getExportedMethods(CodeReferenceMap rootSet) {
640+
Set<String> exportedMethods = new HashSet<>();
641+
if (rootSet != null) {
642+
rootSet
643+
.getReferencedMethods()
644+
.cellSet()
645+
.forEach(
646+
cell ->
647+
cell.getValue()
648+
.forEach(
649+
signature ->
650+
exportedMethods.add(
651+
getQualifiedMethodName(
652+
cell.getRowKey(), cell.getColumnKey(), signature))));
653+
}
654+
return ImmutableSet.copyOf(exportedMethods);
655+
}
656+
657+
private static List<TypeInfo> markClasses(
658+
List<TypeInfo> types, List<String> typeMap, Set<String> markedClasses) {
659+
if (markedClasses.isEmpty()) {
660+
return types;
661+
}
662+
List<TypeInfo> markedTypes = new ArrayList<>();
663+
Set<String> nextMarkedClasses = new HashSet<>();
664+
for (TypeInfo type : types) {
665+
TypeInfo.Builder typeBuilder = type.toBuilder();
666+
if (markedClasses.contains(typeMap.get(type.getTypeId()))) {
667+
// Set type as exported.
668+
typeBuilder.setExported(true).clearMember();
669+
for (MemberInfo member : type.getMemberList()) {
670+
// Set each method of the type as exported.
671+
typeBuilder.addMember(member.toBuilder().setExported(true).build());
672+
}
673+
// Add inner types that need to be exported to a list.
674+
nextMarkedClasses.addAll(
675+
type.getInnerTypesList().stream().map(typeMap::get).collect(toImmutableList()));
676+
}
677+
// Add type to list of marked types
678+
markedTypes.add(typeBuilder.build());
679+
}
680+
// Recursively call with the list of exported classes as the inner classes that need to be
681+
// exported.
682+
// This is because we do not know if the inner class has inner classes (alternative, while
683+
// loop).
684+
return markClasses(markedTypes, typeMap, nextMarkedClasses);
685+
}
686+
687+
private static TypeInfo markMethodsOfType(
688+
TypeInfo type, List<String> typeMap, Set<String> markedMethods) {
689+
TypeInfo.Builder typeBuilder = type.toBuilder().clearMember();
690+
for (MemberInfo member : type.getMemberList()) {
691+
MemberInfo.Builder memberBuilder = member.toBuilder();
692+
if (markedMethods.contains(
693+
getQualifiedMethodName(typeMap.get(type.getTypeId()), member.getName()))) {
694+
memberBuilder.setExported(true);
695+
}
696+
typeBuilder.addMember(memberBuilder.build());
697+
}
698+
return typeBuilder.build();
699+
}
700+
701+
private static ImmutableList<TypeInfo> markMethods(
702+
List<TypeInfo> types, List<String> typeMap, Set<String> markedMethods) {
703+
List<TypeInfo> typesWithMarkedMembers = new ArrayList<>();
704+
for (TypeInfo type : types) {
705+
typesWithMarkedMembers.add(markMethodsOfType(type, typeMap, markedMethods));
706+
}
707+
return ImmutableList.copyOf(typesWithMarkedMembers);
708+
}
709+
710+
/*
711+
* Uses exported classes given to 'mark' summary information as 'live' for TypeGraphAnalyzer.
712+
*/
713+
private static LibraryInfo markEntryClasses(
714+
LibraryInfo summary,
715+
ImmutableSet<String> exportedClasses,
716+
ImmutableSet<String> exportedMethods) {
717+
return summary.toBuilder()
718+
.clearType()
719+
.addAllType(
720+
markClasses(
721+
markMethods(summary.getTypeList(), summary.getTypeMapList(), exportedMethods),
722+
summary.getTypeMapList(),
723+
exportedClasses))
724+
.build();
725+
}
726+
727+
static LibraryInfo mark(LibraryInfo summary, File roots) {
728+
CodeReferenceMap rootSet = ProGuardUsageParser.parseDeadCodeFile(roots);
729+
return markEntryClasses(
730+
summary, getExportedClasses(rootSet), UsedCodeMarker.getExportedMethods(rootSet));
731+
}
732+
629733
static final class Context {
630734
// Map of type names to unique integer.
631735
private int typeCount;
@@ -653,20 +757,13 @@ static final class Context {
653757
private final Deque<Set<Integer>> clinitReferencedTypesScope = new ArrayDeque<>();
654758

655759
Context(CodeReferenceMap rootSet) {
760+
exportedMethods = getExportedMethods(rootSet);
761+
exportedClasses = getExportedClasses(rootSet);
762+
}
763+
764+
Context() {
656765
exportedMethods = new HashSet<>();
657-
rootSet
658-
.getReferencedMethods()
659-
.cellSet()
660-
.forEach(
661-
cell -> {
662-
String type = cell.getRowKey();
663-
String name = cell.getColumnKey();
664-
cell.getValue()
665-
.forEach(
666-
signature ->
667-
exportedMethods.add(getQualifiedMethodName(type, name, signature)));
668-
});
669-
exportedClasses = rootSet.getReferencedClasses();
766+
exportedClasses = ImmutableSet.copyOf(new HashSet<>());
670767
}
671768

672769
LibraryInfo getLibraryInfo() {

tree_shaker/src/main/java/com/google/devtools/treeshaker/library_info.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ message TypeInfo {
1616
repeated int32 implements_type = 3;
1717
repeated MemberInfo member = 4;
1818
bool exported = 5;
19+
repeated int32 inner_types = 6;
1920
}
2021

2122
message MemberInfo {

0 commit comments

Comments
 (0)