From 33916d23666cef12e302ad10b36d978b69e3df71 Mon Sep 17 00:00:00 2001 From: aleembhd Date: Wed, 11 Mar 2026 11:17:22 +0530 Subject: [PATCH 1/4] feat(slicing): emit parameter annotations as separate ANNOTATION slices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of adding an annotations field to ParamDef (which changes the schema), parameter annotations are now emitted as separate ObjectUsageSlice entries with label=ANNOTATION using the existing CallDef type. Removed annotations field from ParamDef (no schema change) Changed computeUsageSlice to return List instead of Option For each MethodParameterIn annotation, emit a new ANNOTATION entry with the param name in name and annotation in resolvedMethod This preserves backward compatibility — existing tools ignore the extra entries, while new consumers can read ANNOTATION entries to discover @RequestBody, @PathVariable, @RequestParam, @RequestHeader etc. --- .../appthreat/atom/slicing/UsageSlicing.scala | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/main/scala/io/appthreat/atom/slicing/UsageSlicing.scala b/src/main/scala/io/appthreat/atom/slicing/UsageSlicing.scala index 3835e10..4512af4 100644 --- a/src/main/scala/io/appthreat/atom/slicing/UsageSlicing.scala +++ b/src/main/scala/io/appthreat/atom/slicing/UsageSlicing.scala @@ -62,7 +62,7 @@ object UsageSlicing: .map { decl => exec.submit(() => computeUsageSlice(atom, decl, typeMap, config.excludeOperatorCalls)) } - .flatMap(f => Try(f.get(5, TimeUnit.SECONDS)).toOption.flatten) + .flatMap(f => Try(f.get(5, TimeUnit.SECONDS)).getOrElse(List.empty)) .groupBy { case (method, _) => method } .view .filterKeys(m => !isExcludedMethod(m)) @@ -78,29 +78,47 @@ object UsageSlicing: tgt: Declaration, typeMap: Map[String, String], excludeOperatorCalls: Boolean - ): Option[(Method, ObjectUsageSlice)] = + ): List[(Method, ObjectUsageSlice)] = val defNode = getDefNode(tgt) val (invokedCalls, argToCalls) = partitionInvolvementInCalls(atom, tgt, typeMap, excludeOperatorCalls) (tgt, defNode) match case (local: Local, Some(genCall: Call)) => - Some(local.method.head -> ObjectUsageSlice( + List(local.method.head -> ObjectUsageSlice( targetObj = DefComponent.fromNode(local, genCall, typeMap), definedBy = Some(DefComponent.fromNode(genCall, null, typeMap)), invokedCalls = invokedCalls, argToCalls = argToCalls )) case (param: MethodParameterIn, _) if !param.name.matches("(this|self)") => - Some(param.method -> ObjectUsageSlice( + val paramSlice = param.method -> ObjectUsageSlice( targetObj = DefComponent.fromNode(param, null, typeMap), definedBy = Some(DefComponent.fromNode(param, null, typeMap)), invokedCalls = invokedCalls, argToCalls = argToCalls - )) + ) + val annotationSlices = param.annotation.map { ann => + val annDef = CallDef( + param.name, + ann.fullName, + Option(ann.code).filter(_.nonEmpty).orElse(Option(ann.fullName)), + Some(param.method.isExternal), + ann.lineNumber.map(_.intValue()), + ann.columnNumber.map(_.intValue()), + label = ann.label + ) + param.method -> ObjectUsageSlice( + targetObj = annDef, + definedBy = Some(annDef), + invokedCalls = List.empty, + argToCalls = List.empty + ) + }.toList + paramSlice :: annotationSlices case (m: Method, _) => - createMethodObjectUsageSlice(m, invokedCalls, argToCalls, typeMap) - case _ => None + createMethodObjectUsageSlice(m, invokedCalls, argToCalls, typeMap).toList + case _ => List.empty end computeUsageSlice private def createMethodObjectUsageSlice( From f48c11f4ecc418400d7bb82984b5e9df8a91d930 Mon Sep 17 00:00:00 2001 From: aleembhd Date: Thu, 12 Mar 2026 00:57:40 +0530 Subject: [PATCH 2/4] refactor: retain Option in computeUsageSlice, collect annotations separately instead of List. Param annotation slices are now collected in a separate paramAnnotationSlices function, combined in computeSlices. --- .../appthreat/atom/slicing/UsageSlicing.scala | 65 +++++++++++-------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/src/main/scala/io/appthreat/atom/slicing/UsageSlicing.scala b/src/main/scala/io/appthreat/atom/slicing/UsageSlicing.scala index 4512af4..1e4e90e 100644 --- a/src/main/scala/io/appthreat/atom/slicing/UsageSlicing.scala +++ b/src/main/scala/io/appthreat/atom/slicing/UsageSlicing.scala @@ -49,7 +49,7 @@ object UsageSlicing: val language = atom.metaData.language.headOption val root = atom.metaData.root.headOption - declarations + val filteredDecls = declarations .to(LazyList) .filterNot(_.name == "*") .filter(d => @@ -59,10 +59,18 @@ object UsageSlicing: config.excludeOperatorCalls ) ) + + val mainSlices = filteredDecls .map { decl => exec.submit(() => computeUsageSlice(atom, decl, typeMap, config.excludeOperatorCalls)) } - .flatMap(f => Try(f.get(5, TimeUnit.SECONDS)).getOrElse(List.empty)) + .flatMap(f => Try(f.get(5, TimeUnit.SECONDS)).toOption.flatten) + + val annotationSlices = filteredDecls.collect { + case param: MethodParameterIn if !param.name.matches("(this|self)") => param + }.flatMap(param => paramAnnotationSlices(param, typeMap)) + + (mainSlices ++ annotationSlices) .groupBy { case (method, _) => method } .view .filterKeys(m => !isExcludedMethod(m)) @@ -78,49 +86,54 @@ object UsageSlicing: tgt: Declaration, typeMap: Map[String, String], excludeOperatorCalls: Boolean - ): List[(Method, ObjectUsageSlice)] = + ): Option[(Method, ObjectUsageSlice)] = val defNode = getDefNode(tgt) val (invokedCalls, argToCalls) = partitionInvolvementInCalls(atom, tgt, typeMap, excludeOperatorCalls) (tgt, defNode) match case (local: Local, Some(genCall: Call)) => - List(local.method.head -> ObjectUsageSlice( + Some(local.method.head -> ObjectUsageSlice( targetObj = DefComponent.fromNode(local, genCall, typeMap), definedBy = Some(DefComponent.fromNode(genCall, null, typeMap)), invokedCalls = invokedCalls, argToCalls = argToCalls )) case (param: MethodParameterIn, _) if !param.name.matches("(this|self)") => - val paramSlice = param.method -> ObjectUsageSlice( + Some(param.method -> ObjectUsageSlice( targetObj = DefComponent.fromNode(param, null, typeMap), definedBy = Some(DefComponent.fromNode(param, null, typeMap)), invokedCalls = invokedCalls, argToCalls = argToCalls - ) - val annotationSlices = param.annotation.map { ann => - val annDef = CallDef( - param.name, - ann.fullName, - Option(ann.code).filter(_.nonEmpty).orElse(Option(ann.fullName)), - Some(param.method.isExternal), - ann.lineNumber.map(_.intValue()), - ann.columnNumber.map(_.intValue()), - label = ann.label - ) - param.method -> ObjectUsageSlice( - targetObj = annDef, - definedBy = Some(annDef), - invokedCalls = List.empty, - argToCalls = List.empty - ) - }.toList - paramSlice :: annotationSlices + )) case (m: Method, _) => - createMethodObjectUsageSlice(m, invokedCalls, argToCalls, typeMap).toList - case _ => List.empty + createMethodObjectUsageSlice(m, invokedCalls, argToCalls, typeMap) + case _ => None end computeUsageSlice + private def paramAnnotationSlices( + param: MethodParameterIn, + typeMap: Map[String, String] + ): List[(Method, ObjectUsageSlice)] = + param.annotation.map { ann => + val annDef = CallDef( + param.name, + ann.fullName, + Option(ann.code).filter(_.nonEmpty).orElse(Option(ann.fullName)), + Some(param.method.isExternal), + ann.lineNumber.map(_.intValue()), + ann.columnNumber.map(_.intValue()), + label = ann.label + ) + param.method -> ObjectUsageSlice( + targetObj = annDef, + definedBy = Some(annDef), + invokedCalls = List.empty, + argToCalls = List.empty + ) + }.toList + end paramAnnotationSlices + private def createMethodObjectUsageSlice( m: Method, invokedCalls: List[ObservedCall], From 9f8241e540168466ee1db56ac638f7a31d800549 Mon Sep 17 00:00:00 2001 From: aleembhd Date: Thu, 12 Mar 2026 02:47:29 +0530 Subject: [PATCH 3/4] feat: track annotation self-invocation in paramAnnotationSlices invokedCall --- .../io/appthreat/atom/slicing/UsageSlicing.scala | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/scala/io/appthreat/atom/slicing/UsageSlicing.scala b/src/main/scala/io/appthreat/atom/slicing/UsageSlicing.scala index 1e4e90e..edaf463 100644 --- a/src/main/scala/io/appthreat/atom/slicing/UsageSlicing.scala +++ b/src/main/scala/io/appthreat/atom/slicing/UsageSlicing.scala @@ -125,10 +125,19 @@ object UsageSlicing: ann.columnNumber.map(_.intValue()), label = ann.label ) + val annCall = ObservedCall( + if ann.fullName.nonEmpty then ann.fullName else ann.name, + Option(ann.code).filter(_.nonEmpty).orElse(Option(ann.fullName)), + List.empty, + "", + Some(param.method.isExternal), + ann.lineNumber.map(_.intValue()), + ann.columnNumber.map(_.intValue()) + ) param.method -> ObjectUsageSlice( targetObj = annDef, definedBy = Some(annDef), - invokedCalls = List.empty, + invokedCalls = List(annCall), argToCalls = List.empty ) }.toList From b045f4e680a33f619b8b9a1504a4c8ad61685319 Mon Sep 17 00:00:00 2001 From: abdul-levo Date: Tue, 17 Mar 2026 20:08:36 +0530 Subject: [PATCH 4/4] style: apply scalafmt formatting to UsageSlicing.scala --- src/main/scala/io/appthreat/atom/slicing/UsageSlicing.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/io/appthreat/atom/slicing/UsageSlicing.scala b/src/main/scala/io/appthreat/atom/slicing/UsageSlicing.scala index edaf463..cd4de40 100644 --- a/src/main/scala/io/appthreat/atom/slicing/UsageSlicing.scala +++ b/src/main/scala/io/appthreat/atom/slicing/UsageSlicing.scala @@ -115,7 +115,7 @@ object UsageSlicing: param: MethodParameterIn, typeMap: Map[String, String] ): List[(Method, ObjectUsageSlice)] = - param.annotation.map { ann => + param.annotation.map { ann => val annDef = CallDef( param.name, ann.fullName, @@ -140,7 +140,7 @@ object UsageSlicing: invokedCalls = List(annCall), argToCalls = List.empty ) - }.toList + }.toList end paramAnnotationSlices private def createMethodObjectUsageSlice(