279 Commits

Author SHA1 Message Date
Jan Svoboda
13e1a2cb22 Reapply "[clang] Remove intrusive reference count from DiagnosticOptions (#139584)"
This reverts commit e2a885537f11f8d9ced1c80c2c90069ab5adeb1d. Build failures were fixed right away and reverting the original commit without the fixes breaks the build again.
2025-05-22 12:52:03 -07:00
Kazu Hirata
e2a885537f Revert "[clang] Remove intrusive reference count from DiagnosticOptions (#139584)"
This reverts commit 9e306ad4600c4d3392c194a8be88919ee758425c.

Multiple builtbot failures have been reported:
https://github.com/llvm/llvm-project/pull/139584
2025-05-22 12:44:20 -07:00
Jan Svoboda
9e306ad460
[clang] Remove intrusive reference count from DiagnosticOptions (#139584)
The `DiagnosticOptions` class is currently intrusively
reference-counted, which makes reasoning about its lifetime very
difficult in some cases. For example, `CompilerInvocation` owns the
`DiagnosticOptions` instance (wrapped in `llvm::IntrusiveRefCntPtr`) and
only exposes an accessor returning `DiagnosticOptions &`. One would
think this gives `CompilerInvocation` exclusive ownership of the object,
but that's not the case:

```c++
void shareOwnership(CompilerInvocation &CI) {
  llvm::IntrusiveRefCntPtr<DiagnosticOptions> CoOwner = &CI.getDiagnosticOptions();
  // ...
}
```

This is a perfectly valid pattern that is being actually used in the
codebase.

I would like to ensure the ownership of `DiagnosticOptions` by
`CompilerInvocation` is guaranteed to be exclusive. This can be
leveraged for a copy-on-write optimization later on. This PR changes
usages of `DiagnosticOptions` across `clang`, `clang-tools-extra` and
`lldb` to not be intrusively reference-counted.
2025-05-22 12:33:52 -07:00
Kazu Hirata
95335fefd8
[clang] Use std::tie to implement operator< (NFC) (#139438) 2025-05-11 07:13:41 -07:00
Michael Spencer
32fb8c5f5f
[clang][modules] Lazily load by name lookups in module maps (#132853)
Instead of eagerly populating the `clang::ModuleMap` when looking up a
module by name, this patch changes `HeaderSearch` to only load the
modules that are actually used.

This introduces `ModuleMap::findOrLoadModule` which will load modules
from parsed but not loaded module maps. This cannot be used anywhere
that the module loading code calls into as it can create infinite
recursion.

This currently just reparses module maps when looking up a module by
header. This is fine as redeclarations are allowed from the same file,
but future patches will also make looking up a module by header lazy.

This patch changes the shadow.m test to use explicitly built modules and
`#import`. This test and the shadow feature are very brittle and do not
work in general. The test relied on pcm files being left behind by prior
failing clang invocations that were then reused by the last invocation.
If you clean the cache then the last invocation will always fail. This
is because the input module map and the `-fmodule-map-file=` module map
are parsed in the same module scope, and `-fmodule-map-file=` is
forwarded to implicit module builds. That means you are guaranteed to
hit a module redeclaration error if the TU actually imports the module
it is trying to shadow.

This patch changes when we load A2's module map to after the `A` module
has been loaded, which sets the `IsFromModuleFile` bit on `A`. This
means that A2's `A` is skipped entirely instead of creating a shadow
module, and we get textual inclusion. It is possible to construct a case
where this would happen before this patch too.

An upcoming patch in this series will rework shadowing to work in the
general case, but that's only possible once header -> module lookup is
lazy too.
2025-05-06 16:40:01 -07:00
Jan Svoboda
c85e43bd45
[clang] Hide the LangOptions pointer from CompilerInvocation (#137675)
This PR makes `CompilerInvocation` the sole owner of the `LangOptions`
instance.
2025-04-29 10:37:37 -07:00
Jan Svoboda
77148fce6f
[clang] Do not share ownership of HeaderSearchOptions (#132984)
This PR makes it so that `CompilerInvocation` is the sole owner of the
`HeaderSearchOptions` instance.
2025-04-25 07:38:51 -07:00
Zibi Sarbinowski
bd0b903a91
Mark the file opened by DeserializedDeclsSourceRangePrinter as a text file (#135842)
This PR will fix the following lit failure seeing on z/OS and most likely on Windows:

`FAIL: Clang :: Frontend/dump-minimization-hints.cpp`

Without `OF_TextWithCRLF` flag, a file is treated as binary and is read improperly, at least on z/OS.
2025-04-17 10:08:57 -04:00
Viktoriia Bakalova
9eeafc63d2
Implement -dump-minimization-hints flag. (#133910)
This PR implements a CC1 flag `-dump-minimization-hints`.
The flag allows to specify a file path to dump ranges of deserialized
declarations in `ASTReader`. Example usage:

```
clang -Xclang=-dump-minimization-hints=/tmp/decls -c file.cc -o file.o
```

Example output:
```
// /tmp/decls
{
  "required_ranges": [
    {
      "file": "foo.h",
      "range": [
        {
          "from": {
            "line": 26,
            "column": 1
          },
          "to": {
            "line": 27,
            "column": 77
          }
        }
      ]
    },
    {
      "file": "bar.h",
      "range": [
        {
          "from": {
            "line": 30,
            "column": 1
          },
          "to": {
            "line": 35,
            "column": 1
          }
        },
        {
          "from": {
            "line": 92,
            "column": 1
          },
          "to": {
            "line": 95,
            "column": 1
          }
        }
      ]
    }
  ]
}

```
Specifying the flag creates an instance of
`DeserializedDeclsSourceRangePrinter`, which dumps ranges of deserialized
declarations to aid debugging and bug minimization (we use is as input to [C-Vise](https://github.com/emaxx-google/cvise/tree/multifile-hints).

Required ranges are computed from source ranges of Decls.
`TranslationUnitDecl`, `LinkageSpecDecl` and `NamespaceDecl` are ignored
for the sake of this PR.

Technical details:
* `DeserializedDeclsSourceRangePrinter` implements `ASTConsumer` and
`ASTDeserializationListener`, so that an object of
`DeserializedDeclsSourceRangePrinter` registers as its own listener.
* `ASTDeserializationListener` interface provides the `DeclRead`
callback that we use to collect the deserialized Decls.
Printing or otherwise processing them as this point is dangerous, since
that could trigger additional deserialization and crash compilation.
* The collected Decls are processed in `HandleTranslationUnit` method of
`ASTConsumer`. This is a safe point, since we know that by this point
all the Decls needed by the compiler frontend have been deserialized.
* In case our processing causes further deserialization, `DeclRead` from
the listener might be called again. However, at that point we don't
accept any more Decls for processing.
2025-04-11 12:48:18 +02:00
Qiu Chaofan
b3f01a6aa4
[Clang] Check PP presence when printing stats (#131608)
Front-end option `-print-stats` can be used to print statistics around
the compilation process. But clang with this options will crash when
input is IR file. This patch fixes the crash by checking preprocessor
presence before invoking it.
2025-03-29 01:54:14 +08:00
Haojian Wu
db04c3e4b3
[clang] Implement some missing interfaces for DelegatingDeserializationListener (#133424)
Split from the https://github.com/llvm/llvm-project/pull/133395 per the
review comment.

This patch also moves the `DelegatingDeserializationListener` close to
`ASTDeserializationListener`.
2025-03-28 15:31:21 +01:00
Fangrui Song
5c0aa31c3c -ftime-report: Move FrontendTimer closer to TimeTraceScope
... to improve consistency and make "Clang time report" cover
`FrontendAction::BeginSourceFile` and `FrontendAction::EndSourceFile`.
2025-01-12 13:17:49 -08:00
Jan Svoboda
da1a16ae10
[clang][modules] Preserve the module map that allowed inferring (#113389)
With inferred modules, the dependency scanner takes care to replace the
fake "__inferred_module.map" path with the file that allowed the module
to be inferred. However, this only worked when such a module was
imported directly in the TU. Whenever such module got loaded
transitively, the scanner would fail to perform the replacement. This is
caused by the fact that PCM files are lossy and drop this information.

This patch makes sure that PCMs include this file for each submodule (in
the `SUBMODULE_DEFINITION` record), fixes one existing test with an
incorrect assertion, and does a little drive-by refactoring of
`ModuleMap`.
2024-10-28 11:24:27 -07:00
Jan Svoboda
61946687bc
[clang][modules] Shrink the size of Module::Headers (#113395)
This patch shrinks the size of the `Module` class from 2112B to 1624B. I
wasn't able to get a good data on the actual impact on memory usage, but
given my `clang-scan-deps` workload at hand (with tens of thousands of
instances), I think there should be some win here. This also speeds up
my benchmark by under 0.1%.
2024-10-25 11:33:44 -07:00
Kazu Hirata
3cd3202b78
[Frontend] Teach LoadFromASTFile to take FileName by StringRef (NFC) (#109583)
Without this patch, several callers of LoadFromASTFile construct an
instance of std::string to be passed as FileName, only to be converted
back to StringRef when LoadFromASTFile calls ReadAST.

This patch changes the type of FileName to StringRef and updates the
callers.
2024-09-23 19:21:39 -07:00
Chuanqi Xu
e74a34b693 [NFC] [Serialization] Merge IdentID with IdentifierID
In ASTBitCodes.h, there are two type alias for the ID type of
Identifiers with the same underlying type. It is confusing. This patch
tries to merge the `IdentID` to `IdentifierID` to erase such confusion.
2024-05-13 14:05:02 +08:00
yronglin
03b1a0c2a7
[Clang] Diagnose apply AST consume actions on LLVM IR (#88602)
Fixes https://github.com/llvm/llvm-project/issues/88522

This PR introduce a new diagnostic to report apply AST consume actions
on LLVM IR.

---------

Signed-off-by: yronglin <yronglin777@gmail.com>
2024-04-25 19:05:46 +08:00
Chuanqi Xu
d86cc73bbf [NFC] [Serialization] Avoid using DeclID directly as much as possible
This patch tries to remove all the direct use of DeclID except the real
low level reading and writing. All the use of DeclID is converted to
the use of LocalDeclID or GlobalDeclID. This is helpful to increase the
readability and type safety.
2024-04-25 14:59:09 +08:00
Chuanqi Xu
72b58146b1 Revert "[NFC] [Serialization] Avoid using DeclID directly as much as possible"
This reverts commit 42070a5c092ed420bf92ebf38229c594885e94c7.

I forgot to touch lldb.
2024-04-25 14:26:07 +08:00
Chuanqi Xu
42070a5c09 [NFC] [Serialization] Avoid using DeclID directly as much as possible
This patch tries to remove all the direct use of DeclID except the real
low level reading and writing. All the use of DeclID is converted to
the use of LocalDeclID or GlobalDeclID. This is helpful to increase the
readability and type safety.
2024-04-25 14:14:05 +08:00
Chuanqi Xu
c2a98fdeb3
[NFC] Move DeclID from serialization/ASTBitCodes.h to AST/DeclID.h (#89873)
Previously, the DeclID is defined in serialization/ASTBitCodes.h under
clang::serialization namespace. However, actually the DeclID is not
purely used in serialization part. The DeclID is already widely used in
AST and all around the clang project via classes like `LazyPtrDecl` or
calling `ExternalASTSource::getExernalDecl()`. All such uses are via the
raw underlying type of `DeclID` as `uint32_t`. This is not pretty good.

This patch moves the DeclID class family to a new header `AST/DeclID.h`
so that the whole project can use the wrapped class `DeclID`,
`GlobalDeclID` and `LocalDeclID` instead of the raw underlying type.
This can improve the readability and the type safety.
2024-04-25 13:53:22 +08:00
Jan Svoboda
44af53b22a
[clang][modules] Avoid calling expensive SourceManager::translateFile() (#86216)
The `ASTWriter` algorithm for computing affecting module maps uses
`SourceManager::translateFile()` to get a `FileID` from a `FileEntry`.
This is slow (O(n)) since the function performs a linear walk over
`SLocEntries` until it finds one with a matching `FileEntry`.

This patch removes this use of `SourceManager::translateFile()` by
tracking `FileID` instead of `FileEntry` in couple of places in
`ModuleMap`, giving `ASTWriter` the desired `FileID` directly. There are
no changes required for clients that still want a `FileEntry` from
`ModuleMap`: the existing APIs internally use `SourceManager` to perform
the reverse `FileID` to `FileEntry` conversion in O(1).
2024-03-28 13:02:48 -07:00
Chuanqi Xu
2e5af56b05 [C++20] [Modules] Allow to compile a pcm with and without -fPIC
seperately

We can compile a module unit in 2 phase compilaton:

```
clang++ -std=c++20 a.cppm --precompile -o a.pcm
clang++ -std=c++20 a.pcm -c -o a.o
```

And it is a general requirement that we need to compile a translation
unit with and without -fPIC for static and shared libraries.

But for C++20 modules with 2 phase compilation, it may be waste of time
to compile them 2 times completely. It may be fine to generate one BMI
and compile it with and without -fPIC seperately.

e.g.,

```
clang++ -std=c++20 a.cppm --precompile -o a.pcm
clang++ -std=c++20 a.pcm -c -o a.o
clang++ -std=c++20 a.pcm -c -fPIC -o a-PIC.o
```

Then we can save the time to parse a.cppm repeatedly.
2024-02-23 11:05:15 +08:00
Juergen Ributzka
21361bb860
[clang] Remove unused argument. NFC. (#73594) 2023-11-28 09:19:39 -08:00
David Stone
701d804cdb
[clang][Modules] checkModuleIsAvailable should use a const & parameter instead of pointer (#67902)
The `Module` parameter to `checkModuleIsAvailable` is currently passed
by pointer to non-const. However, it requires only const access and it
cannot be null. Change this to be a reference to const instead.

This then makes it obvious that it is an input-only parameter, so move
it to be before the in-out parameter for diagnostics.
2023-10-08 09:49:05 +08:00
Jan Svoboda
b0abc9dd44 [clang] NFCI: Use FileEntryRef in ASTReader::GetHeaderFileInfo()
This is the `ASTReader` counterpart to PR #67383.
2023-09-29 09:07:13 -07:00
Jan Svoboda
3e9c36303c [clang] NFCI: Use FileEntryRef in SourceManager::setFileIsTransient() 2023-09-28 14:50:00 -07:00
Jan Svoboda
1b07d43298 [clang] NFCI: Use FileEntryRef in ModuleMap::InferredModuleAllowedBy 2023-09-13 13:32:26 -07:00
Jan Svoboda
523c471250 Reapply "[clang] NFCI: Adopt SourceManager::getFileEntryRefForID()"
This reapplies ddbcc10b9e26b18f6a70e23d0611b9da75ffa52f, except for a tiny part that was reverted separately: 65331da0032ab4253a4bc0ddcb2da67664bd86a9. That will be reapplied later on, since it turned out to be more involved.

This commit is enabled by 5523fefb01c282c4cbcaf6314a9aaf658c6c145f and f0f548a65a215c450d956dbcedb03656449705b9, specifically the part that makes 'clang-tidy/checkers/misc/header-include-cycle.cpp' separator agnostic.
2023-09-08 19:04:01 -07:00
Chuanqi Xu
869111ccf2 [NFC] [C++20] [Modules] Refactor the warning to '-fmodule-file=<BMIPath>' for C++20 modules
Previous implementation of the warning to use `-fmodule-file=<BMIPath>`
for C++20 Modules is not straightforward and it is problematic in case
we read the BMIPath by writing tools based clang components. This patch
refactors it with a simple and direct style.
2023-09-07 17:13:16 +08:00
Jan Svoboda
0a9611fd8d Revert "[clang] NFCI: Adopt SourceManager::getFileEntryRefForID()"
This reverts commit ddbcc10b9e26b18f6a70e23d0611b9da75ffa52f.

The 'clang-tidy/checkers/misc/header-include-cycle.cpp' test started failing on Windows: https://lab.llvm.org/buildbot/#/builders/216/builds/26855.
2023-09-06 13:23:23 -07:00
Jan Svoboda
ddbcc10b9e [clang] NFCI: Adopt SourceManager::getFileEntryRefForID()
This commit replaces some calls to the deprecated `FileEntry::getName()` with `FileEntryRef::getName()` by swapping current usages of `SourceManager::getFileEntryForID()` with `SourceManager::getFileEntryRefForID()`. This lowers the number of usages of the deprecated `FileEntry::getName()` from 95 to 50.
2023-09-06 10:49:48 -07:00
Younan Zhang
e257c0a919 [clang][clangd] Ensure the stack bottom before building AST
`clang::runWithSufficientStackSpace` requires the address of the
initial stack bottom to prevent potential stack overflows.

In addition, add a fallback to ASTFrontendAction in case any client
forgets to call it when not through CompilerInstance::ExecuteAction,
which is rare.

Fixes https://github.com/clangd/clangd/issues/1745.

Reviewed By: sammccall

Differential Revision: https://reviews.llvm.org/D158967
2023-09-02 18:53:06 +08:00
Podchishchaeva, Mariya
a623f4c784 [NFC][clang] Fix static analyzer concerns
DelegatingDeserializationListener frees resources in the destructor but doesn't
have user-written copy c'tor or assignment operator, so copying it using
default ones can cause double free.

Reviewed By: aaron.ballman

Differential Revision: https://reviews.llvm.org/D156405
2023-07-28 05:29:56 -07:00
Jan Svoboda
fa5788ff8d [clang][index] NFCI: Make CXFile a FileEntryRef
This patch swaps out the `void *` behind `CXFile` from `FileEntry *` to `FileEntryRef::MapEntry *`. This allows us to remove some deprecated uses of `FileEntry::getName()`.

Depends on D151854.

Reviewed By: benlangmuir

Differential Revision: https://reviews.llvm.org/D151938
2023-06-15 12:34:54 +02:00
Jan Svoboda
3473f728b3 [clang][lex] NFCI: Use DirectoryEntryRef in HeaderSearch::LookupFile
This patch changes the argument type to `HeaderSearch::LookupFile()` from `const DirectoryEntry *` to `DirectoryEntryRef` in order to remove some calls to the deprecated `DirectoryEntry::getName()`.

Depends on D127660.

Reviewed By: bnbarham, benlangmuir

Differential Revision: https://reviews.llvm.org/D127663
2023-05-30 21:42:01 -07:00
Jan Svoboda
e6830b6028 [clang][modules] NFCI: Extract optionality out of Module::{Header,DirectoryName}
Most users of `Module::Header` already assume its `Entry` is populated. Enforce this assumption in the type system and handle the only case where this is not the case by wrapping the whole struct in `std::optional`. Do the same for `Module::DirectoryName`.

Depends on D151584.

Reviewed By: benlangmuir

Differential Revision: https://reviews.llvm.org/D151586
2023-05-30 21:06:51 -07:00
Jan Svoboda
e19f352bee [clang][lex] NFCI: Use DirectoryEntryRef in Preprocessor::MainFileDir
This patch changes the type of `Preprocessor::MainFileDir` from `const DirectoryEntry *` to `Optional<DirectoryEntryRef>` in order to remove some calls to the deprecated `DirectoryEntry::getName()`.

Depends on D127658.

Reviewed By: bnbarham

Differential Revision: https://reviews.llvm.org/D127660
2023-05-30 13:54:06 -07:00
Jan Svoboda
95279d7670 [clang][lex] NFCI: Use DirectoryEntryRef in ModuleMap::inferFrameworkModule()
This patch changes the argument type of `ModuleMap::inferFrameworkModule()` from `const DirectoryEntry *` to `DirectoryEntryRef` in order to remove the deprecated uses of `DirectoryEntry::getName()`.

Depends on D127647.

Reviewed By: bnbarham

Differential Revision: https://reviews.llvm.org/D127648
2023-05-30 13:54:06 -07:00
Jan Svoboda
769d282d72 [clang][lex] NFCI: Use FileEntryRef in ModuleMap::{load,lookup}ModuleMap()
This patch changes the return/argument types of `ModuleMap::{load,lookup}ModuleMap()` from `const FileEntry *` to `FileEntryRef` in order to remove uses of the deprecated `DirectoryEntry::getName()`.

Reviewed By: bnbarham

Differential Revision: https://reviews.llvm.org/D127647
2023-05-30 13:54:06 -07:00
Jan Svoboda
924912956e [clang][modules] NFCI: Distinguish as-written and effective umbrella directories
For modules with umbrellas, we track how they were written in the module map. Unfortunately, the getter for the umbrella directory conflates the "as written" directory and the "effective" directory (either the written one or the parent of the written umbrella header).

This patch makes the distinction between "as written" and "effective" umbrella directories clearer. No functional change intended.

Reviewed By: benlangmuir

Differential Revision: https://reviews.llvm.org/D151581
2023-05-26 15:14:16 -07:00
Chuanqi Xu
807aa26136 [C++20] [Modules] Don't ignore -fmodule-file when we compile pcm files
Close https://github.com/llvm/llvm-project/issues/62843.

Previously when we compile .pcm files into .o files, the
`-fmodule-file=<module-name>=<module-path>` option is ignored. This is
conflicted with our consensus in
https://github.com/llvm/llvm-project/issues/62707.
2023-05-23 14:22:01 +08:00
Stoorx
40136ecefc [clang] Make access to submodules via iterator_range
In file `clang/lib/Basic/Module.cpp` the `Module` class had `submodule_begin()` and `submodule_end()` functions to retrieve corresponding iterators for private vector of Modules. This commit removes mentioned functions, and replaces all of theirs usages with `submodules()` function and range-based for-loops.

Differential Revision: https://reviews.llvm.org/D148954
2023-04-24 12:05:59 +03:00
Chuanqi Xu
7c97c574cc [Modules] Recreate file manager for ftime-trace when compiling a module
Close https://github.com/llvm/llvm-project/issues/60544.

The root cause for the issue is that when we compile a module unit, the
file manager (and proprocessor and source manager) are owned by AST
instead of the compilaton instance. So the file manager may be invalid
when we want to create a time-report file for -ftime-trace when we are
compiling a module unit.

This patch tries to recreate the file manager for -ftime-trace if we
find the file manager is not valid.
2023-02-06 17:17:09 +08:00
Richard Howell
75fbb5d223 [clang][nfc] refactor Module::Header to use OptionalFileEntryRef
Refactor the `Module::Header` class to use an `OptionalFileEntryRef`
instead of a `FileEntry*`. This is preparation for refactoring the
`TopHeaderNames` to use `FileEntryRef` so that we preserve the
lookup path of the headers when serializing.

This is mostly based on https://reviews.llvm.org/D90497

Reviewed By: jansvoboda11

Differential Revision: https://reviews.llvm.org/D142113
2023-01-20 07:23:11 -08:00
Benjamin Kramer
854c10f8d1 [Clang] Prepare for llvm::Optional becoming std::optional.
The needed tweaks are mostly trivial, the one nasty bit is Clang's usage
of OptionalStorage. To keep this working old Optional stays around as
clang::CustomizableOptional, with the default Storage removed.
Optional<File/DirectoryEntryRef> is replaced with a typedef.

I tested this with GCC 7.5, the oldest supported GCC I had around.

Differential Revision: https://reviews.llvm.org/D140332
2022-12-20 00:41:40 +01:00
Krzysztof Parzyszek
205c0589f9 Revert "[clang] Convert OptionalFileEntryRefDegradesToFileEntryPtr to std::optional"
This reverts commit 8f0df9f3bbc6d7f3d5cbfd955c5ee4404c53a75d.

The Optional*RefDegradesTo*EntryPtr types want to keep the same size as
the underlying type, which std::optional doesn't guarantee. For use with
llvm::Optional, they define their own storage class, and there is no way
to do that in std::optional.

On top of that, that commit broke builds with older GCCs, where
std::optional was not trivially copyable (static_assert in the clang
sources was failing).
2022-12-18 11:23:54 -08:00
Krzysztof Parzyszek
8f0df9f3bb [clang] Convert OptionalFileEntryRefDegradesToFileEntryPtr to std::optional 2022-12-17 15:24:14 -08:00
Kazu Hirata
5891420e68 [clang] Use std::nullopt instead of None (NFC)
This patch mechanically replaces None with std::nullopt where the
compiler would warn if None were deprecated.  The intent is to reduce
the amount of manual work required in migrating from Optional to
std::optional.

This is part of an effort to migrate from llvm::Optional to
std::optional:

https://discourse.llvm.org/t/deprecating-llvm-optional-x-hasvalue-getvalue-getvalueor/63716
2022-12-03 11:54:46 -08:00
Ben Langmuir
c4436f675d [clang] Use InMemoryModuleCache for readASTFileControlBlock NFC
When a pcm has already been loaded from disk, reuse it from the
InMemoryModuleCache in readASTFileControlBlock. This avoids potentially
reading it again.

As noted in the FIXME, ideally we would also add the module to the cache
if it will be used again later, but that could modify its build state
and we do not have enough context currenlty to know if it's correct.

Differential Revision: https://reviews.llvm.org/D138160
2022-11-17 13:47:46 -08:00