Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmljsshadowcheck.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3// Qt-Security score:significant
4
6
8
9using namespace Qt::StringLiterals;
10
11/*!
12 * \internal
13 * \class QQmlJSShadowCheck
14 *
15 * This pass looks for possible shadowing when accessing members of QML-exposed
16 * types. A member can be shadowed if a non-final property is re-declared in a
17 * derived class. As the QML engine will always pick up the most derived variant
18 * of that property, we cannot rely on any property of a type to be actually
19 * accessible, unless one of a few special cases holds:
20 *
21 * 1. We are dealing with a direct scope lookup, without an intermediate object.
22 * Such lookups are protected from shadowing. For example "property int a: b"
23 * always works.
24 * 2. The object we are retrieving the property from is identified by an ID, or
25 * an attached property or a singleton. Such objects cannot be replaced.
26 * Therefore we can be sure to see all the type information at compile time.
27 * 3. The property is declared final.
28 * 4. The object we are retrieving the property from is a value type. Value
29 * types cannot be used polymorphically.
30 *
31 * If the property is potentially shadowed, we can still retrieve it, but we
32 * don't know its type. We should assume "var" then.
33 *
34 * All of the above also holds for methods. There we have to transform the
35 * arguments and return types into "var".
36 */
37
38QQmlJSCompilePass::BlocksAndAnnotations QQmlJSShadowCheck::run(const Function *function)
39{
40 m_function = function;
41 m_state = initialState(function);
42 decode(m_function->code.constData(), static_cast<uint>(m_function->code.size()));
43
44 for (const auto &store : std::as_const(m_resettableStores))
45 checkResettable(store.accumulatorIn, store.instructionOffset);
46
47 // Re-check all base types. We may have made them var after detecting them.
48 for (const auto &base : std::as_const(m_baseTypes)) {
49 if (checkBaseType(base) == Shadowable)
50 break;
51 }
52
53 return { std::move(m_basicBlocks), std::move(m_annotations) };
54}
55
56void QQmlJSShadowCheck::generate_LoadProperty(int nameIndex)
57{
58 if (!m_state.readsRegister(Accumulator))
59 return; // enum lookup cannot be shadowed.
60
61 auto accumulatorIn = m_state.registers.find(Accumulator);
62 if (accumulatorIn != m_state.registers.end()) {
63 checkShadowing(
64 accumulatorIn.value().content, m_jsUnitGenerator->stringForIndex(nameIndex),
65 Accumulator);
66 }
67}
68
69void QQmlJSShadowCheck::generate_GetLookup(int index)
70{
71 if (!m_state.readsRegister(Accumulator))
72 return; // enum lookup cannot be shadowed.
73
74 auto accumulatorIn = m_state.registers.find(Accumulator);
75 if (accumulatorIn != m_state.registers.end()) {
76 checkShadowing(
77 accumulatorIn.value().content, m_jsUnitGenerator->lookupName(index), Accumulator);
78 }
79}
80
81void QQmlJSShadowCheck::generate_GetOptionalLookup(int index, int offset)
82{
83 Q_UNUSED(offset);
84 generate_GetLookup(index);
85}
86
87void QQmlJSShadowCheck::handleStore(int base, const QString &memberName)
88{
89 const int instructionOffset = currentInstructionOffset();
90 QQmlJSRegisterContent readAccumulator
91 = m_annotations[instructionOffset].readRegisters[Accumulator].content;
92 const auto baseType = m_state.registers[base].content;
93
94 // If the accumulator is already read as var, we don't have to do anything.
95 if (readAccumulator.contains(m_typeResolver->varType())) {
96 if (checkBaseType(baseType) == NotShadowable)
97 m_baseTypes.append(baseType);
98 return;
99 }
100
101 if (checkShadowing(baseType, memberName, base) == Shadowable)
102 return;
103
104 // If the property isn't shadowable, we have to turn the read register into
105 // var if the accumulator can hold undefined. This has to be done in a second pass
106 // because the accumulator may still turn into var due to its own shadowing.
107 const QQmlJSRegisterContent member = m_typeResolver->memberType(baseType, memberName);
108 if (member.isProperty())
109 m_resettableStores.append({m_state.accumulatorIn(), instructionOffset});
110}
111
112void QQmlJSShadowCheck::generate_StoreProperty(int nameIndex, int base)
113{
114 handleStore(base, m_jsUnitGenerator->stringForIndex(nameIndex));
115}
116
117void QQmlJSShadowCheck::generate_SetLookup(int index, int base)
118{
119 handleStore(base, m_jsUnitGenerator->lookupName(index));
120}
121
122void QQmlJSShadowCheck::generate_CallProperty(int nameIndex, int base, int argc, int argv)
123{
124 Q_UNUSED(argc);
125 Q_UNUSED(argv);
126 checkShadowing(m_state.registers[base].content, m_jsUnitGenerator->lookupName(nameIndex), base);
127}
128
129void QQmlJSShadowCheck::generate_CallPropertyLookup(int nameIndex, int base, int argc, int argv)
130{
131 Q_UNUSED(argc);
132 Q_UNUSED(argv);
133 checkShadowing(m_state.registers[base].content, m_jsUnitGenerator->lookupName(nameIndex), base);
134}
135
136QV4::Moth::ByteCodeHandler::Verdict QQmlJSShadowCheck::startInstruction(QV4::Moth::Instr::Type)
137{
138 m_state = nextStateFromAnnotations(m_state, m_annotations);
139 return (m_state.hasInternalSideEffects() || m_state.changedRegisterIndex() != InvalidRegister)
140 ? ProcessInstruction
141 : SkipInstruction;
142}
143
144void QQmlJSShadowCheck::endInstruction(QV4::Moth::Instr::Type)
145{
146}
147
148QQmlJSShadowCheck::Shadowability QQmlJSShadowCheck::checkShadowing(
149 QQmlJSRegisterContent baseType, const QString &memberName, int baseRegister)
150{
151 if (checkBaseType(baseType) == Shadowable)
152 return Shadowable;
153 else
154 m_baseTypes.append(baseType);
155
156 // JavaScript objects are not shadowable, as far as we can analyze them at all.
157 if (baseType.containedType()->isJavaScriptBuiltin())
158 return NotShadowable;
159
160 if (!baseType.containedType()->isReferenceType())
161 return NotShadowable;
162
163 switch (baseType.variant()) {
164 case QQmlJSRegisterContent::Singleton:
165 // composite singletons can't have a create() function. The create() function
166 // is what allows a derived class to be returned.
167 if (baseType.containedType()->isComposite())
168 return NotShadowable;
169
170 // Extended singletons aren't shadowable. There is no space for the extra members
171 // in between the type's metaobject and the extension's metaobject. The derived type
172 // can't be known in advance since you can only produce it through a factory function
173 // that covertly returns a derived type rather than the declared one.
174 if (baseType.containedType()->extensionType().extensionSpecifier
175 != QQmlJSScope::NotExtension) {
176 return NotShadowable;
177 }
178
179 Q_FALLTHROUGH();
180 case QQmlJSRegisterContent::MethodCall:
181 case QQmlJSRegisterContent::Property:
182 case QQmlJSRegisterContent::TypeByName:
183 case QQmlJSRegisterContent::Cast:
184 case QQmlJSRegisterContent::Unknown: {
185 const QQmlJSRegisterContent member = m_typeResolver->memberType(baseType, memberName);
186
187 // You can have something like parent.QtQuick.Screen.pixelDensity
188 // In that case "QtQuick" cannot be resolved as member type and we would later have to look
189 // for "QtQuick.Screen" instead. However, you can only do that with attached properties and
190 // those are not shadowable.
191 if (!member.isValid()) {
192 Q_ASSERT(m_typeResolver->isPrefix(memberName));
193 return NotShadowable;
194 }
195
196 if (member.isProperty()) {
197 if (member.property().isFinal())
198 return NotShadowable; // final properties can't be shadowed
199 } else if (!member.isMethod()) {
200 return NotShadowable; // Only properties and methods can be shadowed
201 }
202
203 m_logger->log(
204 u"Member %1 of %2 can be shadowed"_s.arg(memberName, baseType.descriptiveName()),
205 qmlCompiler, currentSourceLocation());
206
207 // Make it "var". We don't know what it is.
208 const QQmlJSScope::ConstPtr varType = m_typeResolver->varType();
209 InstructionAnnotation &currentAnnotation = m_annotations[currentInstructionOffset()];
210 currentAnnotation.isShadowable = true;
211
212 if (currentAnnotation.changedRegisterIndex != InvalidRegister) {
213 m_typeResolver->adjustOriginalType(currentAnnotation.changedRegister, varType);
214 m_adjustedTypes.insert(currentAnnotation.changedRegister);
215 }
216
217 for (auto it = currentAnnotation.readRegisters.begin(),
218 end = currentAnnotation.readRegisters.end();
219 it != end; ++it) {
220 if (it.key() != baseRegister)
221 it->second.content = m_typeResolver->convert(it->second.content, varType);
222 }
223
224 return Shadowable;
225 }
226 default:
227 // In particular ObjectById is fine as that cannot change into something else.
228 return NotShadowable;
229 }
230}
231
232void QQmlJSShadowCheck::checkResettable(
233 QQmlJSRegisterContent accumulatorIn, int instructionOffset)
234{
235 if (!m_typeResolver->canHoldUndefined(accumulatorIn))
236 return;
237
238 QQmlJSRegisterContent &readAccumulator
239 = m_annotations[instructionOffset].readRegisters[Accumulator].content;
240 readAccumulator = m_typeResolver->convert(readAccumulator, m_typeResolver->varType());
241}
242
243QQmlJSShadowCheck::Shadowability QQmlJSShadowCheck::checkBaseType(
244 QQmlJSRegisterContent baseType)
245{
246 if (!m_adjustedTypes.contains(baseType))
247 return NotShadowable;
248 addError(u"Cannot use shadowable base type for further lookups: %1"_s.arg(baseType.descriptiveName()));
249 return Shadowable;
250}
251
252QT_END_NAMESPACE