• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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