• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2019 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.protolog.tool
18 
19 import com.android.internal.protolog.common.LogLevel
20 import com.github.javaparser.ast.CompilationUnit
21 import com.github.javaparser.ast.expr.Expression
22 import com.github.javaparser.ast.expr.FieldAccessExpr
23 import com.github.javaparser.ast.expr.MethodCallExpr
24 import com.github.javaparser.ast.expr.NameExpr
25 
26 /**
27  * Helper class for visiting all ProtoLog calls.
28  * For every valid call in the given {@code CompilationUnit} a {@code ProtoLogCallVisitor} callback
29  * is executed.
30  */
31 class ProtoLogCallProcessorImpl(
32     private val protoLogClassName: String,
33     private val protoLogGroupClassName: String,
34     private val groupMap: Map<String, LogGroup>
35 ) : ProtoLogCallProcessor {
36     private val protoLogSimpleClassName = protoLogClassName.substringAfterLast('.')
37     private val protoLogGroupSimpleClassName = protoLogGroupClassName.substringAfterLast('.')
38 
39     private fun getLogGroupName(
40         expr: Expression,
41         isClassImported: Boolean,
42         staticImports: Set<String>,
43         fileName: String
44     ): String {
45         val context = ParsingContext(fileName, expr)
46         return when (expr) {
47             is NameExpr -> when {
48                 expr.nameAsString in staticImports -> expr.nameAsString
49                 else ->
50                     throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup: $expr",
51                             context)
52             }
53             is FieldAccessExpr -> when {
54                 expr.scope.toString() == protoLogGroupClassName || isClassImported &&
55                         expr.scope.toString() == protoLogGroupSimpleClassName -> expr.nameAsString
56                 else ->
57                     throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup: $expr",
58                             context)
59             }
60             else -> throw InvalidProtoLogCallException("Invalid group argument " +
61                     "- must be ProtoLogGroup enum member reference: $expr", context)
62         }
63     }
64 
65     private fun isProtoCall(
66         call: MethodCallExpr,
67         isLogClassImported: Boolean,
68         staticLogImports: Collection<String>
69     ): Boolean {
70         return call.scope.isPresent && call.scope.get().toString() == protoLogClassName ||
71                 isLogClassImported && call.scope.isPresent &&
72                 call.scope.get().toString() == protoLogSimpleClassName ||
73                 !call.scope.isPresent && staticLogImports.contains(call.name.toString())
74     }
75 
76     fun process(code: CompilationUnit, logCallVisitor: ProtoLogCallVisitor?, fileName: String):
77             Collection<CodeProcessingException> {
78         return process(code, logCallVisitor, null, fileName)
79     }
80 
81     override fun process(
82         code: CompilationUnit,
83         logCallVisitor: ProtoLogCallVisitor?,
84         otherCallVisitor: MethodCallVisitor?,
85         fileName: String
86     ): Collection<CodeProcessingException> {
87         CodeUtils.checkWildcardStaticImported(code, protoLogClassName, fileName)
88         CodeUtils.checkWildcardStaticImported(code, protoLogGroupClassName, fileName)
89 
90         val isLogClassImported = CodeUtils.isClassImportedOrSamePackage(code, protoLogClassName)
91         val staticLogImports = CodeUtils.staticallyImportedMethods(code, protoLogClassName)
92         val isGroupClassImported = CodeUtils.isClassImportedOrSamePackage(code,
93                 protoLogGroupClassName)
94         val staticGroupImports = CodeUtils.staticallyImportedMethods(code, protoLogGroupClassName)
95 
96         val errors = mutableListOf<CodeProcessingException>()
97         code.findAll(MethodCallExpr::class.java)
98                 .filter { call ->
99                     isProtoCall(call, isLogClassImported, staticLogImports)
100                 }.forEach { call ->
101                     val context = ParsingContext(fileName, call)
102 
103                     try {
104                         val logMethods = LogLevel.entries.map { it.shortCode }
105                         if (logMethods.contains(call.name.id)) {
106                             // Process a log call
107                             if (call.arguments.size < 2) {
108                                 errors.add(InvalidProtoLogCallException(
109                                     "Method signature does not match " +
110                                             "any ProtoLog method: $call", context
111                                 ))
112                                 return@forEach
113                             }
114 
115                             val messageString = CodeUtils.concatMultilineString(
116                                 call.getArgument(1),
117                                 context
118                             )
119                             val groupNameArg = call.getArgument(0)
120                             val groupName =
121                                 getLogGroupName(
122                                     groupNameArg, isGroupClassImported,
123                                     staticGroupImports, fileName
124                                 )
125                             if (groupName !in groupMap) {
126                                 errors.add(InvalidProtoLogCallException(
127                                     "Unknown group argument " +
128                                             "- not a ProtoLogGroup enum member: $call", context
129                                 ))
130                                 return@forEach
131                             }
132 
133                             logCallVisitor?.processCall(
134                                 call, messageString, getLevelForMethodName(
135                                     call.name.toString(), call, context
136                                 ), groupMap.getValue(groupName),
137                                 context.lineNumber
138                             )
139                         } else if (call.name.id == "init") {
140                             // No processing
141                         } else {
142                             // Process non-log message calls
143                             otherCallVisitor?.processCall(call)
144                         }
145                     } catch (e: Throwable) {
146                         errors.add(InvalidProtoLogCallException(
147                             "Error processing log call: $call",
148                             context, e
149                         ))
150                     }
151                 }
152         return errors
153     }
154 
155     private fun getLevelForMethodName(
156         name: String,
157         node: MethodCallExpr,
158         context: ParsingContext
159     ): LogLevel = when (name) {
160             "d" -> LogLevel.DEBUG
161             "v" -> LogLevel.VERBOSE
162             "i" -> LogLevel.INFO
163             "w" -> LogLevel.WARN
164             "e" -> LogLevel.ERROR
165             "wtf" -> LogLevel.WTF
166             else ->
167                 throw InvalidProtoLogCallException("Unknown log level $name in $node", context)
168         }
169 }
170