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