summaryrefslogtreecommitdiff
path: root/java/hiddenapi.go
blob: 2cd025e4331c580779c6409dc01e989ddaeb5906 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
// Copyright 2019 Google Inc. 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 java

import (
	"strings"

	"github.com/google/blueprint"

	"android/soong/android"
)

var hiddenAPIGenerateCSVRule = pctx.AndroidStaticRule("hiddenAPIGenerateCSV", blueprint.RuleParams{
	Command:     "${config.Class2NonSdkList} --stub-api-flags ${stubAPIFlags} $in $outFlag $out",
	CommandDeps: []string{"${config.Class2NonSdkList}"},
}, "outFlag", "stubAPIFlags")

type hiddenAPI struct {
	// The path to the dex jar that is in the boot class path. If this is nil then the associated
	// module is not a boot jar, but could be one of the <x>-hiddenapi modules that provide additional
	// annotations for the <x> boot dex jar but which do not actually provide a boot dex jar
	// themselves.
	bootDexJarPath android.Path

	// The path to the CSV file that contains mappings from Java signature to various flags derived
	// from annotations in the source, e.g. whether it is public or the sdk version above which it
	// can no longer be used.
	//
	// It is created by the Class2NonSdkList tool which processes the .class files in the class
	// implementation jar looking for UnsupportedAppUsage and CovariantReturnType annotations. The
	// tool also consumes the hiddenAPISingletonPathsStruct.stubFlags file in order to perform
	// consistency checks on the information in the annotations and to filter out bridge methods
	// that are already part of the public API.
	flagsCSVPath android.Path

	// The path to the CSV file that contains mappings from Java signature to the value of properties
	// specified on UnsupportedAppUsage annotations in the source.
	//
	// Like the flagsCSVPath file this is also created by the Class2NonSdkList in the same way.
	// Although the two files could potentially be created in a single invocation of the
	// Class2NonSdkList at the moment they are created using their own invocation, with the behavior
	// being determined by the property that is used.
	metadataCSVPath android.Path

	// The path to the CSV file that contains mappings from Java signature to source location
	// information.
	//
	// It is created by the merge_csv tool which processes the class implementation jar, extracting
	// all the files ending in .uau (which are CSV files) and merges them together. The .uau files are
	// created by the unsupported app usage annotation processor during compilation of the class
	// implementation jar.
	indexCSVPath android.Path
}

func (h *hiddenAPI) flagsCSV() android.Path {
	return h.flagsCSVPath
}

func (h *hiddenAPI) metadataCSV() android.Path {
	return h.metadataCSVPath
}

func (h *hiddenAPI) bootDexJar() android.Path {
	return h.bootDexJarPath
}

func (h *hiddenAPI) indexCSV() android.Path {
	return h.indexCSVPath
}

type hiddenAPIIntf interface {
	bootDexJar() android.Path
	flagsCSV() android.Path
	indexCSV() android.Path
	metadataCSV() android.Path
}

var _ hiddenAPIIntf = (*hiddenAPI)(nil)

func (h *hiddenAPI) hiddenAPI(ctx android.ModuleContext, name string, primary bool, dexJar android.ModuleOutPath,
	implementationJar android.Path, uncompressDex bool) android.ModuleOutPath {
	if !ctx.Config().IsEnvTrue("UNSAFE_DISABLE_HIDDENAPI_FLAGS") {

		// Modules whose names are of the format <x>-hiddenapi provide hiddenapi information
		// for the boot jar module <x>. Otherwise, the module provides information for itself.
		// Either way extract the name of the boot jar module.
		bootJarName := strings.TrimSuffix(name, "-hiddenapi")

		// If this module is on the boot jars list (or providing information for a module
		// on the list) then extract the hiddenapi information from it, and if necessary
		// encode that information in the generated dex file.
		//
		// It is important that hiddenapi information is only gathered for/from modules on
		// that are actually on the boot jars list because the runtime only enforces access
		// to the hidden API for the bootclassloader. If information is gathered for modules
		// not on the list then that will cause failures in the CtsHiddenApiBlacklist...
		// tests.
		if inList(bootJarName, ctx.Config().BootJars()) {
			// Create ninja rules to generate various CSV files needed by hiddenapi and store the paths
			// in the hiddenAPI structure.
			h.hiddenAPIGenerateCSV(ctx, implementationJar)

			// If this module is actually on the boot jars list and not providing
			// hiddenapi information for a module on the boot jars list then encode
			// the gathered information in the generated dex file.
			if name == bootJarName {
				hiddenAPIJar := android.PathForModuleOut(ctx, "hiddenapi", name+".jar")

				// More than one library with the same classes can be encoded but only one can
				// be added to the global set of flags, otherwise it will result in duplicate
				// classes which is an error. Therefore, only add the dex jar of one of them
				// to the global set of flags.
				if primary {
					h.bootDexJarPath = dexJar
				}
				hiddenAPIEncodeDex(ctx, hiddenAPIJar, dexJar, uncompressDex)
				dexJar = hiddenAPIJar
			}
		}
	}

	return dexJar
}

func (h *hiddenAPI) hiddenAPIGenerateCSV(ctx android.ModuleContext, classesJar android.Path) {
	stubFlagsCSV := hiddenAPISingletonPaths(ctx).stubFlags

	flagsCSV := android.PathForModuleOut(ctx, "hiddenapi", "flags.csv")
	ctx.Build(pctx, android.BuildParams{
		Rule:        hiddenAPIGenerateCSVRule,
		Description: "hiddenapi flags",
		Input:       classesJar,
		Output:      flagsCSV,
		Implicit:    stubFlagsCSV,
		Args: map[string]string{
			"outFlag":      "--write-flags-csv",
			"stubAPIFlags": stubFlagsCSV.String(),
		},
	})
	h.flagsCSVPath = flagsCSV

	metadataCSV := android.PathForModuleOut(ctx, "hiddenapi", "metadata.csv")
	ctx.Build(pctx, android.BuildParams{
		Rule:        hiddenAPIGenerateCSVRule,
		Description: "hiddenapi metadata",
		Input:       classesJar,
		Output:      metadataCSV,
		Implicit:    stubFlagsCSV,
		Args: map[string]string{
			"outFlag":      "--write-metadata-csv",
			"stubAPIFlags": stubFlagsCSV.String(),
		},
	})
	h.metadataCSVPath = metadataCSV

	indexCSV := android.PathForModuleOut(ctx, "hiddenapi", "index.csv")
	rule := android.NewRuleBuilder(pctx, ctx)
	rule.Command().
		BuiltTool("merge_csv").
		FlagWithInput("--zip_input=", classesJar).
		FlagWithOutput("--output=", indexCSV)
	rule.Build("merged-hiddenapi-index", "Merged Hidden API index")
	h.indexCSVPath = indexCSV
}

var hiddenAPIEncodeDexRule = pctx.AndroidStaticRule("hiddenAPIEncodeDex", blueprint.RuleParams{
	Command: `rm -rf $tmpDir && mkdir -p $tmpDir && mkdir $tmpDir/dex-input && mkdir $tmpDir/dex-output &&
		unzip -qoDD $in 'classes*.dex' -d $tmpDir/dex-input &&
		for INPUT_DEX in $$(find $tmpDir/dex-input -maxdepth 1 -name 'classes*.dex' | sort); do
		  echo "--input-dex=$${INPUT_DEX}";
		  echo "--output-dex=$tmpDir/dex-output/$$(basename $${INPUT_DEX})";
		done | xargs ${config.HiddenAPI} encode --api-flags=$flagsCsv $hiddenapiFlags &&
		${config.SoongZipCmd} $soongZipFlags -o $tmpDir/dex.jar -C $tmpDir/dex-output -f "$tmpDir/dex-output/classes*.dex" &&
		${config.MergeZipsCmd} -D -zipToNotStrip $tmpDir/dex.jar -stripFile "classes*.dex" -stripFile "**/*.uau" $out $tmpDir/dex.jar $in`,
	CommandDeps: []string{
		"${config.HiddenAPI}",
		"${config.SoongZipCmd}",
		"${config.MergeZipsCmd}",
	},
}, "flagsCsv", "hiddenapiFlags", "tmpDir", "soongZipFlags")

func hiddenAPIEncodeDex(ctx android.ModuleContext, output android.WritablePath, dexInput android.Path,
	uncompressDex bool) {

	flagsCSV := hiddenAPISingletonPaths(ctx).flags

	// The encode dex rule requires unzipping and rezipping the classes.dex files, ensure that if it was uncompressed
	// in the input it stays uncompressed in the output.
	soongZipFlags := ""
	hiddenapiFlags := ""
	tmpOutput := output
	tmpDir := android.PathForModuleOut(ctx, "hiddenapi", "dex")
	if uncompressDex {
		soongZipFlags = "-L 0"
		tmpOutput = android.PathForModuleOut(ctx, "hiddenapi", "unaligned", "unaligned.jar")
		tmpDir = android.PathForModuleOut(ctx, "hiddenapi", "unaligned")
	}

	enforceHiddenApiFlagsToAllMembers := true
	// If frameworks/base doesn't exist we must be building with the 'master-art' manifest.
	// Disable assertion that all methods/fields have hidden API flags assigned.
	if !ctx.Config().FrameworksBaseDirExists(ctx) {
		enforceHiddenApiFlagsToAllMembers = false
	}
	// b/149353192: when a module is instrumented, jacoco adds synthetic members
	// $jacocoData and $jacocoInit. Since they don't exist when building the hidden API flags,
	// don't complain when we don't find hidden API flags for the synthetic members.
	if j, ok := ctx.Module().(interface {
		shouldInstrument(android.BaseModuleContext) bool
	}); ok && j.shouldInstrument(ctx) {
		enforceHiddenApiFlagsToAllMembers = false
	}

	if !enforceHiddenApiFlagsToAllMembers {
		hiddenapiFlags = "--no-force-assign-all"
	}

	ctx.Build(pctx, android.BuildParams{
		Rule:        hiddenAPIEncodeDexRule,
		Description: "hiddenapi encode dex",
		Input:       dexInput,
		Output:      tmpOutput,
		Implicit:    flagsCSV,
		Args: map[string]string{
			"flagsCsv":       flagsCSV.String(),
			"tmpDir":         tmpDir.String(),
			"soongZipFlags":  soongZipFlags,
			"hiddenapiFlags": hiddenapiFlags,
		},
	})

	if uncompressDex {
		TransformZipAlign(ctx, output, tmpOutput)
	}
}