Skip to content

Commit

Permalink
Separate the summarization action from TreeShaker.
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 553916303
  • Loading branch information
j2objc-copybara authored and copybara-github committed Aug 4, 2023
1 parent 22200f9 commit 2867504
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.google.common.io.Resources;
import com.google.devtools.j2objc.util.SourceVersion;
import com.google.devtools.j2objc.util.Version;
import com.google.protobuf.ExtensionRegistry;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
Expand Down Expand Up @@ -64,6 +65,8 @@ class Options {
private boolean stripReflection = false;
private File treeShakerRoots;
private File outputFile = new File("tree-shaker-report.txt");
private LibraryInfo summary;
private String summaryOutputFile;

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

public LibraryInfo getSummary() {
return summary;
}

public void setSummary(LibraryInfo summary) {
this.summary = summary;
}

public String getSummaryOutputFile() {
return summaryOutputFile;
}

private void addManifest(String manifestFile) throws IOException {
BufferedReader in = new BufferedReader(new FileReader(new File(manifestFile)));
try {
Expand Down Expand Up @@ -174,14 +189,6 @@ public static void version() {
public static Options parse(String[] args) throws IOException {
Options options = new Options();
processArgs(args, options);

if (options.treeShakerRoots == null) {
usage("--tree_shaker_roots not set");
}
if (options.sourceFiles.isEmpty()) {
usage("no source files");
}

return options;
}

Expand Down Expand Up @@ -213,6 +220,13 @@ private static void processArgs(String[] args, Options options) throws IOExcepti
usage("-classpath requires an argument");
}
options.classpath = args[nArg];
} else if (arg.equals("-summary")) {
if (++nArg == args.length) {
usage("-summary requires an argument");
}
options.setSummary(
LibraryInfo.parseFrom(
Files.toByteArray(new File(args[nArg])), ExtensionRegistry.getGeneratedRegistry()));
} else if (arg.equals("--sourcefilelist") || arg.equals("-s")) {
if (++nArg == args.length) {
usage("--sourcefilelist requires an argument");
Expand All @@ -228,6 +242,11 @@ private static void processArgs(String[] args, Options options) throws IOExcepti
usage("--output-file");
}
options.outputFile = new File(args[nArg]);
} else if (arg.equals("--output-summary")) {
if (++nArg == args.length) {
usage("--output-summary");
}
options.summaryOutputFile = args[nArg];
} else if (arg.startsWith(XBOOTCLASSPATH)) {
// TODO(malvania): Enable the bootclasspath option when we have a class file AST
// parser that can use class jars.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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
*
* https://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.treeshaker;

import com.google.common.io.Files;
import com.google.devtools.j2objc.util.ErrorUtil;
import java.io.File;
import java.io.IOException;

/** A tool for creating type information summaries of a Java program. */
public class Summarizer {

private Summarizer() {}

public static void main(String[] args) {
try {
Options options = Options.parse(args);
TreeShaker treeShaker = new TreeShaker(options);
LibraryInfo summary = treeShaker.createLibraryInfo();
File summaryFile = new File(options.getSummaryOutputFile());
summaryFile.createNewFile();
Files.write(summary.toByteArray(), summaryFile);
} catch (IOException e) {
ErrorUtil.error(e.getMessage());
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.jspecify.nullness.Nullable;

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

@Nullable
@VisibleForTesting
CodeReferenceMap findUnusedCode() throws IOException {
@Nullable CodeReferenceMap findUnusedCode() throws IOException {
TypeGraphBuilder tgb = createTypeGraphBuilder();
if (tgb == null) {
return null;
Expand All @@ -155,7 +155,7 @@ CodeReferenceMap findUnusedCode() throws IOException {
logger.atFine().log("External Types: %s", String.join(", ", tgb.getExternalTypeReferences()));
}
Collection<String> unknownMethodReferences = tgb.getUnknownMethodReferences();
if (!unknownMethodReferences.isEmpty() && logger.atWarning().isEnabled()) {
if (!unknownMethodReferences.isEmpty()) {
logger.atWarning().log("Unknown Methods: %s", String.join(", ", unknownMethodReferences));
}
if (options.useClassHierarchyAnalyzer()) {
Expand All @@ -166,9 +166,21 @@ CodeReferenceMap findUnusedCode() throws IOException {
}

private TypeGraphBuilder createTypeGraphBuilder() throws IOException {
UsedCodeMarker.Context context =
new UsedCodeMarker.Context(
ProGuardUsageParser.parseDeadCodeFile(options.getTreeShakerRoots()));
if (options.getSummary() != null) {
LibraryInfo info = options.getSummary();
LibraryInfo markedInfo = UsedCodeMarker.mark(info, options.getTreeShakerRoots());
return new TypeGraphBuilder(markedInfo);
}
return new TypeGraphBuilder(createLibraryInfo());
}

@Nullable LibraryInfo createLibraryInfo() throws IOException {
UsedCodeMarker.Context context;
if (options.getTreeShakerRoots() == null) {
context = new UsedCodeMarker.Context();
} else {
context = new UsedCodeMarker.Context(ProGuardUsageParser.parseDeadCodeFile(options.getTreeShakerRoots()));
}
Parser parser = createParser(options);
List<String> sourceFiles = getSourceFiles();
if (ErrorUtil.errorCount() > 0) {
Expand All @@ -189,7 +201,7 @@ public void handleParsedUnit(String path, CompilationUnit unit) {
if (ErrorUtil.errorCount() > 0) {
return null;
}
return new TypeGraphBuilder(context.getLibraryInfo());
return context.getLibraryInfo();
}

private List<String> getSourceFiles() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,12 @@
import com.google.devtools.j2objc.ast.VariableDeclarationFragment;
import com.google.devtools.j2objc.util.CodeReferenceMap;
import com.google.devtools.j2objc.util.ElementUtil;
import com.google.devtools.j2objc.util.ProGuardUsageParser;
import com.google.devtools.j2objc.util.TypeUtil;
import java.io.File;
import java.lang.reflect.Modifier;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
Expand Down Expand Up @@ -494,6 +497,9 @@ private static Annotations getAnnotations(AnnotatedConstruct annotatedConstruct)
private void startTypeScope(
String typeName, String superName, List<String> interfaces, boolean isExported) {
Integer id = getTypeId(typeName);
if (!context.currentTypeInfoScope.isEmpty()) {
context.currentTypeInfoScope.peek().addInnerTypes(id);
}
Integer eid = getTypeId(superName);
ImmutableList<Integer> iids =
interfaces.stream().map(this::getTypeId).collect(toImmutableList());
Expand Down Expand Up @@ -626,6 +632,104 @@ private void popClinit() {
context.referencedTypesScope.pop();
}

private static ImmutableSet<String> getExportedClasses(CodeReferenceMap rootSet) {
return rootSet == null ? ImmutableSet.of() : rootSet.getReferencedClasses();
}

private static ImmutableSet<String> getExportedMethods(CodeReferenceMap rootSet) {
Set<String> exportedMethods = new HashSet<>();
if (rootSet != null) {
rootSet
.getReferencedMethods()
.cellSet()
.forEach(
cell ->
cell.getValue()
.forEach(
signature ->
exportedMethods.add(
getQualifiedMethodName(
cell.getRowKey(), cell.getColumnKey(), signature))));
}
return ImmutableSet.copyOf(exportedMethods);
}

private static List<TypeInfo> markClasses(
List<TypeInfo> types, List<String> typeMap, Set<String> markedClasses) {
if (markedClasses.isEmpty()) {
return types;
}
List<TypeInfo> markedTypes = new ArrayList<>();
Set<String> nextMarkedClasses = new HashSet<>();
for (TypeInfo type : types) {
TypeInfo.Builder typeBuilder = type.toBuilder();
if (markedClasses.contains(typeMap.get(type.getTypeId()))) {
// Set type as exported.
typeBuilder.setExported(true).clearMember();
for (MemberInfo member : type.getMemberList()) {
// Set each method of the type as exported.
typeBuilder.addMember(member.toBuilder().setExported(true).build());
}
// Add inner types that need to be exported to a list.
nextMarkedClasses.addAll(
type.getInnerTypesList().stream().map(typeMap::get).collect(toImmutableList()));
}
// Add type to list of marked types
markedTypes.add(typeBuilder.build());
}
// Recursively call with the list of exported classes as the inner classes that need to be
// exported.
// This is because we do not know if the inner class has inner classes (alternative, while
// loop).
return markClasses(markedTypes, typeMap, nextMarkedClasses);
}

private static TypeInfo markMethodsOfType(
TypeInfo type, List<String> typeMap, Set<String> markedMethods) {
TypeInfo.Builder typeBuilder = type.toBuilder().clearMember();
for (MemberInfo member : type.getMemberList()) {
MemberInfo.Builder memberBuilder = member.toBuilder();
if (markedMethods.contains(
getQualifiedMethodName(typeMap.get(type.getTypeId()), member.getName()))) {
memberBuilder.setExported(true);
}
typeBuilder.addMember(memberBuilder.build());
}
return typeBuilder.build();
}

private static ImmutableList<TypeInfo> markMethods(
List<TypeInfo> types, List<String> typeMap, Set<String> markedMethods) {
List<TypeInfo> typesWithMarkedMembers = new ArrayList<>();
for (TypeInfo type : types) {
typesWithMarkedMembers.add(markMethodsOfType(type, typeMap, markedMethods));
}
return ImmutableList.copyOf(typesWithMarkedMembers);
}

/*
* Uses exported classes given to 'mark' summary information as 'live' for TypeGraphAnalyzer.
*/
private static LibraryInfo markEntryClasses(
LibraryInfo summary,
ImmutableSet<String> exportedClasses,
ImmutableSet<String> exportedMethods) {
return summary.toBuilder()
.clearType()
.addAllType(
markClasses(
markMethods(summary.getTypeList(), summary.getTypeMapList(), exportedMethods),
summary.getTypeMapList(),
exportedClasses))
.build();
}

static LibraryInfo mark(LibraryInfo summary, File roots) {
CodeReferenceMap rootSet = ProGuardUsageParser.parseDeadCodeFile(roots);
return markEntryClasses(
summary, getExportedClasses(rootSet), UsedCodeMarker.getExportedMethods(rootSet));
}

static final class Context {
// Map of type names to unique integer.
private int typeCount;
Expand Down Expand Up @@ -653,20 +757,13 @@ static final class Context {
private final Deque<Set<Integer>> clinitReferencedTypesScope = new ArrayDeque<>();

Context(CodeReferenceMap rootSet) {
exportedMethods = getExportedMethods(rootSet);
exportedClasses = getExportedClasses(rootSet);
}

Context() {
exportedMethods = new HashSet<>();
rootSet
.getReferencedMethods()
.cellSet()
.forEach(
cell -> {
String type = cell.getRowKey();
String name = cell.getColumnKey();
cell.getValue()
.forEach(
signature ->
exportedMethods.add(getQualifiedMethodName(type, name, signature)));
});
exportedClasses = rootSet.getReferencedClasses();
exportedClasses = ImmutableSet.copyOf(new HashSet<>());
}

LibraryInfo getLibraryInfo() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ message TypeInfo {
repeated int32 implements_type = 3;
repeated MemberInfo member = 4;
bool exported = 5;
repeated int32 inner_types = 6;
}

message MemberInfo {
Expand Down

0 comments on commit 2867504

Please sign in to comment.