1 /*
2  * Copyright (C) 2024 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 com.android.cts.apimap;
18 
19 import com.android.cts.apicommon.ApiClass;
20 import com.android.cts.apicommon.ApiCoverage;
21 import com.android.cts.apicommon.ApiPackage;
22 import com.android.cts.ctsprofiles.ClassProfile;
23 import com.android.cts.ctsprofiles.MethodProfile;
24 import com.android.cts.ctsprofiles.ModuleProfile;
25 
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Set;
31 import java.util.Stack;
32 
33 
34 /** A class for collecting APIs covered by a CTS module. */
35 public class CallGraphManager {
36 
37     // Cache API calls for each CTS method.
38     private final Map<String, CoveredApiCache> mCoveredApiCaches = new HashMap<>();
39 
40     private final ModuleProfile mModule;
41 
42     /** Cache the covered API list for a CTS method. */
43     static class CoveredApiCache {
44 
45         private final Map<String, MethodProfile> mApiConstructors = new HashMap<>();
46 
47         private final Map<String, MethodProfile> mApiMethods = new HashMap<>();
48 
mergeApis(CoveredApiCache apis)49         public void mergeApis(CoveredApiCache apis) {
50             addMethods(apis.getApiMethods());
51             addConstructors(apis.getApiConstructors());
52         }
53 
addMethods(Map<String, MethodProfile> methods)54         public void addMethods(Map<String, MethodProfile> methods) {
55             mApiMethods.putAll(methods);
56         }
57 
addConstructors(Map<String, MethodProfile> constructors)58         public void addConstructors(Map<String, MethodProfile> constructors) {
59             mApiConstructors.putAll(constructors);
60         }
61 
getApiConstructors()62         public Map<String, MethodProfile> getApiConstructors() {
63             return mApiConstructors;
64         }
65 
getApiMethods()66         public Map<String, MethodProfile> getApiMethods() {
67             return mApiMethods;
68         }
69     }
70 
CallGraphManager(ModuleProfile moduleProfile)71     public CallGraphManager(ModuleProfile moduleProfile) {
72         mModule = moduleProfile;
73     }
74 
getModule()75     public ModuleProfile getModule() {
76         return mModule;
77     }
78 
79     /**
80      * Maps detected APIs to CTS test methods and marks them as covered by this CTS module.
81      */
resolveCoveredApis(ApiCoverage apiCoverage)82     public void resolveCoveredApis(ApiCoverage apiCoverage) {
83         for (ClassProfile classProfile : mModule.getClasses()) {
84             if (!classProfile.isNonAbstractTestClass()) {
85                 continue;
86             }
87             for (MethodProfile methodProfile : classProfile.getTestMethods().values()) {
88                 TarJan tarjan = new TarJan(methodProfile, mCoveredApiCaches.keySet());
89                 Stack<Integer> stack = new Stack<>();
90                 Set<Integer> visitedComponents = new HashSet<>();
91                 String methodSignature = methodProfile.getMethodSignatureWithClass();
92                 stack.add(tarjan.getComponentID(methodSignature));
93                 visitedComponents.add(tarjan.getComponentID(methodSignature));
94                 // Do recursive search for API calls.
95                 resolveMethodCoveredApis(stack, visitedComponents, tarjan);
96                 markCoveredApisWithCaller(methodSignature, apiCoverage);
97             }
98         }
99         markCoveredApisWithoutCaller(apiCoverage);
100     }
101 
102     /** Collects covered APIs for a test method via memorized search. */
resolveMethodCoveredApis( Stack<Integer> stack, Set<Integer> visitedComponents, TarJan tarjan)103     private CoveredApiCache resolveMethodCoveredApis(
104             Stack<Integer> stack,
105             Set<Integer> visitedComponents,
106             TarJan tarjan) {
107         List<MethodProfile> methods = tarjan.getComponent(stack.peek());
108         String methodSignature = methods.get(0).getMethodSignatureWithClass();
109         CoveredApiCache coveredApis = mCoveredApiCaches.get(methodSignature);
110         if (coveredApis != null) {
111             return coveredApis;
112         }
113         coveredApis = new CoveredApiCache();
114         for (MethodProfile method: methods) {
115             coveredApis.addMethods(method.getApiMethodCalls());
116             coveredApis.addConstructors(method.getApiConstructorCalls());
117         }
118         for (MethodProfile method: methods) {
119             for (MethodProfile methodCall : method.getCommonMethodCalls().values()) {
120                 String methodCallSignature = methodCall.getMethodSignatureWithClass();
121                 int componentID = tarjan.getComponentID(methodCallSignature);
122                 if (visitedComponents.contains(componentID)) {
123                     continue;
124                 }
125                 visitedComponents.add(componentID);
126                 stack.add(componentID);
127                 CoveredApiCache apis = resolveMethodCoveredApis(stack, visitedComponents, tarjan);
128                 coveredApis.mergeApis(apis);
129                 stack.pop();
130             }
131         }
132         for (MethodProfile method: methods) {
133             mCoveredApiCaches.put(method.getMethodSignatureWithClass(), coveredApis);
134         }
135         return coveredApis;
136     }
137 
138     /** Searches for the API class based on the given package name and class name. */
getApiClass( String packageName, String className, ApiCoverage apiCoverage)139     private ApiClass getApiClass(
140             String packageName, String className, ApiCoverage apiCoverage) {
141         ApiPackage apiPackage = apiCoverage.getPackage(packageName);
142         if (apiPackage != null) {
143             return apiPackage.getClass(className);
144         }
145         return null;
146     }
147 
148     /** Marks that APIs are covered by this CTS module. */
markCoveredApisWithoutCaller(ApiCoverage apiCoverage)149     private void markCoveredApisWithoutCaller(ApiCoverage apiCoverage) {
150         for (ClassProfile classProfile: mModule.getClasses()) {
151             if (!classProfile.isApiClass()) {
152                 continue;
153             }
154             ApiClass apiClass = getApiClass(
155                     classProfile.getPackageName(),
156                     classProfile.getClassName(),
157                     apiCoverage
158             );
159             if (apiClass == null) {
160                 continue;
161             }
162             for (MethodProfile methodProfile: classProfile.getMethods().values()) {
163                 if (methodProfile.getMethodName().equals("<init>")) {
164                     apiClass.markConstructorCovered(
165                             methodProfile.getMethodParams(),
166                             mModule.getModuleName()
167                     );
168                 } else {
169                     apiClass.markMethodCovered(
170                             methodProfile.getMethodName(),
171                             methodProfile.getMethodParams(),
172                             mModule.getModuleName()
173                     );
174                 }
175             }
176         }
177     }
178 
179     /** Marks that APIs are called by the given CTS test method. */
markCoveredApisWithCaller(String methodSignature, ApiCoverage apiCoverage)180     private void markCoveredApisWithCaller(String methodSignature, ApiCoverage apiCoverage) {
181         CoveredApiCache apiCache = mCoveredApiCaches.get(methodSignature);
182         if (apiCache == null) {
183             return;
184         }
185         for (MethodProfile apiConstructor : apiCache.getApiConstructors().values()) {
186             ApiClass apiClass = getApiClass(
187                     apiConstructor.getPackageName(),
188                     apiConstructor.getClassName(),
189                     apiCoverage
190             );
191             if (apiClass != null) {
192                 apiClass.markConstructorCoveredTest(
193                         apiConstructor.getMethodParams(),
194                         String.format("[%s] %s", mModule.getModuleName(), methodSignature)
195                 );
196             }
197         }
198         for (MethodProfile apiMethod : apiCache.getApiMethods().values()) {
199             ApiClass apiClass = getApiClass(
200                     apiMethod.getPackageName(), apiMethod.getClassName(), apiCoverage);
201             if (apiClass != null) {
202                 apiClass.markMethodCoveredTest(
203                         apiMethod.getMethodName(),
204                         apiMethod.getMethodParams(),
205                         String.format("[%s] %s", mModule.getModuleName(), methodSignature)
206                 );
207             }
208         }
209     }
210 }
211