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 com.android.tools.metalava.model.psi
18 
19 import com.android.SdkConstants.DOT_CLASS
20 import com.android.tools.lint.detector.api.ConstantEvaluator
21 import com.android.tools.metalava.model.Codebase
22 import com.android.tools.metalava.model.Item
23 import com.android.tools.metalava.model.canonicalizeFloatingPointString
24 import com.android.tools.metalava.model.javaEscapeString
25 import com.android.tools.metalava.reporter.Issues
26 import com.android.tools.metalava.reporter.Reporter
27 import com.intellij.psi.PsiAnnotation
28 import com.intellij.psi.PsiAnnotationMemberValue
29 import com.intellij.psi.PsiArrayInitializerMemberValue
30 import com.intellij.psi.PsiClass
31 import com.intellij.psi.PsiClassObjectAccessExpression
32 import com.intellij.psi.PsiElement
33 import com.intellij.psi.PsiField
34 import com.intellij.psi.PsiLiteral
35 import com.intellij.psi.PsiReference
36 import com.intellij.psi.PsiTypeCastExpression
37 import com.intellij.psi.PsiVariable
38 import java.util.function.Predicate
39 import org.jetbrains.kotlin.name.ClassId
40 import org.jetbrains.kotlin.name.Name
41 import org.jetbrains.uast.UAnnotation
42 import org.jetbrains.uast.UBinaryExpression
43 import org.jetbrains.uast.UBinaryExpressionWithType
44 import org.jetbrains.uast.UBlockExpression
45 import org.jetbrains.uast.UCallExpression
46 import org.jetbrains.uast.UElement
47 import org.jetbrains.uast.UExpression
48 import org.jetbrains.uast.ULambdaExpression
49 import org.jetbrains.uast.ULiteralExpression
50 import org.jetbrains.uast.UReferenceExpression
51 import org.jetbrains.uast.UUnaryExpression
52 import org.jetbrains.uast.util.isArrayInitializer
53 import org.jetbrains.uast.util.isConstructorCall
54 import org.jetbrains.uast.util.isTypeCast
55 
56 /** Utility methods */
57 class CodePrinter(
58     private val codebase: Codebase,
59     private val reporter: Reporter,
60     /**
61      * Whether we should inline the values of fields, e.g. instead of "Integer.MAX_VALUE" we'd emit
62      * "0x7fffffff"
63      */
64     private val inlineFieldValues: Boolean = true,
65     /** Whether we should inline constants when possible, e.g. instead of "2*20+2" we'd emit "42" */
66     private val inlineConstants: Boolean = true,
67     /**
68      * Whether we should drop unknown AST nodes instead of inserting the corresponding source text
69      * strings
70      */
71     private val skipUnknown: Boolean = false,
72     /** An optional filter to use to determine if we should emit a reference to an item */
73     private val filterReference: Predicate<Item>? = null
74 ) {
warningnull75     private fun warning(message: String, psiElement: PsiElement? = null) {
76         reporter.report(Issues.INTERNAL_ERROR, psiElement, message)
77     }
78 
warningnull79     private fun warning(message: String, uElement: UElement) {
80         warning(message, uElement.sourcePsi ?: uElement.javaPsi)
81     }
82 
83     /** Given an annotation member value, returns the corresponding Java source expression */
toSourceExpressionnull84     internal fun toSourceExpression(value: PsiAnnotationMemberValue, owner: Item): String {
85         val sb = StringBuilder()
86         appendSourceExpression(value, sb, owner)
87         return sb.toString()
88     }
89 
appendSourceExpressionnull90     private fun appendSourceExpression(
91         value: PsiAnnotationMemberValue,
92         sb: StringBuilder,
93         owner: Item
94     ): Boolean {
95         if (value is PsiReference) {
96             val resolved = value.resolve()
97             if (resolved is PsiField) {
98                 sb.append(resolved.containingClass?.qualifiedName).append('.').append(resolved.name)
99                 return true
100             }
101         } else if (value is PsiLiteral) {
102             return appendSourceLiteral(value.value, sb, owner)
103         } else if (value is PsiClassObjectAccessExpression) {
104             sb.append(value.operand.type.canonicalText).append(DOT_CLASS)
105             return true
106         } else if (value is PsiArrayInitializerMemberValue) {
107             sb.append('{')
108             var first = true
109             val initialLength = sb.length
110             for (e in value.initializers) {
111                 val length = sb.length
112                 if (first) {
113                     first = false
114                 } else {
115                     sb.append(", ")
116                 }
117                 val appended = appendSourceExpression(e, sb, owner)
118                 if (!appended) {
119                     // trunk off comma if it bailed for some reason (e.g. constant
120                     // filtered out by API etc)
121                     sb.setLength(length)
122                     if (length == initialLength) {
123                         first = true
124                     }
125                 }
126             }
127             sb.append('}')
128             return true
129         } else if (value is PsiAnnotation) {
130             sb.append('@').append(value.qualifiedName)
131             return true
132         } else {
133             if (value is PsiTypeCastExpression) {
134                 val type = value.castType?.type
135                 val operand = value.operand
136                 if (type != null && operand is PsiAnnotationMemberValue) {
137                     sb.append('(')
138                     sb.append(type.canonicalText)
139                     sb.append(')')
140                     return appendSourceExpression(operand, sb, owner)
141                 }
142             }
143             val constant = ConstantEvaluator.evaluate(null, value)
144             if (constant != null) {
145                 return appendSourceLiteral(constant, sb, owner)
146             }
147         }
148         reporter.report(Issues.INTERNAL_ERROR, owner, "Unexpected annotation default value $value")
149         return false
150     }
151 
appendSourceLiteralnull152     private fun appendSourceLiteral(v: Any?, sb: StringBuilder, owner: Item): Boolean {
153         if (v == null) {
154             sb.append("null")
155             return true
156         }
157         when (v) {
158             is Int,
159             is Boolean,
160             is Byte,
161             is Short -> {
162                 sb.append(v.toString())
163                 return true
164             }
165             is Long -> {
166                 sb.append(v.toString()).append('L')
167                 return true
168             }
169             is String -> {
170                 sb.append('"').append(javaEscapeString(v)).append('"')
171                 return true
172             }
173             is Float -> {
174                 return when {
175                     v == Float.POSITIVE_INFINITY -> {
176                         // This convention (displaying fractions) is inherited from doclava
177                         sb.append("(1.0f/0.0f)")
178                         true
179                     }
180                     v == Float.NEGATIVE_INFINITY -> {
181                         sb.append("(-1.0f/0.0f)")
182                         true
183                     }
184                     java.lang.Float.isNaN(v) -> {
185                         sb.append("(0.0f/0.0f)")
186                         true
187                     }
188                     else -> {
189                         sb.append(canonicalizeFloatingPointString(v.toString()) + "f")
190                         true
191                     }
192                 }
193             }
194             is Double -> {
195                 return when {
196                     v == Double.POSITIVE_INFINITY -> {
197                         // This convention (displaying fractions) is inherited from doclava
198                         sb.append("(1.0/0.0)")
199                         true
200                     }
201                     v == Double.NEGATIVE_INFINITY -> {
202                         sb.append("(-1.0/0.0)")
203                         true
204                     }
205                     java.lang.Double.isNaN(v) -> {
206                         sb.append("(0.0/0.0)")
207                         true
208                     }
209                     else -> {
210                         sb.append(canonicalizeFloatingPointString(v.toString()))
211                         true
212                     }
213                 }
214             }
215             is Char -> {
216                 sb.append('\'').append(javaEscapeString(v.toString())).append('\'')
217                 return true
218             }
219             else -> {
220                 reporter.report(Issues.INTERNAL_ERROR, owner, "Unexpected literal value $v")
221             }
222         }
223 
224         return false
225     }
226 
toSourceStringnull227     fun toSourceString(value: UExpression?): String? {
228         value ?: return null
229         val sb = StringBuilder()
230         return if (appendExpression(sb, value)) {
231             sb.toString()
232         } else {
233             null
234         }
235     }
236 
appendExpressionnull237     private fun appendExpression(sb: StringBuilder, expression: UExpression): Boolean {
238         if (expression.isArrayInitializer()) {
239             val call = expression as UCallExpression
240             val initializers = call.valueArguments
241             sb.append('{')
242             var first = true
243             val initialLength = sb.length
244             for (e in initializers) {
245                 val length = sb.length
246                 if (first) {
247                     first = false
248                 } else {
249                     sb.append(", ")
250                 }
251                 val appended = appendExpression(sb, e)
252                 if (!appended) {
253                     // truncate trailing comma if it bailed for some reason (e.g. constant
254                     // filtered out by API etc)
255                     sb.setLength(length)
256                     if (length == initialLength) {
257                         first = true
258                     }
259                 }
260             }
261             sb.append('}')
262             return sb.length != 2
263         } else if (expression is UReferenceExpression) {
264             when (val resolved = expression.resolve()) {
265                 is PsiField -> {
266                     @Suppress("UnnecessaryVariable") val field = resolved
267                     if (!inlineFieldValues) {
268                         val value = field.computeConstantValue()
269                         if (appendLiteralValue(sb, value)) {
270                             return true
271                         }
272                     }
273 
274                     val declaringClass = field.containingClass
275                     if (declaringClass == null) {
276                         warning("No containing class found for " + field.name, field)
277                         return false
278                     }
279                     val qualifiedName = declaringClass.qualifiedName
280                     val fieldName = field.name
281 
282                     if (qualifiedName != null) {
283                         if (filterReference != null) {
284                             val cls = codebase.findClass(qualifiedName)
285                             val fld = cls?.findField(fieldName, true)
286                             if (fld == null || !filterReference.test(fld)) {
287                                 // This field is not visible: remove from typedef
288                                 if (fld != null) {
289                                     reporter.report(
290                                         Issues.HIDDEN_TYPEDEF_CONSTANT,
291                                         fld,
292                                         "Typedef class references hidden field $fld: removed from typedef metadata"
293                                     )
294                                 }
295                                 return false
296                             }
297                         }
298                         sb.append(qualifiedName)
299                         sb.append('.')
300                         sb.append(fieldName)
301                         return true
302                     }
303                     return if (skipUnknown) {
304                         false
305                     } else {
306                         sb.append(expression.asSourceString())
307                         true
308                     }
309                 }
310                 is PsiVariable -> {
311                     sb.append(resolved.name)
312                     return true
313                 }
314                 else -> {
315                     if (skipUnknown) {
316                         warning("Unexpected reference to $expression", expression)
317                         return false
318                     }
319                     sb.append(expression.asSourceString())
320                     return true
321                 }
322             }
323         } else if (expression is ULiteralExpression) {
324             val literalValue = expression.value
325             if (appendLiteralValue(sb, literalValue)) {
326                 return true
327             }
328         } else if (expression is UAnnotation) {
329             sb.append('@').append(expression.qualifiedName)
330             return true
331         } else if (expression is UBinaryExpressionWithType) {
332             if ((expression).isTypeCast()) {
333                 sb.append('(').append(expression.type.canonicalText).append(')')
334                 val operand = expression.operand
335                 return appendExpression(sb, operand)
336             }
337             return false
338         } else if (expression is UBinaryExpression) {
339             if (inlineConstants) {
340                 val constant = expression.evaluate()
341                 if (constant != null) {
342                     sb.append(constantToSource(constant))
343                     return true
344                 }
345             }
346 
347             if (appendExpression(sb, expression.leftOperand)) {
348                 sb.append(' ').append(expression.operator.text).append(' ')
349                 if (appendExpression(sb, expression.rightOperand)) {
350                     return true
351                 }
352             }
353         } else if (expression is UUnaryExpression) {
354             sb.append(expression.operator.text)
355             if (appendExpression(sb, expression.operand)) {
356                 return true
357             }
358         } else if (expression is ULambdaExpression) {
359             sb.append("{ ")
360             val valueParameters = expression.valueParameters
361             if (valueParameters.isNotEmpty()) {
362                 var first = true
363                 for (parameter in valueParameters) {
364                     if (first) {
365                         first = false
366                     } else {
367                         sb.append(", ")
368                     }
369                     sb.append(parameter.name)
370                 }
371                 sb.append(" -> ")
372             }
373             val body = expression.body
374 
375             if (body is UBlockExpression) {
376                 var first = true
377                 for (e in body.expressions) {
378                     if (first) {
379                         first = false
380                     } else {
381                         sb.append("; ")
382                     }
383                     if (!appendExpression(sb, e)) {
384                         return false
385                     }
386                 }
387 
388                 // Special case: Turn empty lambda {  } into {}
389                 if (sb.length > 2) {
390                     sb.append(' ')
391                 } else {
392                     sb.setLength(1)
393                 }
394                 sb.append('}')
395                 return true
396             } else {
397                 if (appendExpression(sb, body)) {
398                     sb.append(" }")
399                     return true
400                 }
401             }
402         } else if (expression is UBlockExpression) {
403             sb.append('{')
404             var first = true
405             for (e in expression.expressions) {
406                 if (first) {
407                     first = false
408                 } else {
409                     sb.append("; ")
410                 }
411                 if (!appendExpression(sb, e)) {
412                     return false
413                 }
414             }
415             sb.append('}')
416             return true
417         } else if (expression.isConstructorCall()) {
418             val call = expression as UCallExpression
419             val resolved = call.classReference?.resolve()
420             if (resolved is PsiClass) {
421                 sb.append(resolved.qualifiedName)
422             } else {
423                 sb.append(call.classReference?.resolvedName)
424             }
425             sb.append('(')
426             var first = true
427             for (arg in call.valueArguments) {
428                 if (first) {
429                     first = false
430                 } else {
431                     sb.append(", ")
432                 }
433                 if (!appendExpression(sb, arg)) {
434                     return false
435                 }
436             }
437             sb.append(')')
438             return true
439         } else {
440             sb.append(expression.asSourceString())
441             return true
442         }
443 
444         // For example, binary expressions like 3 + 4
445         val literalValue = ConstantEvaluator.evaluate(null, expression)
446         if (literalValue != null) {
447             if (appendLiteralValue(sb, literalValue)) {
448                 return true
449             }
450         }
451 
452         warning(
453             "Unexpected annotation expression of type ${expression.javaClass} and is $expression",
454             expression
455         )
456 
457         return false
458     }
459 
460     companion object {
appendLiteralValuenull461         private fun appendLiteralValue(sb: StringBuilder, literalValue: Any?): Boolean {
462             if (literalValue == null) {
463                 sb.append("null")
464                 return true
465             } else if (literalValue is Number || literalValue is Boolean) {
466                 sb.append(literalValue.toString())
467                 return true
468             } else if (literalValue is String || literalValue is Char) {
469                 sb.append('"')
470                 sb.append(javaEscapeString(literalValue.toString()))
471                 sb.append('"')
472                 return true
473             }
474             return false
475         }
476 
constantToSourcenull477         fun constantToSource(value: Any?): String {
478             if (value == null) {
479                 return "null"
480             }
481 
482             when (value) {
483                 is Int -> {
484                     return value.toString()
485                 }
486                 is String -> {
487                     return "\"${javaEscapeString(value)}\""
488                 }
489                 is Long -> {
490                     return value.toString() + "L"
491                 }
492                 is Boolean -> {
493                     return value.toString()
494                 }
495                 is Byte -> {
496                     return value.toString()
497                 }
498                 is Short -> {
499                     return value.toString()
500                 }
501                 is Float -> {
502                     return when {
503                         value == Float.POSITIVE_INFINITY -> "(1.0f/0.0f)"
504                         value == Float.NEGATIVE_INFINITY -> "(-1.0f/0.0f)"
505                         java.lang.Float.isNaN(value) -> "(0.0f/0.0f)"
506                         else -> {
507                             canonicalizeFloatingPointString(value.toString()) + "f"
508                         }
509                     }
510                 }
511                 is Double -> {
512                     return when {
513                         value == Double.POSITIVE_INFINITY -> "(1.0/0.0)"
514                         value == Double.NEGATIVE_INFINITY -> "(-1.0/0.0)"
515                         java.lang.Double.isNaN(value) -> "(0.0/0.0)"
516                         else -> {
517                             canonicalizeFloatingPointString(value.toString())
518                         }
519                     }
520                 }
521                 is Char -> {
522                     return String.format("'%s'", javaEscapeString(value.toString()))
523                 }
524                 is Pair<*, *> -> {
525                     val first = value.first
526                     val second = value.second
527                     if (first is ClassId) {
528                         val qualifiedName =
529                             first.packageFqName.asString() +
530                                 "." +
531                                 first.relativeClassName.asString()
532                         return if (second is Name) {
533                             qualifiedName + "." + second.asString()
534                         } else {
535                             qualifiedName
536                         }
537                     }
538                 }
539             }
540 
541             return value.toString()
542         }
543 
constantToExpressionnull544         internal fun constantToExpression(constant: Any?): String? {
545             return when (constant) {
546                 is Int -> "0x${Integer.toHexString(constant)}"
547                 is String -> "\"${javaEscapeString(constant)}\""
548                 is Long -> "${constant}L"
549                 is Boolean -> constant.toString()
550                 is Byte -> Integer.toHexString(constant.toInt())
551                 is Short -> Integer.toHexString(constant.toInt())
552                 is Float -> {
553                     when {
554                         constant == Float.POSITIVE_INFINITY -> "Float.POSITIVE_INFINITY"
555                         constant == Float.NEGATIVE_INFINITY -> "Float.NEGATIVE_INFINITY"
556                         java.lang.Float.isNaN(constant) -> "Float.NaN"
557                         else -> {
558                             "${canonicalizeFloatingPointString(constant.toString())}F"
559                         }
560                     }
561                 }
562                 is Double -> {
563                     when {
564                         constant == Double.POSITIVE_INFINITY -> "Double.POSITIVE_INFINITY"
565                         constant == Double.NEGATIVE_INFINITY -> "Double.NEGATIVE_INFINITY"
566                         java.lang.Double.isNaN(constant) -> "Double.NaN"
567                         else -> {
568                             canonicalizeFloatingPointString(constant.toString())
569                         }
570                     }
571                 }
572                 is Char -> {
573                     "'${javaEscapeString(constant.toString())}'"
574                 }
575                 else -> {
576                     null
577                 }
578             }
579         }
580     }
581 }
582