1// Copyright 2019 Google Inc. All rights reserved.
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 java
16
17import (
18	"strings"
19
20	"github.com/google/blueprint"
21
22	"android/soong/android"
23)
24
25var hiddenAPIGenerateCSVRule = pctx.AndroidStaticRule("hiddenAPIGenerateCSV", blueprint.RuleParams{
26	Command:     "${config.Class2Greylist} --stub-api-flags ${stubAPIFlags} $in $outFlag $out",
27	CommandDeps: []string{"${config.Class2Greylist}"},
28}, "outFlag", "stubAPIFlags")
29
30type hiddenAPI struct {
31	bootDexJarPath  android.Path
32	flagsCSVPath    android.Path
33	indexCSVPath    android.Path
34	metadataCSVPath android.Path
35}
36
37func (h *hiddenAPI) flagsCSV() android.Path {
38	return h.flagsCSVPath
39}
40
41func (h *hiddenAPI) metadataCSV() android.Path {
42	return h.metadataCSVPath
43}
44
45func (h *hiddenAPI) bootDexJar() android.Path {
46	return h.bootDexJarPath
47}
48
49func (h *hiddenAPI) indexCSV() android.Path {
50	return h.indexCSVPath
51}
52
53type hiddenAPIIntf interface {
54	bootDexJar() android.Path
55	flagsCSV() android.Path
56	indexCSV() android.Path
57	metadataCSV() android.Path
58}
59
60var _ hiddenAPIIntf = (*hiddenAPI)(nil)
61
62func (h *hiddenAPI) hiddenAPI(ctx android.ModuleContext, name string, primary bool, dexJar android.ModuleOutPath,
63	implementationJar android.Path, uncompressDex bool) android.ModuleOutPath {
64	if !ctx.Config().IsEnvTrue("UNSAFE_DISABLE_HIDDENAPI_FLAGS") {
65
66		// Modules whose names are of the format <x>-hiddenapi provide hiddenapi information
67		// for the boot jar module <x>. Otherwise, the module provides information for itself.
68		// Either way extract the name of the boot jar module.
69		bootJarName := strings.TrimSuffix(name, "-hiddenapi")
70
71		// If this module is on the boot jars list (or providing information for a module
72		// on the list) then extract the hiddenapi information from it, and if necessary
73		// encode that information in the generated dex file.
74		//
75		// It is important that hiddenapi information is only gathered for/from modules on
76		// that are actually on the boot jars list because the runtime only enforces access
77		// to the hidden API for the bootclassloader. If information is gathered for modules
78		// not on the list then that will cause failures in the CtsHiddenApiBlacklist...
79		// tests.
80		if inList(bootJarName, ctx.Config().BootJars()) {
81			// Derive the greylist from classes jar.
82			flagsCSV := android.PathForModuleOut(ctx, "hiddenapi", "flags.csv")
83			metadataCSV := android.PathForModuleOut(ctx, "hiddenapi", "metadata.csv")
84			indexCSV := android.PathForModuleOut(ctx, "hiddenapi", "index.csv")
85			h.hiddenAPIGenerateCSV(ctx, flagsCSV, metadataCSV, indexCSV, implementationJar)
86
87			// If this module is actually on the boot jars list and not providing
88			// hiddenapi information for a module on the boot jars list then encode
89			// the gathered information in the generated dex file.
90			if name == bootJarName {
91				hiddenAPIJar := android.PathForModuleOut(ctx, "hiddenapi", name+".jar")
92
93				// More than one library with the same classes can be encoded but only one can
94				// be added to the global set of flags, otherwise it will result in duplicate
95				// classes which is an error. Therefore, only add the dex jar of one of them
96				// to the global set of flags.
97				if primary {
98					h.bootDexJarPath = dexJar
99				}
100				hiddenAPIEncodeDex(ctx, hiddenAPIJar, dexJar, uncompressDex)
101				dexJar = hiddenAPIJar
102			}
103		}
104	}
105
106	return dexJar
107}
108
109func (h *hiddenAPI) hiddenAPIGenerateCSV(ctx android.ModuleContext, flagsCSV, metadataCSV, indexCSV android.WritablePath, classesJar android.Path) {
110	stubFlagsCSV := hiddenAPISingletonPaths(ctx).stubFlags
111
112	ctx.Build(pctx, android.BuildParams{
113		Rule:        hiddenAPIGenerateCSVRule,
114		Description: "hiddenapi flags",
115		Input:       classesJar,
116		Output:      flagsCSV,
117		Implicit:    stubFlagsCSV,
118		Args: map[string]string{
119			"outFlag":      "--write-flags-csv",
120			"stubAPIFlags": stubFlagsCSV.String(),
121		},
122	})
123	h.flagsCSVPath = flagsCSV
124
125	ctx.Build(pctx, android.BuildParams{
126		Rule:        hiddenAPIGenerateCSVRule,
127		Description: "hiddenapi metadata",
128		Input:       classesJar,
129		Output:      metadataCSV,
130		Implicit:    stubFlagsCSV,
131		Args: map[string]string{
132			"outFlag":      "--write-metadata-csv",
133			"stubAPIFlags": stubFlagsCSV.String(),
134		},
135	})
136	h.metadataCSVPath = metadataCSV
137
138	rule := android.NewRuleBuilder()
139	rule.Command().
140		BuiltTool(ctx, "merge_csv").
141		FlagWithInput("--zip_input=", classesJar).
142		FlagWithOutput("--output=", indexCSV)
143	rule.Build(pctx, ctx, "merged-hiddenapi-index", "Merged Hidden API index")
144	h.indexCSVPath = indexCSV
145}
146
147var hiddenAPIEncodeDexRule = pctx.AndroidStaticRule("hiddenAPIEncodeDex", blueprint.RuleParams{
148	Command: `rm -rf $tmpDir && mkdir -p $tmpDir && mkdir $tmpDir/dex-input && mkdir $tmpDir/dex-output &&
149		unzip -qoDD $in 'classes*.dex' -d $tmpDir/dex-input &&
150		for INPUT_DEX in $$(find $tmpDir/dex-input -maxdepth 1 -name 'classes*.dex' | sort); do
151		  echo "--input-dex=$${INPUT_DEX}";
152		  echo "--output-dex=$tmpDir/dex-output/$$(basename $${INPUT_DEX})";
153		done | xargs ${config.HiddenAPI} encode --api-flags=$flagsCsv $hiddenapiFlags &&
154		${config.SoongZipCmd} $soongZipFlags -o $tmpDir/dex.jar -C $tmpDir/dex-output -f "$tmpDir/dex-output/classes*.dex" &&
155		${config.MergeZipsCmd} -D -zipToNotStrip $tmpDir/dex.jar -stripFile "classes*.dex" -stripFile "**/*.uau" $out $tmpDir/dex.jar $in`,
156	CommandDeps: []string{
157		"${config.HiddenAPI}",
158		"${config.SoongZipCmd}",
159		"${config.MergeZipsCmd}",
160	},
161}, "flagsCsv", "hiddenapiFlags", "tmpDir", "soongZipFlags")
162
163func hiddenAPIEncodeDex(ctx android.ModuleContext, output android.WritablePath, dexInput android.Path,
164	uncompressDex bool) {
165
166	flagsCSV := hiddenAPISingletonPaths(ctx).flags
167
168	// The encode dex rule requires unzipping and rezipping the classes.dex files, ensure that if it was uncompressed
169	// in the input it stays uncompressed in the output.
170	soongZipFlags := ""
171	hiddenapiFlags := ""
172	tmpOutput := output
173	tmpDir := android.PathForModuleOut(ctx, "hiddenapi", "dex")
174	if uncompressDex {
175		soongZipFlags = "-L 0"
176		tmpOutput = android.PathForModuleOut(ctx, "hiddenapi", "unaligned", "unaligned.jar")
177		tmpDir = android.PathForModuleOut(ctx, "hiddenapi", "unaligned")
178	}
179
180	enforceHiddenApiFlagsToAllMembers := true
181	// If frameworks/base doesn't exist we must be building with the 'master-art' manifest.
182	// Disable assertion that all methods/fields have hidden API flags assigned.
183	if !ctx.Config().FrameworksBaseDirExists(ctx) {
184		enforceHiddenApiFlagsToAllMembers = false
185	}
186	// b/149353192: when a module is instrumented, jacoco adds synthetic members
187	// $jacocoData and $jacocoInit. Since they don't exist when building the hidden API flags,
188	// don't complain when we don't find hidden API flags for the synthetic members.
189	if j, ok := ctx.Module().(interface {
190		shouldInstrument(android.BaseModuleContext) bool
191	}); ok && j.shouldInstrument(ctx) {
192		enforceHiddenApiFlagsToAllMembers = false
193	}
194
195	if !enforceHiddenApiFlagsToAllMembers {
196		hiddenapiFlags = "--no-force-assign-all"
197	}
198
199	ctx.Build(pctx, android.BuildParams{
200		Rule:        hiddenAPIEncodeDexRule,
201		Description: "hiddenapi encode dex",
202		Input:       dexInput,
203		Output:      tmpOutput,
204		Implicit:    flagsCSV,
205		Args: map[string]string{
206			"flagsCsv":       flagsCSV.String(),
207			"tmpDir":         tmpDir.String(),
208			"soongZipFlags":  soongZipFlags,
209			"hiddenapiFlags": hiddenapiFlags,
210		},
211	})
212
213	if uncompressDex {
214		TransformZipAlign(ctx, output, tmpOutput)
215	}
216}
217