diff options
| author | jsmall-nvidia <jsmall@nvidia.com> | 2023-04-17 15:09:37 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-04-17 15:09:37 -0400 |
| commit | 90a9f43573ec0777c2ae4fa20c8fdc51a4ae7b3a (patch) | |
| tree | 360750778be872a086674024a9ce5a68bf4e7cb3 | |
| parent | a3f622ace1bdef1f1a4150ec85d1328d1a589333 (diff) | |
Round trip source map (#2810)
* #include an absolute path didn't work - because paths were taken to always be relative.
* Make output of obfuscation locs work in a slang-module.
* Tidy up detection for writing serialized source locs.
* Support for .zip references.
Handling of obfuscated source maps read from containers.
A test to check obfuscated source map working on a module.
* When using obfuscation, always obfuscate locs instead of stripping them. We keep a source map, so we can still produce reasonable errors.
* Write out source locs if debug information is enabled.
* Check output without sourcemap.
* Small fixes.
* Small improvements around hash calculation for source map name.
* Disable test that fails on x86 gcc linux for now.
* Fix issues around obfuscated source map using lines rather than columns.
Fix some issues around encoding/decoding.
* Make column calculation of source locs take into account utf8/tabs.
Don't special case obfuscated source map for lookup for source loc.
* Support following multiple source maps.
* Small fixes/improvements around SourceMap lookup.
| -rw-r--r-- | source/compiler-core/slang-artifact-container-util.cpp | 48 | ||||
| -rw-r--r-- | source/compiler-core/slang-artifact-container-util.h | 3 | ||||
| -rw-r--r-- | source/compiler-core/slang-artifact-desc-util.cpp | 10 | ||||
| -rw-r--r-- | source/compiler-core/slang-json-source-map-util.cpp | 90 | ||||
| -rw-r--r-- | source/compiler-core/slang-json-source-map-util.h | 4 | ||||
| -rw-r--r-- | source/compiler-core/slang-source-loc.cpp | 177 | ||||
| -rw-r--r-- | source/compiler-core/slang-source-loc.h | 34 | ||||
| -rw-r--r-- | source/slang/slang-compiler.cpp | 20 | ||||
| -rw-r--r-- | source/slang/slang-ir-obfuscate-loc.cpp | 118 | ||||
| -rw-r--r-- | source/slang/slang-lower-to-ir.cpp | 4 | ||||
| -rw-r--r-- | source/slang/slang-options.cpp | 6 | ||||
| -rw-r--r-- | source/slang/slang.cpp | 89 | ||||
| -rw-r--r-- | tests/serialization/obfuscated-loc-module.slang | 21 | ||||
| -rw-r--r-- | tests/serialization/obfuscated-module-check-loc.slang | 29 | ||||
| -rw-r--r-- | tests/serialization/obfuscated-module-check-loc.slang.1.expected | 6 | ||||
| -rw-r--r-- | tests/serialization/obfuscated-module-check-loc.slang.3.expected | 6 | ||||
| -rw-r--r-- | tests/serialization/obfuscated-serialized-module-test.slang | 2 |
17 files changed, 558 insertions, 109 deletions
diff --git a/source/compiler-core/slang-artifact-container-util.cpp b/source/compiler-core/slang-artifact-container-util.cpp index 29e2e736e..6121df964 100644 --- a/source/compiler-core/slang-artifact-container-util.cpp +++ b/source/compiler-core/slang-artifact-container-util.cpp @@ -626,7 +626,6 @@ SlangResult ArtifactContainerReader::read(ISlangFileSystemExt* fileSystem, ComPt return _readArtifactDirectory(0, outArtifact); } - SlangResult ArtifactContainerReader::_readFile(Index fileIndex, ComPtr<IArtifact>& outArtifact) { outArtifact.setNull(); @@ -655,6 +654,19 @@ SlangResult ArtifactContainerReader::_readFile(Index fileIndex, ComPtr<IArtifact return SLANG_OK; } + // We don't have manifest, so for now well assume if the name ends in "-obfuscated" and it's a source map + // it's an obfuscated one + if (desc.kind == ArtifactKind::Json && + desc.payload == ArtifactPayload::SourceMap) + { + auto name = Path::getFileNameWithoutExt(entry.name); + + if (name.endsWith(toSlice("-obfuscated"))) + { + desc.style = ArtifactStyle::Obfuscated; + } + } + // I guess I can just make an artifact for this auto artifact = ArtifactUtil::createArtifact(desc); @@ -781,6 +793,40 @@ SlangResult ArtifactContainerReader::_readArtifactDirectory(Index directoryIndex return SLANG_OK; } +SlangResult ArtifactContainerUtil::readContainer(IArtifact* artifact, ComPtr<IArtifact>& outArtifact) +{ + auto desc = artifact->getDesc(); + + ComPtr<ISlangMutableFileSystem> fileSystem; + + switch (desc.kind) + { + case ArtifactKind::Zip: + { + SLANG_RETURN_ON_FAIL(ZipFileSystem::create(fileSystem)); + + ComPtr<ISlangBlob> blob; + SLANG_RETURN_ON_FAIL(artifact->loadBlob(ArtifactKeep::No, blob.writeRef())); + + // Load into the zip + + // Now write out to the output file + IArchiveFileSystem* archiveFileSystem = as<IArchiveFileSystem>(fileSystem); + SLANG_ASSERT(archiveFileSystem); + + SLANG_RETURN_ON_FAIL(archiveFileSystem->loadArchive(blob->getBufferPointer(), blob->getBufferSize())); + break; + } + default: + { + return SLANG_FAIL; + } + } + + SLANG_RETURN_ON_FAIL(readContainer(fileSystem, outArtifact)); + return SLANG_OK; +} + /* static */SlangResult ArtifactContainerUtil::readContainer(ISlangFileSystemExt* fileSystem, ComPtr<IArtifact>& outArtifact) { SLANG_UNUSED(outArtifact); diff --git a/source/compiler-core/slang-artifact-container-util.h b/source/compiler-core/slang-artifact-container-util.h index b3e591797..c6e836245 100644 --- a/source/compiler-core/slang-artifact-container-util.h +++ b/source/compiler-core/slang-artifact-container-util.h @@ -33,6 +33,9 @@ struct ArtifactContainerUtil static SlangResult writeContainer(IArtifact* artifact, const String& defaultFileName, ISlangMutableFileSystem* fileSystem); static SlangResult readContainer(ISlangFileSystemExt* fileSystem, ComPtr<IArtifact>& outArtifact); + + /// Read an artifact that represents a container as an artifact hierarchy + static SlangResult readContainer(IArtifact* artifact, ComPtr<IArtifact>& outArtifact); }; } // namespace Slang diff --git a/source/compiler-core/slang-artifact-desc-util.cpp b/source/compiler-core/slang-artifact-desc-util.cpp index fb1c2dbd2..67501782a 100644 --- a/source/compiler-core/slang-artifact-desc-util.cpp +++ b/source/compiler-core/slang-artifact-desc-util.cpp @@ -460,7 +460,15 @@ static const KindExtension g_cpuKindExts[] = /* static */bool ArtifactDescUtil::isLinkable(const ArtifactDesc& desc) { - if (isDerivedFrom(desc.kind, ArtifactKind::CompileBinary)) + // If is a container with compile results *assume* that result is linkable + if (isDerivedFrom(desc.kind, ArtifactKind::Container) && + isDerivedFrom(desc.payload, ArtifactPayload::CompileResults)) + { + return true; + } + + // if it's a compile binary or a container + if (isDerivedFrom(desc.kind, ArtifactKind::CompileBinary)) { if (isDerivedFrom(desc.payload, ArtifactPayload::KernelLike)) { diff --git a/source/compiler-core/slang-json-source-map-util.cpp b/source/compiler-core/slang-json-source-map-util.cpp index a5a454bd5..3929a3387 100644 --- a/source/compiler-core/slang-json-source-map-util.cpp +++ b/source/compiler-core/slang-json-source-map-util.cpp @@ -105,34 +105,31 @@ static SlangResult _decode(UnownedStringSlice& ioEncoded, Index& out) { Index v = 0; - Index shift = 0; const char* cur = ioEncoded.begin(); const char* end = ioEncoded.end(); - // Must have some chars - if (cur >= end) { - return SLANG_FAIL; - } - - for (; cur < end; ++cur) - { - const Index value = g_vlqDecodeTable[*cur]; - if (value < 0) + Index shift = 0; + Index decodeValue = 0; + do { - return SLANG_FAIL; - } + // Must have a char to decode + if (cur >= end) + { + return SLANG_FAIL; + } + + decodeValue = g_vlqDecodeTable[*cur++]; + if (decodeValue < 0) + { + return SLANG_FAIL; + } - v += (value & 0x1f) << shift; + v += (decodeValue & 0x1f) << shift; - // If the continuation bit is not set we are done - if (( value & 0x20) == 0) - { - ++cur; - break; + shift += 5; } - - shift += 5; + while (decodeValue & 0x20); } // Save out the remaining part @@ -158,20 +155,16 @@ void _encode(Index v, StringBuilder& out) do { - // Encode it - const auto nextV = v >> 5; - - // Encode 5 bits - char c = g_vlqEncodeTable[(v & 0x1f)]; - - // See what bits are remaining - v = (v >> 5); - - // Set the continuation bit's if there is more to encode - c |= v ? 0x20 : 0; + const Index nextV = v >> 5; + const Index encodeValue = (v & 0x1f) + (nextV ? 0x20 : 0); + // Encode 5 bits, plus continuation bit + char c = g_vlqEncodeTable[encodeValue]; + // Save the char *cur++ = c; + + v = nextV; } while (v); @@ -461,4 +454,39 @@ SlangResult JSONSourceMapUtil::encode(SourceMap* sourceMap, JSONContainer* conta return SLANG_OK; } +SlangResult JSONSourceMapUtil::read(ISlangBlob* blob, DiagnosticSink* parentSink, RefPtr<SourceMap>& outSourceMap) +{ + SourceManager sourceManager; + sourceManager.initialize(nullptr, nullptr); + DiagnosticSink sink(&sourceManager, nullptr); + + sink.setParentSink(parentSink); + + RefPtr<JSONContainer> container = new JSONContainer(&sourceManager); + + JSONValue rootValue; + { + // Now need to parse as JSON + SourceFile* sourceFile = sourceManager.createSourceFileWithBlob(PathInfo::makeUnknown(), blob); + SourceView* sourceView = sourceManager.createSourceView(sourceFile, nullptr, SourceLoc()); + + JSONLexer lexer; + lexer.init(sourceView, &sink); + + JSONBuilder builder(container); + + JSONParser parser; + SLANG_RETURN_ON_FAIL(parser.parse(&lexer, sourceView, &builder, &sink)); + + rootValue = builder.getRootValue(); + } + + RefPtr<SourceMap> sourceMap; + + SLANG_RETURN_ON_FAIL(decode(container, rootValue, &sink, sourceMap)); + + outSourceMap = sourceMap; + return SLANG_OK; +} + } // namespace Slang diff --git a/source/compiler-core/slang-json-source-map-util.h b/source/compiler-core/slang-json-source-map-util.h index 51b11b6cd..ba417dd7c 100644 --- a/source/compiler-core/slang-json-source-map-util.h +++ b/source/compiler-core/slang-json-source-map-util.h @@ -14,6 +14,10 @@ struct JSONSourceMapUtil /// Converts the source map contents into JSON static SlangResult encode(SourceMap* sourceMap, JSONContainer* container, DiagnosticSink* sink, JSONValue& outValue); + + /// Read the blob (encoded as JSON) as a source map. + /// Sink is optional, and can be passed as nullptr + static SlangResult read(ISlangBlob* blob, DiagnosticSink* sink, RefPtr<SourceMap>& outSourceMap); }; } // namespace Slang diff --git a/source/compiler-core/slang-source-loc.cpp b/source/compiler-core/slang-source-loc.cpp index 951f7feec..23cbdf839 100644 --- a/source/compiler-core/slang-source-loc.cpp +++ b/source/compiler-core/slang-source-loc.cpp @@ -3,6 +3,7 @@ #include "../core/slang-string-util.h" #include "../core/slang-string-escape-util.h" +#include "../core/slang-char-encode.h" #include "slang-artifact-representation-impl.h" #include "slang-artifact-impl.h" @@ -189,44 +190,118 @@ void SourceView::addDefaultLineDirective(SourceLoc directiveLoc) m_entries.add(entry); } -HandleSourceLoc SourceView::getHandleLoc(SourceLoc loc, SourceLocType type) +SlangResult _findLocWithSourceMap(SourceManager* lookupSourceManager, SourceView* sourceView, SourceLoc loc, HandleSourceLoc& outLoc) { - auto obfuscatedSourceMap = getSourceFile()->getObfuscatedSourceMap(); - if (obfuscatedSourceMap) + auto sourceFile = sourceView->getSourceFile(); + + // Hold a list of sourceFiles visited so we can't end up in a loop of lookups + List<SourceFile*> sourceFiles; + sourceFiles.add(sourceFile); + + Index entryIndex = -1; + + // Do the initial lookup using the loc + { + const auto offset = sourceView->getRange().getOffset(loc); + + const auto lineIndex = sourceFile->calcLineIndexFromOffset(offset); + const auto colIndex = sourceFile->calcColumnIndex(lineIndex, offset); + + // If we are in this function the sourceFile should have a map + auto sourceMap = sourceFile->getSourceMap(); + SLANG_ASSERT(sourceMap); + + entryIndex = sourceMap->findEntry(lineIndex, colIndex); + } + + if (entryIndex < 0) { - const Index col = getRange().getOffset(loc); + return SLANG_FAIL; + } + + // Keep searching through source maps + do + { + auto sourceMap = sourceFile->getSourceMap(); - const Index entryIndex = obfuscatedSourceMap->findEntry(0, col); - if (entryIndex >= 0) + // Find the entry + const auto& entry = sourceMap->getEntryByIndex(entryIndex); + const auto sourceFileName = sourceMap->getSourceFileName(entry.sourceFileIndex); + + // If we have a source name, see if it already exists in source manager + if (sourceFileName.getLength()) { - const auto& entry = obfuscatedSourceMap->getEntryByIndex(entryIndex); + if (auto foundSourceFile = lookupSourceManager->findSourceFileByPathRecursively(sourceFileName)) + { + // We only follow if the source file hasn't already been visisted + if (sourceFiles.indexOf(foundSourceFile) < 0) + { + // Add so we don't reprocess + sourceFiles.add(foundSourceFile); + + // If it has a source map, we try and look up the current location in it's source map + if (auto foundSourceMap = foundSourceFile->getSourceMap()) + { + const auto foundEntryIndex = foundSourceMap->findEntry(entry.sourceLine, entry.sourceColumn); + + // If we found the entry repeat the lookup + if (foundEntryIndex >= 0) + { + sourceFile = foundSourceFile; + entryIndex = foundEntryIndex; + continue; + } + } + } + } + } + } while (false); - // Generate the HandleSourceLoc + // Generate the HandleSourceLoc + auto sourceMap = sourceFile->getSourceMap(); + const auto& entry = sourceMap->getEntryByIndex(entryIndex); - HandleSourceLoc handleLoc; - handleLoc.line = entry.sourceLine + 1; - handleLoc.column = entry.sourceColumn + 1; + // We need to add the pool of the originating source view/file + const auto originatingSourceManager = sourceView->getSourceManager(); - auto& managerPool = getSourceManager()->getStringSlicePool(); + auto& managerPool = originatingSourceManager->getStringSlicePool(); - handleLoc.pathHandle = managerPool.add(obfuscatedSourceMap->getSourceFileName(entry.sourceFileIndex)); + outLoc.line = entry.sourceLine + 1; + outLoc.column = entry.sourceColumn + 1; + outLoc.pathHandle = managerPool.add(sourceMap->getSourceFileName(entry.sourceFileIndex)); + + return SLANG_OK; +} + +HandleSourceLoc SourceView::getHandleLoc(SourceLoc loc, SourceLocType type) +{ + // If it's nominal + if (type == SourceLocType::Nominal && m_sourceFile->getSourceMap()) + { + // TODO(JS): + // Ideally we'd do the lookup on the "current" source manager rather than the source manager on this + // view, which may be a parent to the current one. + auto lookupSourceManager = m_sourceFile->getSourceManager(); + + HandleSourceLoc handleLoc; + if (SLANG_SUCCEEDED(_findLocWithSourceMap(lookupSourceManager, this, loc, handleLoc))) + { return handleLoc; } } + // Get the offset in bytes for this loc const int offset = m_range.getOffset(loc); // We need the line index from the original source file const int lineIndex = m_sourceFile->calcLineIndexFromOffset(offset); - // TODO: we should really translate the byte index in the line - // to deal with: - // - // - Non-ASCII characters, while might consume multiple bytes - // + // TODO: // - Tab characters, which should really adjust how we report // columns (although how are we supposed to know the setting - // that an IDE expects us to use when reporting locations?) + // that an IDE expects us to use when reporting locations?) + // + // For now we just count tabs as single chars const int columnIndex = m_sourceFile->calcColumnIndex(lineIndex, offset); HandleSourceLoc handleLoc; @@ -409,12 +484,43 @@ int SourceFile::calcLineIndexFromOffset(int offset) return int(lo); } -int SourceFile::calcColumnIndex(int lineIndex, int offset) +int SourceFile::calcColumnOffset(int lineIndex, int offset) { const auto& lineBreakOffsets = getLineBreakOffsets(); return offset - lineBreakOffsets[lineIndex]; } +int SourceFile::calcColumnIndex(int lineIndex, int offset, int tabSize) +{ + const int colOffset = calcColumnOffset(lineIndex, offset); + + // If we don't have the content of the file, the best we can do is to assume there is a char per column + if (!hasContent()) + { + return colOffset; + } + + const auto line = getLineAtIndex(lineIndex); + + const auto head = line.head(colOffset); + + auto colCount = UTF8Util::calcCodePointCount(head); + + if (tabSize >= 0) + { + Count tabCount = 0; + for (auto c : head) + { + tabCount += Count(c == '\t'); + } + + // We substract one from tabSize, because colCount will already holds a +1 for each tab. + colCount += tabCount * (tabSize - 1); + } + + return int(colCount); +} + /* !!!!!!!!!!!!!!!!!!!!!!!!! SourceFile !!!!!!!!!!!!!!!!!!!!!!!!!!!! */ void SourceFile::setContents(ISlangBlob* blob) @@ -677,6 +783,37 @@ SourceView* SourceManager::findSourceViewRecursively(SourceLoc loc) const return nullptr; } +SourceFile* SourceManager::findSourceFileByPathRecursively(const String& name) const +{ + // Start with this manager + const SourceManager* manager = this; + do + { + SourceFile* sourceFile = manager->findSourceFileByPath(name); + // If we found a hit we are done + if (sourceFile) + { + return sourceFile; + } + // Try the parent + manager = manager->m_parent; + } while (manager); + // Didn't find it + return nullptr; +} + +SourceFile* SourceManager::findSourceFileByPath(const String& name) const +{ + for(auto sourceFile : m_sourceFiles) + { + if (sourceFile->getPathInfo().foundPath == name) + { + return sourceFile; + } + } + return nullptr; +} + SourceFile* SourceManager::findSourceFile(const String& uniqueIdentity) const { SourceFile*const* filePtr = m_sourceFileMap.TryGetValue(uniqueIdentity); diff --git a/source/compiler-core/slang-source-loc.h b/source/compiler-core/slang-source-loc.h index 4103c9d6d..98b1bdbbd 100644 --- a/source/compiler-core/slang-source-loc.h +++ b/source/compiler-core/slang-source-loc.h @@ -218,8 +218,13 @@ public: /// Calculate the line based on the offset int calcLineIndexFromOffset(int offset); - /// Calculate the offset for a line - int calcColumnIndex(int line, int offset); + /// Calculate the offset (in bytes) for a line + int calcColumnOffset(int line, int offset); + + /// Given a line and offset (in bytes for the whole file), return the column index, taking into account tabs + /// and utf8 encoding. + /// Passing tabSize uses the default tab size (currently tab set to 1) + int calcColumnIndex(int line, int offset, int tabSize = -1); /// Get the content holding blob ISlangBlob* getContentBlob() const { return m_contentBlob; } @@ -247,11 +252,11 @@ public: /// Get the source manager this was created on SourceManager* getSourceManager() const { return m_sourceManager; } - /// If set this "file" only exists as a way to obfuscate locations - /// The mapping between the two is specified in the specified source map - SourceMap* getObfuscatedSourceMap() const { return m_obfuscatedSourceMap; } - /// Set the obfuscated source map - void setObfuscatedSourceMap(SourceMap* sourceMap) { m_obfuscatedSourceMap = sourceMap; } + /// Get the source map associated with this file. If it's set when doing + /// lookup for source locations, the source map will be used + SourceMap* getSourceMap() const { return m_sourceMap; } + /// Set a source map + void setSourceMap(SourceMap* sourceMap) { m_sourceMap = sourceMap; } /// Ctor SourceFile(SourceManager* sourceManager, const PathInfo& pathInfo, size_t contentSize); @@ -272,15 +277,15 @@ public: // the input file: List<uint32_t> m_lineBreakOffsets; - // If set then this file isn't a regular source file, but provides obfuscation. - // The mapping of that obfuscation can be found via the obfuscated source map - RefPtr<SourceMap> m_obfuscatedSourceMap; + // If set then the locations in this file are really from locations from elsewhere, + // where the SourceMap specifies that mapping + RefPtr<SourceMap> m_sourceMap; }; enum class SourceLocType { - Nominal, ///< The normal interpretation which takes into account #line directives - Actual, ///< Ignores #line directives - and is the location as seen in the actual file + Nominal, ///< The normal interpretation which takes into account #line directives and source maps + Actual, ///< Ignores #line directives/source maps - and is the location as seen in the actual file }; // A source location in a format a human might like to see @@ -441,6 +446,11 @@ struct SourceManager /// Find if the source file is defined on this manager. SourceFile* findSourceFile(const String& uniqueIdentity) const; + /// Find a source file by path. + SourceFile* findSourceFileByPath(const String& name) const; + /// Find a source file by path recursively. + SourceFile* findSourceFileByPathRecursively(const String& name) const; + /// Searches this manager, and then the parent to see if can find a match SourceFile* findSourceFileByContentRecursively(const char* text); /// Find the source file that contains *the memory* text points to. diff --git a/source/slang/slang-compiler.cpp b/source/slang/slang-compiler.cpp index 43a6d238e..becd65596 100644 --- a/source/slang/slang-compiler.cpp +++ b/source/slang/slang-compiler.cpp @@ -1812,6 +1812,19 @@ namespace Slang } + bool _shouldWriteSourceLocs(Linkage* linkage) + { + // If debug information or source manager are not avaiable we can't/shouldn't write out locs + if (linkage->debugInfoLevel == DebugInfoLevel::None || + linkage->getSourceManager() == nullptr) + { + return false; + } + + // Otherwise we do want to write out the locs + return true; + } + SlangResult EndToEndCompileRequest::writeContainerToStream(Stream* stream) { auto linkage = getLinkage(); @@ -1827,7 +1840,9 @@ namespace Slang // Also currently only IR is needed. options.optionFlags &= ~SerialOptionFlag::ASTModule; } - else if (linkage->debugInfoLevel != DebugInfoLevel::None && linkage->getSourceManager()) + + // If debug information is enabled, enable writing out source locs + if (_shouldWriteSourceLocs(linkage)) { options.optionFlags |= SerialOptionFlag::SourceLocation; options.sourceManager = linkage->getSourceManager(); @@ -1897,7 +1912,8 @@ namespace Slang auto sourceMap = translationUnit->getModule()->getIRModule()->getObfuscatedSourceMap(); - if (sourceMap) + // If we have a source map *and* we want to generate them for output add to the container + if (sourceMap && getLinkage()->m_generateSourceMap) { // Write it out String json; diff --git a/source/slang/slang-ir-obfuscate-loc.cpp b/source/slang/slang-ir-obfuscate-loc.cpp index 3ff2f3713..9a2f15fa4 100644 --- a/source/slang/slang-ir-obfuscate-loc.cpp +++ b/source/slang/slang-ir-obfuscate-loc.cpp @@ -46,6 +46,17 @@ static void _findInstsRec(IRInst* inst, List<InstWithLoc>& out) } } +// We assume the root source manager is the stdlibs +static SourceLoc _getStdLibLastLoc(SourceManager* sourceManager) +{ + auto rootManager = sourceManager; + while (rootManager->getParent()) + { + rootManager = rootManager->getParent(); + } + return rootManager->getNextRangeStart(); +} + SlangResult obfuscateModuleLocs(IRModule* module, SourceManager* sourceManager) { // There shouldn't be an obfuscated source map set @@ -60,26 +71,28 @@ SlangResult obfuscateModuleLocs(IRModule* module, SourceManager* sourceManager) instWithLocs.sort(); // Lets produce a hash, so we can use as a key for random number generation. - // We could base it on time, or some other thing as there is no requirement for - // stability or consistency. - // We use a hash because it avoids issues around clocks, and availability of a clock - // as a good source of entropy. - // - // An argument *could* be made to generate the name via some mechanism that uniquely identified the - // combination of flags, options, files, names that identified the compilation, but that is - // not easily achieved. + // + // We could base it on time, or some other random seed. But it would be preferable + // if it was stable, and compilations of the same module on different machines + // produce the same hash. + // + // Doing so would mean that we could use the obfuscated location ouput to output + // the origin. + HashCode hash = 0; List<LocPair> locPairs; // We want the hash to be stable. One problem is the source locs depend on their order of inclusion. - // To work around this we are going + // To work around this we are going to hash via offsets, not locs. { SourceView* sourceView = nullptr; + const SourceLoc endStdLibLoc = _getStdLibLastLoc(sourceManager); + SourceLoc curLoc; for (const auto& instWithLoc : instWithLocs) - { + { if (instWithLoc.loc != curLoc) { LocPair locPair; @@ -89,21 +102,36 @@ SlangResult obfuscateModuleLocs(IRModule* module, SourceManager* sourceManager) // This is the current loc curLoc = instWithLoc.loc; + // Ignore any stdlib locs in the hash + if (instWithLoc.loc.getRaw() < endStdLibLoc.getRaw()) + { + continue; + } + // If the loc isn't in the view, lookup the view it is in if (sourceView == nullptr || !sourceView->getRange().contains(curLoc)) { sourceView = sourceManager->findSourceViewRecursively(curLoc); SLANG_ASSERT(sourceView); - + // If there is no source view we can't apply to the hash + if (sourceView == nullptr) + { + continue; + } + + const auto pathInfo = sourceView->getViewPathInfo(); + const auto name = pathInfo.getName(); + const auto nameHash = getHashCode(pathInfo.getName().getUnownedSlice()); + // Combine the name - hash = combineHash(hash, getHashCode(sourceView->getViewPathInfo().getName().getUnownedSlice())); + hash = combineHash(hash, nameHash); } - SLANG_ASSERT(sourceView); // We combine the *offset* which is stable - hash = combineHash(hash, getHashCode(sourceView->getRange().getOffset(curLoc))); - } + const auto offset = sourceView->getRange().getOffset(curLoc); + hash = combineHash(hash, getHashCode(offset)); + } } } @@ -138,16 +166,28 @@ SlangResult obfuscateModuleLocs(IRModule* module, SourceManager* sourceManager) dst[i * 2 + 1] = CharUtil::getHexChar(data[i] >> 4); } buf.appendInPlace(dst, charsCount); + + // Make it clear this "source" is actually just for obfuscation. + buf << toSlice("-obfuscated"); + obfusctatedPathInfo = PathInfo::makePath(buf); } } SourceFile* obfuscatedFile = sourceManager->createSourceFileWithSize(obfusctatedPathInfo, uniqueLocCount); - // We have only one line for all locs, just set up that way... + // We put each loc on it's own line. We do this rather than using a single line because + // it means the `#line` directives can still do something meaningful, since the best resolution + // they have is a single line. { - const uint32_t offsets[2] = { 0, uint32_t(uniqueLocCount) }; - obfuscatedFile->setLineBreakOffsets(offsets, SLANG_COUNT_OF(offsets)); + List<uint32_t> offsets; + offsets.setCount(uniqueLocCount + 1); + for (Index i = 0; i < uniqueLocCount + 1; ++i) + { + offsets[i] = uint32_t(i); + } + + obfuscatedFile->setLineBreakOffsets(offsets.getBuffer(), offsets.getCount()); } // Create the view we are going to use from the obfusctated "file". @@ -210,10 +250,16 @@ SlangResult obfuscateModuleLocs(IRModule* module, SourceManager* sourceManager) RefPtr<SourceMap> sourceMap = new SourceMap; sourceMap->m_file = obfusctatedPathInfo.getName(); - // Make sure we have line 0. - // We only end up with one line in the obfuscated map. - sourceMap->advanceToLine(0); - + // Set up entries one per line + List<SourceMap::Entry> entries; + { + entries.setCount(uniqueLocCount); + for (auto& entry : entries) + { + entry.init(); + } + } + { // Current view, with cached "View" based sourceFileIndex SourceView* curView = nullptr; @@ -275,27 +321,35 @@ SlangResult obfuscateModuleLocs(IRModule* module, SourceManager* sourceManager) sourceFileIndex = curPathSourceFileIndex; } - // Create the entry - SourceMap::Entry entry; - entry.init(); + // Calculate the line index associated with this loc + const Index generatedLineIndex = Index(obfuscatedRange.getOffset(pair.obfuscatedLoc)); + + // Set it up + SourceMap::Entry& entry = entries[generatedLineIndex]; entry.sourceFileIndex = sourceFileIndex; - // Calculate the column offset, from the pair obfuscated loc - entry.generatedColumn = Index(obfuscatedRange.getOffset(pair.obfuscatedLoc)); - + // The generated has a line per loc, so the generated column is always 0 + entry.generatedColumn = 0; + // We need to subtract 1, because handleLoc locations are 1 indexed, but SourceMap // entry is 0 indexed. entry.sourceColumn = handleLoc.column - 1; entry.sourceLine = handleLoc.line - 1; - - // Add it to the source map - sourceMap->addEntry(entry); } } + // Add all of the entries in line order to the source map + for (Index i = 0; i < uniqueLocCount; ++i) + { + // Advance to the current line. + sourceMap->advanceToLine(i); + // Add it to the source map + sourceMap->addEntry(entries[i]); + } + // Associate the sourceMap with the obfuscated file - obfuscatedFile->setObfuscatedSourceMap(sourceMap); + obfuscatedFile->setSourceMap(sourceMap); // Set the obfuscated map onto the module module->setObfuscatedSourceMap(sourceMap); diff --git a/source/slang/slang-lower-to-ir.cpp b/source/slang/slang-lower-to-ir.cpp index 8cceaff02..110f85b87 100644 --- a/source/slang/slang-lower-to-ir.cpp +++ b/source/slang/slang-lower-to-ir.cpp @@ -9652,7 +9652,7 @@ RefPtr<IRModule> generateIRForTranslationUnit( // We don't do the obfuscation remapping here, because DCE and other passes may // change what locs are actually needed, we need to be sure // that if we have obfuscation enabled we don't forget to obfuscate. - stripOptions.stripSourceLocs = linkage->m_obfuscateCode && !linkage->m_generateSourceMap; + stripOptions.stripSourceLocs = false; stripFrontEndOnlyInstructions(module, stripOptions); // Stripping out decorations could leave some dead code behind @@ -9667,7 +9667,7 @@ RefPtr<IRModule> generateIRForTranslationUnit( options.keepExportsAlive = true; eliminateDeadCode(module, options); - if (linkage->m_obfuscateCode && linkage->m_generateSourceMap) + if (linkage->m_obfuscateCode) { // The obfuscated source map is stored on the module obfuscateModuleLocs(module, compileRequest->getSourceManager()); diff --git a/source/slang/slang-options.cpp b/source/slang/slang-options.cpp index ae7988322..b1e1b93d2 100644 --- a/source/slang/slang-options.cpp +++ b/source/slang/slang-options.cpp @@ -1623,6 +1623,12 @@ struct OptionsParser desc.kind = ArtifactKind::Library; } + // If its a zip we'll *assume* its a zip holding compilation results + if (desc.kind == ArtifactKind::Zip) + { + desc.payload = ArtifactPayload::CompileResults; + } + if (!ArtifactDescUtil::isLinkable(desc)) { sink->diagnose(referenceModuleName.loc, Diagnostics::kindNotLinkable, Path::getPathExt(path)); diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp index 4904abe03..c8cda0dca 100644 --- a/source/slang/slang.cpp +++ b/source/slang/slang.cpp @@ -15,6 +15,8 @@ #include "../compiler-core/slang-artifact-associated-impl.h" #include "../compiler-core/slang-artifact-container-util.h" +#include "../compiler-core/slang-json-source-map-util.h" + #include "../core/slang-memory-file-system.h" #include "slang-module-library.h" @@ -4833,10 +4835,85 @@ void EndToEndCompileRequest::setDefaultModuleName(const char* defaultModuleName) frontEndReq->m_defaultModuleName = namePool->getName(defaultModuleName); } +SlangResult _addLibraryReference(EndToEndCompileRequest* req, IArtifact* artifact, ModuleLibrary* moduleLibrary) +{ + FrontEndCompileRequest* frontEndRequest = req->getFrontEndReq(); + frontEndRequest->m_extraEntryPoints.addRange(moduleLibrary->m_entryPoints.getBuffer(), moduleLibrary->m_entryPoints.getCount()); + + // Add to the m_libModules + auto linkage = req->getLinkage(); + linkage->m_libModules.add(ComPtr<IArtifact>(artifact)); + + return SLANG_OK; +} + SlangResult _addLibraryReference(EndToEndCompileRequest* req, IArtifact* artifact) { auto desc = artifact->getDesc(); + // TODO(JS): + // This isn't perhaps the best way to handle this scenario, as IArtifact can + // support lazy evaluation, with suitable hander. + // For now we just read in and strip out the bits we want. + if (isDerivedFrom(desc.kind, ArtifactKind::Container) && + isDerivedFrom(desc.payload, ArtifactPayload::CompileResults)) + { + // We want to read as a file system + ComPtr<IArtifact> container; + + SLANG_RETURN_ON_FAIL(ArtifactContainerUtil::readContainer(artifact, container)); + + // Find the payload... It should be linkable + if (!ArtifactDescUtil::isLinkable(container->getDesc())) + { + return SLANG_FAIL; + } + + ComPtr<IModuleLibrary> libraryIntf; + SLANG_RETURN_ON_FAIL(loadModuleLibrary(ArtifactKeep::Yes, container, req, libraryIntf)); + + auto library = as<ModuleLibrary>(libraryIntf); + + // Look for source maps + for (auto associated : container->getAssociated()) + { + auto assocDesc = associated->getDesc(); + + // If we find an obfuscated source map load it and associate + if (isDerivedFrom(assocDesc.kind, ArtifactKind::Json) && + isDerivedFrom(assocDesc.payload, ArtifactPayload::SourceMap) && + isDerivedFrom(assocDesc.style, ArtifactStyle::Obfuscated)) + { + ComPtr<ISlangBlob> sourceMapBlob; + SLANG_RETURN_ON_FAIL(associated->loadBlob(ArtifactKeep::No, sourceMapBlob.writeRef())); + + RefPtr<SourceMap> sourceMap; + SLANG_RETURN_ON_FAIL(JSONSourceMapUtil::read(sourceMapBlob, nullptr, sourceMap)); + + // I guess we add to all ir modules? + + for (auto irModule : library->m_modules) + { + irModule->setObfuscatedSourceMap(sourceMap); + } + + // Look up the source file + auto sourceManager = req->getSink()->getSourceManager(); + + auto name = Path::getFileNameWithoutExt(associated->getName()); + + if (name.getLength()) + { + auto sourceFile = sourceManager->findSourceFileByPathRecursively(name); + sourceFile->setSourceMap(sourceMap); + } + } + } + + SLANG_RETURN_ON_FAIL(_addLibraryReference(req, container, library)); + return SLANG_OK; + } + if (desc.kind == ArtifactKind::Library && desc.payload == ArtifactPayload::SlangIR) { ComPtr<IModuleLibrary> libraryIntf; @@ -4849,15 +4926,13 @@ SlangResult _addLibraryReference(EndToEndCompileRequest* req, IArtifact* artifac return SLANG_FAIL; } - FrontEndCompileRequest* frontEndRequest = req->getFrontEndReq(); - frontEndRequest->m_extraEntryPoints.addRange(library->m_entryPoints.getBuffer(), library->m_entryPoints.getCount()); - } - else - { - // TODO(JS): - // Do we want to check the path exists? + SLANG_RETURN_ON_FAIL(_addLibraryReference(req, artifact, library)); + return SLANG_OK; } + // TODO(JS): + // Do we want to check the path exists? + // Add to the m_libModules auto linkage = req->getLinkage(); linkage->m_libModules.add(ComPtr<IArtifact>(artifact)); diff --git a/tests/serialization/obfuscated-loc-module.slang b/tests/serialization/obfuscated-loc-module.slang new file mode 100644 index 000000000..63c4f61ee --- /dev/null +++ b/tests/serialization/obfuscated-loc-module.slang @@ -0,0 +1,21 @@ +//TEST_IGNORE_FILE: + +// obfuscated-loc-module.slang + + +int billy(int v) +{ + return v + 1; +} + +// This function is designed to fail during IR passes/emit. +int silly(int a) +{ + int t = 0; + [ForceUnroll(10)] + while ( a > 0) + { + t = t + t + a; + } + return t; +} diff --git a/tests/serialization/obfuscated-module-check-loc.slang b/tests/serialization/obfuscated-module-check-loc.slang new file mode 100644 index 000000000..f603d5403 --- /dev/null +++ b/tests/serialization/obfuscated-module-check-loc.slang @@ -0,0 +1,29 @@ +//TEST:COMPILE: tests/serialization/obfuscated-loc-module.slang -o tests/serialization/obfuscated-loc-module.zip -g -obfuscate -source-map +//TEST:SIMPLE:-target hlsl -stage compute -entry computeMain -obfuscate -r tests/serialization/obfuscated-loc-module.zip +//TEST:COMPILE: tests/serialization/obfuscated-loc-module.slang -o tests/serialization/obfuscated-loc-module.zip -g -obfuscate +// Disable for now as it breaks on gcc/release as different hash seems to be produced +//DISABLE_TEST:SIMPLE:-target hlsl -stage compute -entry computeMain -obfuscate -r tests/serialization/obfuscated-loc-module.zip + +//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):out,name=outputBuffer +RWStructuredBuffer<float> outputBuffer; + +// This test checks obfuscated source map loc tracking through a round trip, and producing a location correctly from slang-module that has a source map + +// We *don't* import because if we do we'll get a fresh compilation from source... we want to make sure it's using the -r module +//import obfuscated_loc_module; +int silly(int v); +int billy(int v); + +[numthreads(1, 1, 1)] +void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID) +{ + int x = int(dispatchThreadID.x); + + x = billy(x); + + // Will produce an error, because silly has an error. + int v = silly(x); + + outputBuffer[x] = v; +} + diff --git a/tests/serialization/obfuscated-module-check-loc.slang.1.expected b/tests/serialization/obfuscated-module-check-loc.slang.1.expected new file mode 100644 index 000000000..aaf3bfd48 --- /dev/null +++ b/tests/serialization/obfuscated-module-check-loc.slang.1.expected @@ -0,0 +1,6 @@ +result code = -1 +standard error = { +tests/serialization/obfuscated-loc-module.slang(16): error 40020: loop does not terminate within the limited number of iterations, unrolling is aborted. +} +standard output = { +} diff --git a/tests/serialization/obfuscated-module-check-loc.slang.3.expected b/tests/serialization/obfuscated-module-check-loc.slang.3.expected new file mode 100644 index 000000000..94f740eb5 --- /dev/null +++ b/tests/serialization/obfuscated-module-check-loc.slang.3.expected @@ -0,0 +1,6 @@ +result code = -1 +standard error = { +bc65f637-obfuscated(6): error 40020: loop does not terminate within the limited number of iterations, unrolling is aborted. +} +standard output = { +} diff --git a/tests/serialization/obfuscated-serialized-module-test.slang b/tests/serialization/obfuscated-serialized-module-test.slang index 55a68f6c2..b007b7516 100644 --- a/tests/serialization/obfuscated-serialized-module-test.slang +++ b/tests/serialization/obfuscated-serialized-module-test.slang @@ -3,7 +3,7 @@ // A test to try out the basics of module // serialization, obfuscation and source maps. -//TEST:COMPILE: tests/serialization/serialized-module.slang -o tests/serialization/obfuscated-serialized-module.slang-module -obfuscate -source-map +//TEST:COMPILE: tests/serialization/serialized-module.slang -o tests/serialization/obfuscated-serialized-module.slang-module -g -obfuscate -source-map //TEST:COMPARE_COMPUTE_EX:-slang -compute -Xslang... -r tests/serialization/obfuscated-serialized-module.slang-module -obfuscate -source-map -X. -shaderobj //import obfuscated_serialized_module; |
