Skip to content

Commit

Permalink
[error] Fix null dereference on invalid "switch" value.
Browse files Browse the repository at this point in the history
  • Loading branch information
pfusik committed Nov 14, 2023
1 parent 9f1f31e commit 9c6accf
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 168 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ test/node_modules: test/package.json
cd $(<D) && npm i --no-package-lock

test/bin/%/error.txt: test/error/%.fu fut
$(DO)mkdir -p $(@D) && ! $(FUT) -o $(@:%.txt=%.cs) $< 2>$@ && perl -ne 'print "$$ARGV($$.): $$1\n" while m!//(ERROR: .+?)(?=$$| //)!g' $< | diff -u --strip-trailing-cr - $@ && echo PASSED >$@
$(DO)mkdir -p $(@D) && $(FUT) -o $(@:%.txt=%.cs) $< 2>$@ || test $$? -eq 1 && perl -ne 'print "$$ARGV($$.): $$1\n" while m!//(ERROR: .+?)(?=$$| //)!g' $< | diff -u --strip-trailing-cr - $@ && echo PASSED >$@

test-transpile: $(patsubst test/%.fu, test/$(FUT_HOST)/%/all, $(TESTS)) test/$(FUT_HOST)/fut/all

Expand Down
84 changes: 44 additions & 40 deletions Sema.fu
Original file line number Diff line number Diff line change
Expand Up @@ -1835,53 +1835,57 @@ public class FuSema
{
OpenScope(statement);
statement.Value = VisitExpr(statement.Value);
switch (statement.Value.Type) {
case FuIntegerType i when i.Id != FuId.LongType:
case FuEnum _:
// case FuStringType _: matched by case FuClassType
break;
case FuClassType klass when !(klass is FuStorageType):
break;
default:
ReportError(statement.Value, $"'switch' on type '{statement.Value.Type}' - expected 'int', 'enum', 'string' or object reference");
return;
if (statement.Value != this.Poison) {
switch (statement.Value.Type) {
case FuIntegerType i when i.Id != FuId.LongType:
case FuEnum _:
// case FuStringType _: matched by case FuClassType
break;
case FuClassType klass when !(klass is FuStorageType):
break;
default:
ReportError(statement.Value, $"'switch' on type '{statement.Value.Type}' - expected 'int', 'enum', 'string' or object reference");
break;
}
}
statement.SetCompletesNormally(false);
foreach (FuCase! kase in statement.Cases) {
for (int i = 0; i < kase.Values.Count; i++) {
if (statement.Value.Type is FuClassType switchPtr && switchPtr.Class.Id != FuId.StringClass) {
FuExpr# value = kase.Values[i];
if (value is FuBinaryExpr when1 && when1.Op == FuToken.When)
value = when1.Left;
if (value is FuLiteralNull) {
if (statement.Value != this.Poison) {
for (int i = 0; i < kase.Values.Count; i++) {
if (statement.Value.Type is FuClassType switchPtr && switchPtr.Class.Id != FuId.StringClass) {
FuExpr# value = kase.Values[i];
if (value is FuBinaryExpr when1 && when1.Op == FuToken.When)
value = when1.Left;
if (value is FuLiteralNull) {
}
else if (!(value is FuVar# def) || def.Value != null)
ReportError(kase.Values[i], "Expected 'case Type name'");
else if (!(ResolveType(def) is FuClassType casePtr) || casePtr is FuStorageType)
ReportError(def, "'case' with non-reference type");
else if (casePtr is FuReadWriteClassType
&& !(switchPtr is FuDynamicPtrType)
&& (casePtr is FuDynamicPtrType || !(switchPtr is FuReadWriteClassType)))
ReportError(def, $"'{switchPtr}' cannot be casted to '{casePtr}'");
else if (casePtr.Class.IsSameOrBaseOf(switchPtr.Class))
ReportError(def, $"'{statement.Value}' is '{switchPtr}', 'case {casePtr}' would always match");
else if (!switchPtr.Class.IsSameOrBaseOf(casePtr.Class))
ReportError(def, $"'{switchPtr}' is not base class of '{casePtr.Class.Name}', 'case {casePtr}' would never match");
else {
statement.Add(def);
if (kase.Values[i] is FuBinaryExpr! when2 && when2.Op == FuToken.When)
when2.Right = ResolveBool(when2.Right);
}
}
else if (kase.Values[i] is FuBinaryExpr! when1 && when1.Op == FuToken.When) {
when1.Left = FoldConst(when1.Left);
Coerce(when1.Left, statement.Value.Type);
when1.Right = ResolveBool(when1.Right);
}
else if (!(value is FuVar# def) || def.Value != null)
ReportError(kase.Values[i], "Expected 'case Type name'");
else if (!(ResolveType(def) is FuClassType casePtr) || casePtr is FuStorageType)
ReportError(def, "'case' with non-reference type");
else if (casePtr is FuReadWriteClassType
&& !(switchPtr is FuDynamicPtrType)
&& (casePtr is FuDynamicPtrType || !(switchPtr is FuReadWriteClassType)))
ReportError(def, $"'{switchPtr}' cannot be casted to '{casePtr}'");
else if (casePtr.Class.IsSameOrBaseOf(switchPtr.Class))
ReportError(def, $"'{statement.Value}' is '{switchPtr}', 'case {casePtr}' would always match");
else if (!switchPtr.Class.IsSameOrBaseOf(casePtr.Class))
ReportError(def, $"'{switchPtr}' is not base class of '{casePtr.Class.Name}', 'case {casePtr}' would never match");
else {
statement.Add(def);
if (kase.Values[i] is FuBinaryExpr! when2 && when2.Op == FuToken.When)
when2.Right = ResolveBool(when2.Right);
kase.Values[i] = FoldConst(kase.Values[i]);
Coerce(kase.Values[i], statement.Value.Type);
}
}
else if (kase.Values[i] is FuBinaryExpr! when1 && when1.Op == FuToken.When) {
when1.Left = FoldConst(when1.Left);
Coerce(when1.Left, statement.Value.Type);
when1.Right = ResolveBool(when1.Right);
}
else {
kase.Values[i] = FoldConst(kase.Values[i]);
Coerce(kase.Values[i], statement.Value.Type);
}
}
if (ResolveStatements(kase.Body))
ReportError(kase.Body.Last(), "'case' must end with 'break', 'continue', 'return' or 'throw'");
Expand Down
92 changes: 47 additions & 45 deletions libfut.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5933,60 +5933,62 @@ void FuSema::visitSwitch(FuSwitch * statement)
{
openScope(statement);
statement->value = visitExpr(statement->value);
const FuIntegerType * i;
const FuClassType * klass;
if (((i = dynamic_cast<const FuIntegerType *>(statement->value->type.get())) && i->id != FuId::longType) || dynamic_cast<const FuEnum *>(statement->value->type.get())) {
}
else if ((klass = dynamic_cast<const FuClassType *>(statement->value->type.get())) && !dynamic_cast<const FuStorageType *>(klass)) {
}
else {
reportError(statement->value.get(), std::format("'switch' on type '{}' - expected 'int', 'enum', 'string' or object reference", statement->value->type->toString()));
return;
if (statement->value != this->poison) {
const FuIntegerType * i;
const FuClassType * klass;
if (((i = dynamic_cast<const FuIntegerType *>(statement->value->type.get())) && i->id != FuId::longType) || dynamic_cast<const FuEnum *>(statement->value->type.get())) {
}
else if ((klass = dynamic_cast<const FuClassType *>(statement->value->type.get())) && !dynamic_cast<const FuStorageType *>(klass)) {
}
else
reportError(statement->value.get(), std::format("'switch' on type '{}' - expected 'int', 'enum', 'string' or object reference", statement->value->type->toString()));
}
statement->setCompletesNormally(false);
for (FuCase &kase : statement->cases) {
for (int i = 0; i < std::ssize(kase.values); i++) {
const FuClassType * switchPtr;
if ((switchPtr = dynamic_cast<const FuClassType *>(statement->value->type.get())) && switchPtr->class_->id != FuId::stringClass) {
std::shared_ptr<FuExpr> value = kase.values[i];
const FuBinaryExpr * when1;
if ((when1 = dynamic_cast<const FuBinaryExpr *>(value.get())) && when1->op == FuToken::when)
value = when1->left;
if (dynamic_cast<const FuLiteralNull *>(value.get())) {
}
else {
std::shared_ptr<FuVar> def;
if (!(def = std::dynamic_pointer_cast<FuVar>(value)) || def->value != nullptr)
reportError(kase.values[i].get(), "Expected 'case Type name'");
if (statement->value != this->poison) {
for (int i = 0; i < std::ssize(kase.values); i++) {
const FuClassType * switchPtr;
if ((switchPtr = dynamic_cast<const FuClassType *>(statement->value->type.get())) && switchPtr->class_->id != FuId::stringClass) {
std::shared_ptr<FuExpr> value = kase.values[i];
const FuBinaryExpr * when1;
if ((when1 = dynamic_cast<const FuBinaryExpr *>(value.get())) && when1->op == FuToken::when)
value = when1->left;
if (dynamic_cast<const FuLiteralNull *>(value.get())) {
}
else {
const FuClassType * casePtr;
if (!(casePtr = dynamic_cast<const FuClassType *>(resolveType(def.get()).get())) || dynamic_cast<const FuStorageType *>(casePtr))
reportError(def.get(), "'case' with non-reference type");
else if (dynamic_cast<const FuReadWriteClassType *>(casePtr) && !dynamic_cast<const FuDynamicPtrType *>(switchPtr) && (dynamic_cast<const FuDynamicPtrType *>(casePtr) || !dynamic_cast<const FuReadWriteClassType *>(switchPtr)))
reportError(def.get(), std::format("'{}' cannot be casted to '{}'", switchPtr->toString(), casePtr->toString()));
else if (casePtr->class_->isSameOrBaseOf(switchPtr->class_))
reportError(def.get(), std::format("'{}' is '{}', 'case {}' would always match", statement->value->toString(), switchPtr->toString(), casePtr->toString()));
else if (!switchPtr->class_->isSameOrBaseOf(casePtr->class_))
reportError(def.get(), std::format("'{}' is not base class of '{}', 'case {}' would never match", switchPtr->toString(), casePtr->class_->name, casePtr->toString()));
std::shared_ptr<FuVar> def;
if (!(def = std::dynamic_pointer_cast<FuVar>(value)) || def->value != nullptr)
reportError(kase.values[i].get(), "Expected 'case Type name'");
else {
statement->add(def);
FuBinaryExpr * when2;
if ((when2 = dynamic_cast<FuBinaryExpr *>(kase.values[i].get())) && when2->op == FuToken::when)
when2->right = resolveBool(when2->right);
const FuClassType * casePtr;
if (!(casePtr = dynamic_cast<const FuClassType *>(resolveType(def.get()).get())) || dynamic_cast<const FuStorageType *>(casePtr))
reportError(def.get(), "'case' with non-reference type");
else if (dynamic_cast<const FuReadWriteClassType *>(casePtr) && !dynamic_cast<const FuDynamicPtrType *>(switchPtr) && (dynamic_cast<const FuDynamicPtrType *>(casePtr) || !dynamic_cast<const FuReadWriteClassType *>(switchPtr)))
reportError(def.get(), std::format("'{}' cannot be casted to '{}'", switchPtr->toString(), casePtr->toString()));
else if (casePtr->class_->isSameOrBaseOf(switchPtr->class_))
reportError(def.get(), std::format("'{}' is '{}', 'case {}' would always match", statement->value->toString(), switchPtr->toString(), casePtr->toString()));
else if (!switchPtr->class_->isSameOrBaseOf(casePtr->class_))
reportError(def.get(), std::format("'{}' is not base class of '{}', 'case {}' would never match", switchPtr->toString(), casePtr->class_->name, casePtr->toString()));
else {
statement->add(def);
FuBinaryExpr * when2;
if ((when2 = dynamic_cast<FuBinaryExpr *>(kase.values[i].get())) && when2->op == FuToken::when)
when2->right = resolveBool(when2->right);
}
}
}
}
}
else {
FuBinaryExpr * when1;
if ((when1 = dynamic_cast<FuBinaryExpr *>(kase.values[i].get())) && when1->op == FuToken::when) {
when1->left = foldConst(when1->left);
coerce(when1->left.get(), statement->value->type.get());
when1->right = resolveBool(when1->right);
}
else {
kase.values[i] = foldConst(kase.values[i]);
coerce(kase.values[i].get(), statement->value->type.get());
FuBinaryExpr * when1;
if ((when1 = dynamic_cast<FuBinaryExpr *>(kase.values[i].get())) && when1->op == FuToken::when) {
when1->left = foldConst(when1->left);
coerce(when1->left.get(), statement->value->type.get());
when1->right = resolveBool(when1->right);
}
else {
kase.values[i] = foldConst(kase.values[i]);
coerce(kase.values[i].get(), statement->value->type.get());
}
}
}
}
Expand Down
78 changes: 41 additions & 37 deletions libfut.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6050,50 +6050,54 @@ void VisitSwitch(FuSwitch statement)
{
OpenScope(statement);
statement.Value = VisitExpr(statement.Value);
switch (statement.Value.Type) {
case FuIntegerType i when i.Id != FuId.LongType:
case FuEnum _:
break;
case FuClassType klass when !(klass is FuStorageType):
break;
default:
ReportError(statement.Value, $"'switch' on type '{statement.Value.Type}' - expected 'int', 'enum', 'string' or object reference");
return;
if (statement.Value != this.Poison) {
switch (statement.Value.Type) {
case FuIntegerType i when i.Id != FuId.LongType:
case FuEnum _:
break;
case FuClassType klass when !(klass is FuStorageType):
break;
default:
ReportError(statement.Value, $"'switch' on type '{statement.Value.Type}' - expected 'int', 'enum', 'string' or object reference");
break;
}
}
statement.SetCompletesNormally(false);
foreach (FuCase kase in statement.Cases) {
for (int i = 0; i < kase.Values.Count; i++) {
if (statement.Value.Type is FuClassType switchPtr && switchPtr.Class.Id != FuId.StringClass) {
FuExpr value = kase.Values[i];
if (value is FuBinaryExpr when1 && when1.Op == FuToken.When)
value = when1.Left;
if (value is FuLiteralNull) {
if (statement.Value != this.Poison) {
for (int i = 0; i < kase.Values.Count; i++) {
if (statement.Value.Type is FuClassType switchPtr && switchPtr.Class.Id != FuId.StringClass) {
FuExpr value = kase.Values[i];
if (value is FuBinaryExpr when1 && when1.Op == FuToken.When)
value = when1.Left;
if (value is FuLiteralNull) {
}
else if (!(value is FuVar def) || def.Value != null)
ReportError(kase.Values[i], "Expected 'case Type name'");
else if (!(ResolveType(def) is FuClassType casePtr) || casePtr is FuStorageType)
ReportError(def, "'case' with non-reference type");
else if (casePtr is FuReadWriteClassType && !(switchPtr is FuDynamicPtrType) && (casePtr is FuDynamicPtrType || !(switchPtr is FuReadWriteClassType)))
ReportError(def, $"'{switchPtr}' cannot be casted to '{casePtr}'");
else if (casePtr.Class.IsSameOrBaseOf(switchPtr.Class))
ReportError(def, $"'{statement.Value}' is '{switchPtr}', 'case {casePtr}' would always match");
else if (!switchPtr.Class.IsSameOrBaseOf(casePtr.Class))
ReportError(def, $"'{switchPtr}' is not base class of '{casePtr.Class.Name}', 'case {casePtr}' would never match");
else {
statement.Add(def);
if (kase.Values[i] is FuBinaryExpr when2 && when2.Op == FuToken.When)
when2.Right = ResolveBool(when2.Right);
}
}
else if (kase.Values[i] is FuBinaryExpr when1 && when1.Op == FuToken.When) {
when1.Left = FoldConst(when1.Left);
Coerce(when1.Left, statement.Value.Type);
when1.Right = ResolveBool(when1.Right);
}
else if (!(value is FuVar def) || def.Value != null)
ReportError(kase.Values[i], "Expected 'case Type name'");
else if (!(ResolveType(def) is FuClassType casePtr) || casePtr is FuStorageType)
ReportError(def, "'case' with non-reference type");
else if (casePtr is FuReadWriteClassType && !(switchPtr is FuDynamicPtrType) && (casePtr is FuDynamicPtrType || !(switchPtr is FuReadWriteClassType)))
ReportError(def, $"'{switchPtr}' cannot be casted to '{casePtr}'");
else if (casePtr.Class.IsSameOrBaseOf(switchPtr.Class))
ReportError(def, $"'{statement.Value}' is '{switchPtr}', 'case {casePtr}' would always match");
else if (!switchPtr.Class.IsSameOrBaseOf(casePtr.Class))
ReportError(def, $"'{switchPtr}' is not base class of '{casePtr.Class.Name}', 'case {casePtr}' would never match");
else {
statement.Add(def);
if (kase.Values[i] is FuBinaryExpr when2 && when2.Op == FuToken.When)
when2.Right = ResolveBool(when2.Right);
kase.Values[i] = FoldConst(kase.Values[i]);
Coerce(kase.Values[i], statement.Value.Type);
}
}
else if (kase.Values[i] is FuBinaryExpr when1 && when1.Op == FuToken.When) {
when1.Left = FoldConst(when1.Left);
Coerce(when1.Left, statement.Value.Type);
when1.Right = ResolveBool(when1.Right);
}
else {
kase.Values[i] = FoldConst(kase.Values[i]);
Coerce(kase.Values[i], statement.Value.Type);
}
}
if (ResolveStatements(kase.Body))
ReportError(kase.Body[^1], "'case' must end with 'break', 'continue', 'return' or 'throw'");
Expand Down
Loading

0 comments on commit 9c6accf

Please sign in to comment.