1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.signature.cts.api; 18 19 import static android.signature.cts.CurrentApi.API_FILE_DIRECTORY; 20 21 import android.os.Bundle; 22 import android.signature.cts.DexApiDocumentParser; 23 import android.signature.cts.DexField; 24 import android.signature.cts.DexMember; 25 import android.signature.cts.DexMemberChecker; 26 import android.signature.cts.DexMethod; 27 import android.signature.cts.FailureType; 28 29 import java.io.File; 30 import java.util.function.Predicate; 31 import java.util.stream.Stream; 32 33 /** 34 * Checks that it is not possible to access hidden APIs. 35 */ 36 public class HiddenApiTest extends AbstractApiTest { 37 38 private String[] hiddenapiFiles; 39 private String[] hiddenapiTestFlags; 40 41 @Override initializeFromArgs(Bundle instrumentationArgs)42 protected void initializeFromArgs(Bundle instrumentationArgs) throws Exception { 43 hiddenapiFiles = getCommaSeparatedList(instrumentationArgs, "hiddenapi-files"); 44 hiddenapiTestFlags = getCommaSeparatedList(instrumentationArgs, "hiddenapi-test-flags"); 45 } 46 47 @Override setUp()48 protected void setUp() throws Exception { 49 super.setUp(); 50 DexMemberChecker.init(); 51 } 52 53 // We have four methods to split up the load, keeping individual test runs small. 54 55 private final static Predicate<DexMember> METHOD_FILTER = 56 dexMember -> (dexMember instanceof DexMethod); 57 58 private final static Predicate<DexMember> FIELD_FILTER = 59 dexMember -> (dexMember instanceof DexField); 60 testSignatureMethodsThroughReflection()61 public void testSignatureMethodsThroughReflection() { 62 doTestSignature(METHOD_FILTER,/* reflection= */ true, /* jni= */ false); 63 } 64 testSignatureMethodsThroughJni()65 public void testSignatureMethodsThroughJni() { 66 doTestSignature(METHOD_FILTER, /* reflection= */ false, /* jni= */ true); 67 } 68 testSignatureFieldsThroughReflection()69 public void testSignatureFieldsThroughReflection() { 70 doTestSignature(FIELD_FILTER, /* reflection= */ true, /* jni= */ false); 71 } 72 testSignatureFieldsThroughJni()73 public void testSignatureFieldsThroughJni() { 74 doTestSignature(FIELD_FILTER, /* reflection= */ false, /* jni= */ true); 75 } 76 77 /** 78 * Tests that the device does not expose APIs on the provided lists of 79 * DEX signatures. 80 * 81 * Will check the entire API, and then report the complete list of failures 82 */ doTestSignature(Predicate<DexMember> memberFilter, boolean reflection, boolean jni)83 private void doTestSignature(Predicate<DexMember> memberFilter, boolean reflection, 84 boolean jni) { 85 runWithTestResultObserver(resultObserver -> { 86 DexMemberChecker.Observer observer = new DexMemberChecker.Observer() { 87 @Override 88 public void classAccessible(boolean accessible, DexMember member) { 89 } 90 91 @Override 92 public void fieldAccessibleViaReflection(boolean accessible, DexField field) { 93 if (accessible) { 94 synchronized(resultObserver) { 95 resultObserver.notifyFailure( 96 FailureType.EXTRA_FIELD, 97 field.toString(), 98 "Hidden field accessible through reflection"); 99 } 100 } 101 } 102 103 @Override 104 public void fieldAccessibleViaJni(boolean accessible, DexField field) { 105 if (accessible) { 106 synchronized(resultObserver) { 107 resultObserver.notifyFailure( 108 FailureType.EXTRA_FIELD, 109 field.toString(), 110 "Hidden field accessible through JNI"); 111 } 112 } 113 } 114 115 @Override 116 public void methodAccessibleViaReflection(boolean accessible, DexMethod method) { 117 if (accessible) { 118 synchronized(resultObserver) { 119 resultObserver.notifyFailure( 120 FailureType.EXTRA_METHOD, 121 method.toString(), 122 "Hidden method accessible through reflection"); 123 } 124 } 125 } 126 127 @Override 128 public void methodAccessibleViaJni(boolean accessible, DexMethod method) { 129 if (accessible) { 130 synchronized(resultObserver) { 131 resultObserver.notifyFailure( 132 FailureType.EXTRA_METHOD, 133 method.toString(), 134 "Hidden method accessible through JNI"); 135 } 136 } 137 } 138 }; 139 parseDexApiFilesAsStream(hiddenapiFiles) 140 .filter(memberFilter) 141 .forEach(dexMember -> { 142 if (shouldTestMember(dexMember)) { 143 DexMemberChecker.checkSingleMember(dexMember, reflection, jni, 144 observer); 145 } 146 }); 147 }); 148 } 149 parseDexApiFilesAsStream(String[] apiFiles)150 private Stream<DexMember> parseDexApiFilesAsStream(String[] apiFiles) { 151 DexApiDocumentParser dexApiDocumentParser = new DexApiDocumentParser(); 152 // To allow parallelization with a DexMember output type, we need two 153 // pipes. 154 Stream<Stream<DexMember>> inputsAsStreams = Stream.of(apiFiles).parallel() 155 .map(name -> new File(API_FILE_DIRECTORY + "/" + name)) 156 .flatMap(file -> readFileOptimized(file)) 157 .map(obj -> dexApiDocumentParser.parseAsStream(obj)); 158 // The flatMap inherently serializes the pipe. The number of inputs is 159 // still small here, so reduce by concatenating (note the caveats of 160 // concats). 161 return inputsAsStreams.reduce(null, (prev, stream) -> { 162 if (prev == null) { 163 return stream; 164 } 165 return Stream.concat(prev, stream); 166 }); 167 } 168 169 private boolean shouldTestMember(DexMember member) { 170 for (String testFlag : hiddenapiTestFlags) { 171 for (String memberFlag : member.getHiddenapiFlags()) { 172 if (testFlag.equals(memberFlag)) { 173 return true; 174 } 175 } 176 } 177 return false; 178 } 179 180 } 181