1818package compiler
1919
2020import com.certora.collect.*
21+ import config.Config
2122import log.Logger
2223import log.LoggerTypes
2324import report.TreeViewLocation
2425import utils.Range
2526import utils.*
2627import java.io.File
2728import java.io.Serializable
29+ import kotlin.io.path.Path
2830
2931private val logger = Logger (LoggerTypes .COMMON )
3032
@@ -130,6 +132,8 @@ data class SourceSegment(
130132 )
131133 }
132134
135+
136+
133137 fun resolveFromContext (context : SourceContext , identifier : SourceIdentifier ): SourceSegment ? {
134138 val sourceFile = identifier.resolve(context) ? : return null
135139 val relativeFile = identifier.relativeSourceFile(context)?.toString() ? : return null
@@ -138,6 +142,44 @@ data class SourceSegment(
138142 }
139143}
140144
145+ /* *
146+ * Takes a [range] and approximates the [compiler.SourceSegment] for it.
147+ *
148+ * Note, that this function is used in Solana where a Range doesn't have a precise end
149+ * and the returned [compiler.SourceSegment]'s content will just start at
150+ * [range]'s start and contain the remaining content of the start line.
151+ */
152+ fun Range.Range?.toHeuristicSourceSegment (): SourceSegment ? {
153+ val sourceFile = this ?.file ? : return null
154+ val (allBytes, lineStarts) = try {
155+ val sourceFilePath = Path (sourceFile)
156+ val s = if (sourceFilePath.isAbsolute){
157+ CertoraFileCache .tryResolvePathInCargoHome(sourceFile)?.toString() ? : sourceFile
158+ } else {
159+ Config .prependSourcesDir(sourceFile)
160+ }
161+ CertoraFileCache .byteContent(s) to CertoraFileCache .lineStarts(s)
162+ } catch (e: CertoraFileCacheException ) {
163+ logger.warn(e.cause) { " Had error when reading from source file $sourceFile " }
164+ return null
165+ }
166+ val startIndex =
167+ lineStarts.lineNumberStartOffset(start.line.toInt())?.let { it + start.charByteOffset.toInt() }
168+ ? : return null
169+ // This is a heuristic, we don't have the full range, but only the start. As end range we define the offset
170+ // at the end of the start line, or if not existent, the remaining content of the file. The result
171+ // is that content of the returned SourceSegment will be the start line until the end of this line.
172+ val endIndex = lineStarts.lineNumberStartOffset(start.line.toInt() + 1 ) ? : allBytes.size
173+
174+ if (startIndex >= allBytes.size) {
175+ logger.warn { " Trying to source contents starting at ($startIndex ) which is out of the bounds of the file content of ($sourceFile )" }
176+ return null
177+ }
178+ return SourceSegment (
179+ this ,
180+ content = String (allBytes, startIndex, endIndex - startIndex).removeNonPrintableChars()
181+ )
182+ }
141183
142184@Suppress(" ForbiddenMethodCall" ) // for Regex
143185private fun String.removeNonPrintableChars () = replace(Regex ( " [^\\ P{C}\\ s]" ), " " ).let {
0 commit comments