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
4#include "qqmljsshadowcheck_p.h"
5
6QT_BEGIN_NAMESPACE
7
8using namespace Qt::StringLiterals;
9
10/*!
11 * \internal
12 * \class QQmlJSShadowCheck
13 *
14 * This pass looks for possible shadowing when accessing members of QML-exposed
15 * types. A member can be shadowed if a non-final property is re-declared in a
16 * derived class. As the QML engine will always pick up the most derived variant
17 * of that property, we cannot rely on any property of a type to be actually
18 * accessible, unless one of a few special cases holds:
19 *
20 * 1. We are dealing with a direct scope lookup, without an intermediate object.
21 * Such lookups are protected from shadowing. For example "property int a: b"
22 * always works.
23 * 2. The object we are retrieving the property from is identified by an ID, or
24 * an attached property or a singleton. Such objects cannot be replaced.
25 * Therefore we can be sure to see all the type information at compile time.
26 * 3. The property is declared final.
27 * 4. The object we are retrieving the property from is a value type. Value
28 * types cannot be used polymorphically.
29 *
30 * If the property is potentially shadowed, we can still retrieve it, but we
31 * don't know its type. We should assume "var" then.
32 *
33 * All of the above also holds for methods. There we have to transform the
34 * arguments and return types into "var".
35 */
36
37QQmlJSCompilePass::BlocksAndAnnotations QQmlJSShadowCheck::run(const Function *function,
38 QQmlJS::DiagnosticMessage *error)
39{
40 m_function = function;
41 m_error = error;
42 m_state = initialState(function);
43 decode(code: m_function->code.constData(), len: static_cast<uint>(m_function->code.size()));
44
45 for (const auto &store : m_resettableStores)
46 checkResettable(accumulatorIn: store.accumulatorIn, instructionOffset: store.instructionOffset);
47
48 // Re-check all base types. We may have made them var after detecting them.
49 for (const auto &base : m_baseTypes) {
50 if (checkBaseType(baseType: base) == Shadowable)
51 break;
52 }
53
54 return { .basicBlocks: std::move(m_basicBlocks), .annotations: std::move(m_annotations) };
55}
56
57void QQmlJSShadowCheck::generate_LoadProperty(int nameIndex)
58{
59 if (!m_state.readsRegister(registerIndex: Accumulator))
60 return; // enum lookup cannot be shadowed.
61
62 auto accumulatorIn = m_state.registers.find(key: Accumulator);
63 if (accumulatorIn != m_state.registers.end()) {
64 checkShadowing(
65 baseType: accumulatorIn.value().content, propertyName: m_jsUnitGenerator->stringForIndex(index: nameIndex),
66 baseRegister: Accumulator);
67 }
68}
69
70void QQmlJSShadowCheck::generate_GetLookup(int index)
71{
72 if (!m_state.readsRegister(registerIndex: Accumulator))
73 return; // enum lookup cannot be shadowed.
74
75 auto accumulatorIn = m_state.registers.find(key: Accumulator);
76 if (accumulatorIn != m_state.registers.end()) {
77 checkShadowing(
78 baseType: accumulatorIn.value().content, propertyName: m_jsUnitGenerator->lookupName(index), baseRegister: Accumulator);
79 }
80}
81
82void QQmlJSShadowCheck::generate_GetOptionalLookup(int index, int offset)
83{
84 Q_UNUSED(offset);
85 generate_GetLookup(index);
86}
87
88void QQmlJSShadowCheck::handleStore(int base, const QString &memberName)
89{
90 const int instructionOffset = currentInstructionOffset();
91 const QQmlJSRegisterContent &readAccumulator
92 = m_annotations[instructionOffset].readRegisters[Accumulator].content;
93 const auto baseType = m_state.registers[base].content;
94
95 // If the accumulator is already read as var, we don't have to do anything.
96 if (m_typeResolver->registerContains(reg: readAccumulator, type: m_typeResolver->varType())) {
97 if (checkBaseType(baseType) == NotShadowable)
98 m_baseTypes.append(t: baseType);
99 return;
100 }
101
102 if (checkShadowing(baseType, propertyName: memberName, baseRegister: base) == Shadowable)
103 return;
104
105 // If the property isn't shadowable, we have to turn the read register into
106 // var if the accumulator can hold undefined. This has to be done in a second pass
107 // because the accumulator may still turn into var due to its own shadowing.
108 const QQmlJSRegisterContent member = m_typeResolver->memberType(type: baseType, name: memberName);
109 if (member.isProperty())
110 m_resettableStores.append(t: {.accumulatorIn: m_state.accumulatorIn(), .instructionOffset: instructionOffset});
111}
112
113void QQmlJSShadowCheck::generate_StoreProperty(int nameIndex, int base)
114{
115 handleStore(base, memberName: m_jsUnitGenerator->stringForIndex(index: nameIndex));
116}
117
118void QQmlJSShadowCheck::generate_SetLookup(int index, int base)
119{
120 handleStore(base, memberName: m_jsUnitGenerator->lookupName(index));
121}
122
123void QQmlJSShadowCheck::generate_CallProperty(int nameIndex, int base, int argc, int argv)
124{
125 Q_UNUSED(argc);
126 Q_UNUSED(argv);
127 checkShadowing(baseType: m_state.registers[base].content, propertyName: m_jsUnitGenerator->lookupName(index: nameIndex), baseRegister: base);
128}
129
130void QQmlJSShadowCheck::generate_CallPropertyLookup(int nameIndex, int base, int argc, int argv)
131{
132 Q_UNUSED(argc);
133 Q_UNUSED(argv);
134 checkShadowing(baseType: m_state.registers[base].content, propertyName: m_jsUnitGenerator->lookupName(index: nameIndex), baseRegister: base);
135}
136
137QV4::Moth::ByteCodeHandler::Verdict QQmlJSShadowCheck::startInstruction(QV4::Moth::Instr::Type)
138{
139 m_state = nextStateFromAnnotations(oldState: m_state, annotations: m_annotations);
140 return (m_state.hasSideEffects() || m_state.changedRegisterIndex() != InvalidRegister)
141 ? ProcessInstruction
142 : SkipInstruction;
143}
144
145void QQmlJSShadowCheck::endInstruction(QV4::Moth::Instr::Type)
146{
147}
148
149QQmlJSShadowCheck::Shadowability QQmlJSShadowCheck::checkShadowing(
150 const QQmlJSRegisterContent &baseType, const QString &memberName, int baseRegister)
151{
152 if (checkBaseType(baseType) == Shadowable)
153 return Shadowable;
154 else
155 m_baseTypes.append(t: baseType);
156
157 if (baseType.storedType()->accessSemantics() != QQmlJSScope::AccessSemantics::Reference)
158 return NotShadowable;
159
160 switch (baseType.variant()) {
161 case QQmlJSRegisterContent::ExtensionObjectProperty:
162 case QQmlJSRegisterContent::ExtensionScopeProperty:
163 case QQmlJSRegisterContent::MethodReturnValue:
164 case QQmlJSRegisterContent::ObjectProperty:
165 case QQmlJSRegisterContent::ScopeProperty:
166 case QQmlJSRegisterContent::Unknown: {
167 const QQmlJSRegisterContent member = m_typeResolver->memberType(type: baseType, name: memberName);
168
169 // You can have something like parent.QtQuick.Screen.pixelDensity
170 // In that case "QtQuick" cannot be resolved as member type and we would later have to look
171 // for "QtQuick.Screen" instead. However, you can only do that with attached properties and
172 // those are not shadowable.
173 if (!member.isValid()) {
174 Q_ASSERT(m_typeResolver->isPrefix(memberName));
175 return NotShadowable;
176 }
177
178 if (member.isProperty()) {
179 if (member.property().isFinal())
180 return NotShadowable; // final properties can't be shadowed
181 } else if (!member.isMethod()) {
182 return NotShadowable; // Only properties and methods can be shadowed
183 }
184
185 m_logger->log(
186 message: u"Member %1 of %2 can be shadowed"_s.arg(args: memberName, args: baseType.descriptiveName()),
187 id: qmlCompiler, srcLocation: currentSourceLocation());
188
189 // Make it "var". We don't know what it is.
190 const QQmlJSScope::ConstPtr varType = m_typeResolver->varType();
191 const QQmlJSRegisterContent varContent = m_typeResolver->globalType(type: varType);
192 InstructionAnnotation &currentAnnotation = m_annotations[currentInstructionOffset()];
193
194 if (currentAnnotation.changedRegisterIndex != InvalidRegister) {
195 m_typeResolver->adjustOriginalType(
196 tracked: currentAnnotation.changedRegister.storedType(), conversion: varType);
197 m_typeResolver->adjustOriginalType(
198 tracked: m_typeResolver->containedType(container: currentAnnotation.changedRegister), conversion: varType);
199 m_adjustedTypes.insert(value: currentAnnotation.changedRegister);
200 }
201
202 for (auto it = currentAnnotation.readRegisters.begin(),
203 end = currentAnnotation.readRegisters.end();
204 it != end; ++it) {
205 if (it.key() != baseRegister)
206 it->second.content = m_typeResolver->convert(from: it->second.content, to: varContent);
207 }
208 return Shadowable;
209 }
210 default:
211 // In particular ObjectById is fine as that cannot change into something else
212 // Singleton should also be fine, unless the factory function creates an object
213 // with different property types than the declared class.
214 return NotShadowable;
215 }
216}
217
218void QQmlJSShadowCheck::checkResettable(
219 const QQmlJSRegisterContent &accumulatorIn, int instructionOffset)
220{
221 const QQmlJSScope::ConstPtr varType = m_typeResolver->varType();
222
223 // The stored type is not necessarily updated by the shadow check, but it
224 // will be in the basic blocks pass. For the purpose of adjusting newly
225 // shadowable types we can ignore it. We only want to know if any of the
226 // contents can hold undefined.
227 if (!m_typeResolver->canHoldUndefined(content: accumulatorIn.storedIn(newStoredType: varType)))
228 return;
229
230 const QQmlJSRegisterContent varContent = m_typeResolver->globalType(type: varType);
231
232 QQmlJSRegisterContent &readAccumulator
233 = m_annotations[instructionOffset].readRegisters[Accumulator].content;
234 readAccumulator = m_typeResolver->convert(from: readAccumulator, to: varContent);
235}
236
237QQmlJSShadowCheck::Shadowability QQmlJSShadowCheck::checkBaseType(
238 const QQmlJSRegisterContent &baseType)
239{
240 if (!m_adjustedTypes.contains(value: baseType))
241 return NotShadowable;
242 setError(u"Cannot use shadowable base type for further lookups: %1"_s.arg(a: baseType.descriptiveName()));
243 return Shadowable;
244}
245
246QT_END_NAMESPACE
247

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtdeclarative/src/qmlcompiler/qqmljsshadowcheck.cpp